Merge "Rename NotificationEntry.isChildInGroup()" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 2d164f8..b924ac8 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -16,11 +16,13 @@
":android.app.usage.flags-aconfig-java{.generated_srcjars}",
":android.companion.flags-aconfig-java{.generated_srcjars}",
":android.content.pm.flags-aconfig-java{.generated_srcjars}",
+ ":android.content.res.flags-aconfig-java{.generated_srcjars}",
":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
":android.nfc.flags-aconfig-java{.generated_srcjars}",
":android.os.flags-aconfig-java{.generated_srcjars}",
":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
":android.security.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}",
":camera_platform_flags_core_java_lib{.generated_srcjars}",
@@ -33,10 +35,13 @@
":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
":android.widget.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.media.audio.flags-aconfig-java{.generated_srcjars}",
":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
":sdk_sandbox_flags_lib{.generated_srcjars}",
":android.permission.flags-aconfig-java{.generated_srcjars}",
+ ":android.database.sqlite-aconfig-java{.generated_srcjars}",
":hwui_flags_java_lib{.generated_srcjars}",
+ ":framework_graphics_flags_java_lib{.generated_srcjars}",
":display_flags_lib{.generated_srcjars}",
":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
":android.multiuser.flags-aconfig-java{.generated_srcjars}",
@@ -44,7 +49,10 @@
":android.credentials.flags-aconfig-java{.generated_srcjars}",
":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
":android.service.voice.flags-aconfig-java{.generated_srcjars}",
+ ":aconfig_midi_flags_java_lib{.generated_srcjars}",
":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.net.flags-aconfig-java{.generated_srcjars}",
+ ":device_policy_aconfig_flags_lib{.generated_srcjars}",
]
filegroup {
@@ -301,6 +309,19 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Resources
+aconfig_declarations {
+ name: "android.content.res.flags-aconfig",
+ package: "android.content.res",
+ srcs: ["core/java/android/content/res/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.content.res.flags-aconfig-java",
+ aconfig_declarations: "android.content.res.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Media BetterTogether
aconfig_declarations {
name: "com.android.media.flags.bettertogether-aconfig",
@@ -314,6 +335,13 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Media Audio
+java_aconfig_library {
+ name: "com.android.media.audio.flags-aconfig-java",
+ aconfig_declarations: "aconfig_audio_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Permissions
aconfig_declarations {
name: "android.permission.flags-aconfig",
@@ -332,6 +360,19 @@
}
+// SQLite
+aconfig_declarations {
+ name: "android.database.sqlite-aconfig",
+ package: "android.database.sqlite",
+ srcs: ["core/java/android/database/sqlite/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.database.sqlite-aconfig-java",
+ aconfig_declarations: "android.database.sqlite-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Biometrics
aconfig_declarations {
name: "android.hardware.biometrics.flags-aconfig",
@@ -352,6 +393,12 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "framework_graphics_flags_java_lib",
+ aconfig_declarations: "framework_graphics_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Display
java_aconfig_library {
name: "display_flags_lib",
@@ -471,3 +518,43 @@
aconfig_declarations: "android.companion.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// CoreNetworking
+java_aconfig_library {
+ name: "com.android.net.flags-aconfig-java",
+ aconfig_declarations: "com.android.net.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// DevicePolicy
+aconfig_declarations {
+ name: "device_policy_aconfig_flags",
+ package: "android.app.admin.flags",
+ srcs: [
+ "core/java/android/app/admin/flags/flags.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "device_policy_aconfig_flags_lib",
+ aconfig_declarations: "device_policy_aconfig_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+cc_aconfig_library {
+ name: "device_policy_aconfig_flags_c_lib",
+ aconfig_declarations: "device_policy_aconfig_flags",
+}
+
+// Notifications
+aconfig_declarations {
+ name: "android.service.notification.flags-aconfig",
+ package: "android.service.notification",
+ srcs: ["core/java/android/service/notification/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.service.notification.flags-aconfig-java",
+ aconfig_declarations: "android.service.notification.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index a507465a..0c199a6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -227,7 +227,6 @@
"android.hardware.radio.messaging-V3-java",
"android.hardware.radio.modem-V3-java",
"android.hardware.radio.network-V3-java",
- "android.hardware.radio.satellite-V1-java",
"android.hardware.radio.sim-V3-java",
"android.hardware.radio.voice-V3-java",
"android.hardware.thermal-V1.0-java-constants",
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 9218cc9..da02298 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -59,6 +59,7 @@
// Extract the impl jar from "framework-minus-apex.ravenwood-base" for subsequent build rules.
java_genrule_host {
name: "framework-minus-apex.ravenwood",
+ defaults: ["hoststubgen-for-prototype-only-genrule"],
cmd: "cp $(in) $(out)",
srcs: [
":framework-minus-apex.ravenwood-base{ravenwood.jar}",
@@ -66,5 +67,4 @@
out: [
"framework-minus-apex.ravenwood.jar",
],
- visibility: ["//visibility:public"],
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index f1403bd5..e833bb9 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -439,7 +439,7 @@
* provides an easy way to tell whether the job is being executed due to the deadline
* expiring. Note: If the job is running because its deadline expired, it implies that its
* constraints will not be met. However,
- * {@link android.app.job.JobInfo.Builder#setPeriodic(boolean) periodic jobs} will only ever
+ * {@link android.app.job.JobInfo.Builder#setPeriodic(long) periodic jobs} will only ever
* run when their constraints are satisfied, therefore, the constraints will still be satisfied
* for a periodic job even if the deadline has expired.
*/
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 4e3cb7d..3e835b8 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -2,7 +2,7 @@
flag {
name: "relax_prefetch_connectivity_constraint_only_on_charger"
- namespace: "backstagepower"
+ namespace: "backstage_power"
description: "Only relax a prefetch job's connectivity constraint when the device is charging"
bug: "299329948"
}
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 3cbee5d0..384a480 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -173,8 +173,6 @@
import dalvik.annotation.optimization.NeverCompile;
-import libcore.util.EmptyArray;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -260,7 +258,7 @@
/**
* A map from uid to the last op-mode we have seen for
* {@link AppOpsManager#OP_SCHEDULE_EXACT_ALARM}. Used for evaluating permission state change
- * when the denylist changes.
+ * when the app-op changes.
*/
@VisibleForTesting
@GuardedBy("mLock")
@@ -671,9 +669,6 @@
@VisibleForTesting
final class Constants implements DeviceConfig.OnPropertiesChangedListener,
EconomyManagerInternal.TareStateChangeListener {
- @VisibleForTesting
- static final int MAX_EXACT_ALARM_DENY_LIST_SIZE = 250;
-
// Key names stored in the settings value.
@VisibleForTesting
static final String KEY_MIN_FUTURITY = "min_futurity";
@@ -727,8 +722,6 @@
@VisibleForTesting
static final String KEY_PRIORITY_ALARM_DELAY = "priority_alarm_delay";
@VisibleForTesting
- static final String KEY_EXACT_ALARM_DENY_LIST = "exact_alarm_deny_list";
- @VisibleForTesting
static final String KEY_MIN_DEVICE_IDLE_FUZZ = "min_device_idle_fuzz";
@VisibleForTesting
static final String KEY_MAX_DEVICE_IDLE_FUZZ = "max_device_idle_fuzz";
@@ -835,13 +828,6 @@
public long PRIORITY_ALARM_DELAY = DEFAULT_PRIORITY_ALARM_DELAY;
/**
- * Read-only set of apps that won't get SCHEDULE_EXACT_ALARM when the app-op mode for
- * OP_SCHEDULE_EXACT_ALARM is MODE_DEFAULT. Since this is read-only and volatile, this can
- * be accessed without synchronizing on {@link #mLock}.
- */
- public volatile Set<String> EXACT_ALARM_DENY_LIST = Collections.emptySet();
-
- /**
* Minimum time interval that an IDLE_UNTIL will be pulled earlier to a subsequent
* WAKE_FROM_IDLE alarm.
*/
@@ -1025,21 +1011,6 @@
PRIORITY_ALARM_DELAY = properties.getLong(KEY_PRIORITY_ALARM_DELAY,
DEFAULT_PRIORITY_ALARM_DELAY);
break;
- case KEY_EXACT_ALARM_DENY_LIST:
- final String rawValue = properties.getString(KEY_EXACT_ALARM_DENY_LIST,
- "");
- final String[] values = rawValue.isEmpty()
- ? EmptyArray.STRING
- : rawValue.split(",", MAX_EXACT_ALARM_DENY_LIST_SIZE + 1);
- if (values.length > MAX_EXACT_ALARM_DENY_LIST_SIZE) {
- Slog.w(TAG, "Deny list too long, truncating to "
- + MAX_EXACT_ALARM_DENY_LIST_SIZE + " elements.");
- updateExactAlarmDenyList(
- Arrays.copyOf(values, MAX_EXACT_ALARM_DENY_LIST_SIZE));
- } else {
- updateExactAlarmDenyList(values);
- }
- break;
case KEY_MIN_DEVICE_IDLE_FUZZ:
case KEY_MAX_DEVICE_IDLE_FUZZ:
if (!deviceIdleFuzzBoundariesUpdated) {
@@ -1110,28 +1081,6 @@
}
}
- private void updateExactAlarmDenyList(String[] newDenyList) {
- final Set<String> newSet = Collections.unmodifiableSet(new ArraySet<>(newDenyList));
- final Set<String> removed = new ArraySet<>(EXACT_ALARM_DENY_LIST);
- final Set<String> added = new ArraySet<>(newDenyList);
-
- added.removeAll(EXACT_ALARM_DENY_LIST);
- removed.removeAll(newSet);
- if (added.size() > 0) {
- mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED, added)
- .sendToTarget();
- }
- if (removed.size() > 0) {
- mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED, removed)
- .sendToTarget();
- }
- if (newDenyList.length == 0) {
- EXACT_ALARM_DENY_LIST = Collections.emptySet();
- } else {
- EXACT_ALARM_DENY_LIST = newSet;
- }
- }
-
private void updateDeviceIdleFuzzBoundaries() {
final DeviceConfig.Properties properties = DeviceConfig.getProperties(
DeviceConfig.NAMESPACE_ALARM_MANAGER,
@@ -1277,9 +1226,6 @@
TimeUtils.formatDuration(PRIORITY_ALARM_DELAY, pw);
pw.println();
- pw.print(KEY_EXACT_ALARM_DENY_LIST, EXACT_ALARM_DENY_LIST);
- pw.println();
-
pw.print(KEY_MIN_DEVICE_IDLE_FUZZ);
pw.print("=");
TimeUtils.formatDuration(MIN_DEVICE_IDLE_FUZZ, pw);
@@ -2114,14 +2060,10 @@
? permissionState
: (newMode == AppOpsManager.MODE_ALLOWED);
} else {
- final boolean allowedByDefault =
- !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
hadPermission = (oldMode == AppOpsManager.MODE_DEFAULT)
- ? allowedByDefault
- : (oldMode == AppOpsManager.MODE_ALLOWED);
+ || (oldMode == AppOpsManager.MODE_ALLOWED);
hasPermission = (newMode == AppOpsManager.MODE_DEFAULT)
- ? allowedByDefault
- : (newMode == AppOpsManager.MODE_ALLOWED);
+ || (newMode == AppOpsManager.MODE_ALLOWED);
}
if (hadPermission && !hasPermission) {
@@ -2769,11 +2711,8 @@
// Compatibility permission check for older apps.
final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid,
packageName);
- if (mode == AppOpsManager.MODE_DEFAULT) {
- hasPermission = !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
- } else {
- hasPermission = (mode == AppOpsManager.MODE_ALLOWED);
- }
+ hasPermission = (mode == AppOpsManager.MODE_DEFAULT)
+ || (mode == AppOpsManager.MODE_ALLOWED);
}
mStatLogger.logDurationStat(Stats.HAS_SCHEDULE_EXACT_ALARM, start);
return hasPermission;
@@ -3993,63 +3932,6 @@
}
/**
- * Called when the {@link Constants#EXACT_ALARM_DENY_LIST}, changes with the packages that
- * either got added or deleted.
- * These packages may lose or gain the SCHEDULE_EXACT_ALARM permission.
- *
- * Note that these packages don't need to be installed on the device, but if they are and they
- * do undergo a permission change, we will handle them appropriately.
- *
- * This should not be called with the lock held as it calls out to other services.
- * This is not expected to get called frequently.
- */
- void handleChangesToExactAlarmDenyList(ArraySet<String> changedPackages, boolean added) {
- Slog.w(TAG, "Packages " + changedPackages + (added ? " added to" : " removed from")
- + " the exact alarm deny list.");
-
- final int[] startedUserIds = mActivityManagerInternal.getStartedUserIds();
-
- for (int i = 0; i < changedPackages.size(); i++) {
- final String changedPackage = changedPackages.valueAt(i);
- for (final int userId : startedUserIds) {
- final int uid = mPackageManagerInternal.getPackageUid(changedPackage, 0, userId);
- if (uid <= 0) {
- continue;
- }
- if (!isExactAlarmChangeEnabled(changedPackage, userId)) {
- continue;
- }
- if (isScheduleExactAlarmDeniedByDefault(changedPackage, userId)) {
- continue;
- }
- if (hasUseExactAlarmInternal(changedPackage, uid)) {
- continue;
- }
- if (!mExactAlarmCandidates.contains(UserHandle.getAppId(uid))) {
- // Permission isn't requested, deny list doesn't matter.
- continue;
- }
- final int appOpMode;
- synchronized (mLock) {
- appOpMode = mLastOpScheduleExactAlarm.get(uid,
- AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM));
- }
- if (appOpMode != AppOpsManager.MODE_DEFAULT) {
- // Deny list doesn't matter.
- continue;
- }
- // added: true => package was added to the deny list
- // added: false => package was removed from the deny list
- if (added) {
- removeExactAlarmsOnPermissionRevoked(uid, changedPackage, /*killUid = */ true);
- } else {
- sendScheduleExactAlarmPermissionStateChangedBroadcast(changedPackage, userId);
- }
- }
- }
- }
-
- /**
* Called when an app loses the permission to use exact alarms. This will happen when the app
* no longer has either {@link Manifest.permission#SCHEDULE_EXACT_ALARM} or
* {@link Manifest.permission#USE_EXACT_ALARM}.
@@ -4931,8 +4813,8 @@
public static final int CHARGING_STATUS_CHANGED = 6;
public static final int REMOVE_FOR_CANCELED = 7;
public static final int REMOVE_EXACT_ALARMS = 8;
- public static final int EXACT_ALARM_DENY_LIST_PACKAGES_ADDED = 9;
- public static final int EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED = 10;
+ // Unused id 9
+ // Unused id 10
public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11;
public static final int TARE_AFFORDABILITY_CHANGED = 12;
public static final int CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE = 13;
@@ -5041,12 +4923,6 @@
String packageName = (String) msg.obj;
removeExactAlarmsOnPermissionRevoked(uid, packageName, /*killUid = */true);
break;
- case EXACT_ALARM_DENY_LIST_PACKAGES_ADDED:
- handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, true);
- break;
- case EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED:
- handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, false);
- break;
case REFRESH_EXACT_ALARM_CANDIDATES:
refreshExactAlarmCandidates();
break;
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index 30b4423..e162100 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -124,7 +124,6 @@
"packages/modules/Media/apex/aidl/stable",
],
},
- extensions_info_file: ":sdk-extensions-info",
}
droidstubs {
@@ -132,13 +131,7 @@
defaults: ["framework-doc-stubs-sources-default"],
args: metalava_framework_docs_args +
" --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ",
- api_levels_annotations_enabled: true,
- api_levels_annotations_dirs: [
- "sdk-dir",
- "api-versions-jars-dir",
- ],
- api_levels_sdk_type: "system",
- extensions_info_file: ":sdk-extensions-info",
+ api_levels_module: "api_versions_system",
}
/////////////////////////////////////////////////////////////////////
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index fa4bc0f..7e41660 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -695,6 +695,7 @@
"api-stubs-docs-non-updatable.api.contribution",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
java_api_library {
@@ -710,6 +711,7 @@
"system-api-stubs-docs-non-updatable.api.contribution",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
java_api_library {
@@ -727,6 +729,7 @@
"test-api-stubs-docs-non-updatable.api.contribution",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
java_api_library {
@@ -742,6 +745,7 @@
"api-stubs-docs-non-updatable.api.contribution",
"system-api-stubs-docs-non-updatable.api.contribution",
],
+ enable_validation: false,
}
java_api_library {
@@ -761,6 +765,7 @@
"module-lib-api-stubs-docs-non-updatable.api.contribution",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
java_api_library {
@@ -774,6 +779,7 @@
"stub-annotations",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
java_api_library {
@@ -798,6 +804,7 @@
visibility: [
"//visibility:private",
],
+ enable_validation: false,
}
java_api_library {
@@ -814,6 +821,7 @@
"android_module_lib_stubs_current.from-text",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
////////////////////////////////////////////////////////////////////////
diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline
index 1f023bd..29a8dfa 100644
--- a/api/javadoc-lint-baseline
+++ b/api/javadoc-lint-baseline
@@ -1,68 +1,4 @@
-android/adservices/ondevicepersonalization/DownloadCompletedInput.java:22: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onDownloadCompleted() IsolatedWorker#onDownloadCompleted()" in android.adservices.ondevicepersonalization.DownloadCompletedInput [101]
-android/adservices/ondevicepersonalization/DownloadCompletedOutput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onDownloadCompleted() IsolatedWorker#onDownloadCompleted()" in android.adservices.ondevicepersonalization.DownloadCompletedOutput [101]
-android/adservices/ondevicepersonalization/EventLogRecord.java:13: lint: Unresolved link/see tag "RequestRecordRecord" in android.adservices.ondevicepersonalization.EventLogRecord [101]
-android/adservices/ondevicepersonalization/EventUrlProvider.java:43: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onEvent IsolatedWorker#onEvent" in android.adservices.ondevicepersonalization.EventUrlProvider [101]
-android/adservices/ondevicepersonalization/ExecuteInput.java:22: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteInput [101]
-android/adservices/ondevicepersonalization/ExecuteOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput [101]
-android/adservices/ondevicepersonalization/ExecuteOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute() OnDevicePersonalizationManager#execute()" in android.adservices.ondevicepersonalization.ExecuteOutput [101]
-android/adservices/ondevicepersonalization/ExecuteOutput.java:31: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput [101]
-android/adservices/ondevicepersonalization/ExecuteOutput.java:93: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.ExecuteOutput.Builder [101]
-android/adservices/ondevicepersonalization/IsolatedService.java:18: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.IsolatedService [101]
-android/adservices/ondevicepersonalization/IsolatedService.java:18: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute" in android.adservices.ondevicepersonalization.IsolatedService [101]
-android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "IsolatedCmputationCallback#onWebViewEvent()" in android.adservices.ondevicepersonalization.IsolatedService [101]
-android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "IsolatedCmputationCallback#onEvent()" in android.adservices.ondevicepersonalization.IsolatedService [101]
-android/adservices/ondevicepersonalization/IsolatedService.java:119: lint: Unresolved link/see tag "WebView" in android.adservices.ondevicepersonalization.IsolatedService [101]
-android/adservices/ondevicepersonalization/IsolatedWorker.java:9: lint: Unresolved link/see tag "RunTimeException" in android.adservices.ondevicepersonalization.IsolatedWorker [101]
-android/adservices/ondevicepersonalization/IsolatedWorker.java:24: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#execute" in android.adservices.ondevicepersonalization.IsolatedWorker [101]
-android/adservices/ondevicepersonalization/IsolatedWorker.java:57: lint: Unresolved link/see tag "#onExecute()" in android.adservices.ondevicepersonalization.IsolatedWorker [101]
-android/adservices/ondevicepersonalization/IsolatedWorker.java:74: lint: Unresolved link/see tag "#onRender()" in android.adservices.ondevicepersonalization.IsolatedWorker [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:-11: lint: Unresolved link/see tag "requestSurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:11: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:11: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedService#onExecute() IsolatedService#onExecute()" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:19: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:54: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:54: lint: Unresolved link/see tag "SurfaceView#getHostToken()" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:54: lint: Unresolved link/see tag "execute" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "#execute()" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:60: lint: Unresolved link/see tag "View" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:64: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:69: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/OnDevicePersonalizationManager.java:70: lint: Unresolved link/see tag "SurfacePackage" in android.adservices.ondevicepersonalization.OnDevicePersonalizationManager [101]
-android/adservices/ondevicepersonalization/RenderInput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onRender() IsolatedWorker#onRender()" in android.adservices.ondevicepersonalization.RenderInput [101]
-android/adservices/ondevicepersonalization/RenderInput.java:53: lint: Unresolved link/see tag "onExecute" in android.adservices.ondevicepersonalization.RenderInput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.OnDevicePersonalizationManager#requestSurfacePackage() OnDevicePersonalizationManager#requestSurfacePackage()" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:31: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:31: lint: Unresolved link/see tag "getTemplateParams" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:41: lint: Unresolved link/see tag "getContent()" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:52: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:102: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:102: lint: Unresolved link/see tag "getTemplateParams" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:114: lint: Unresolved link/see tag "getContent()" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101]
-android/adservices/ondevicepersonalization/RenderOutput.java:127: lint: Unresolved link/see tag "getTemplateId()" in android.adservices.ondevicepersonalization.RenderOutput.Builder [101]
-android/adservices/ondevicepersonalization/RenderingConfig.java:20: lint: Unresolved link/see tag "View" in android.adservices.ondevicepersonalization.RenderingConfig [101]
-android/adservices/ondevicepersonalization/RenderingConfig.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.RenderingConfig [101]
-android/adservices/ondevicepersonalization/RenderingConfig.java:20: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onRender() IsolatedWorker#onRender()" in android.adservices.ondevicepersonalization.RenderingConfig [101]
-android/adservices/ondevicepersonalization/RenderingConfig.java:33: lint: Unresolved link/see tag "IsolatedSurface#getRemoteData" in android.adservices.ondevicepersonalization.RenderingConfig [101]
-android/adservices/ondevicepersonalization/RenderingConfig.java:85: lint: Unresolved link/see tag "IsolatedSurface#getRemoteData" in android.adservices.ondevicepersonalization.RenderingConfig.Builder [101]
-android/adservices/ondevicepersonalization/RequestLogRecord.java:19: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.RequestLogRecord [101]
-android/adservices/ondevicepersonalization/SurfacePackageToken.java:20: lint: Unresolved link/see tag "SurfaceView" in android.adservices.ondevicepersonalization.SurfacePackageToken [101]
-android/adservices/ondevicepersonalization/WebViewEventInput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onWebViewEvent() IsolatedWorker#onWebViewEvent()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]
-android/adservices/ondevicepersonalization/WebViewEventInput.java:30: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]
-android/adservices/ondevicepersonalization/WebViewEventInput.java:41: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.EventUrlProvider#createEventTrackingUrlWithResponse() EventUrlProvider#createEventTrackingUrlWithResponse()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]
-android/adservices/ondevicepersonalization/WebViewEventOutput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onWebViewEvent() IsolatedWorker#onWebViewEvent()" in android.adservices.ondevicepersonalization.WebViewEventOutput [101]
-android/app/ActivityOptions.java:366: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101]
-android/app/ActivityOptions.java:370: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101]
-android/app/ActivityOptions.java:384: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101]
-android/app/ApplicationStartInfo.java:96: lint: Unresolved link/see tag "#START_TIMESTAMP_JAVA_CLASSLOADING_COMPLETE" in android.app.ApplicationStartInfo [101]
-android/app/BroadcastOptions.java:132: lint: Unresolved link/see tag "#setDeliveryGroupMatchingFilter(android.content.IntentFilter)" in android.app.BroadcastOptions [101]
-android/app/GrammaticalInflectionManager.java:60: lint: Unresolved link/see tag "android.os.Environment#getDataSystemCeDirectory(int)" in android.app.GrammaticalInflectionManager [101]
-android/app/Notification.java:509: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.app.Notification [101]
-android/app/Notification.java:650: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.app.Notification [101]
-android/app/Notification.java:1866: lint: Unresolved link/see tag "/*missing*/" in android.app.Notification.Action [101]
-android/app/Notification.java:4796: lint: Unresolved link/see tag "android.content.pm.ShortcutInfo#setLongLived() ShortcutInfo#setLongLived()" in android.app.Notification.MessagingStyle [101]
+// b/305195721
android/app/admin/DevicePolicyManager.java:2670: lint: Unresolved link/see tag "android.os.UserManager#DISALLOW_CAMERA UserManager#DISALLOW_CAMERA" in android.app.admin.DevicePolicyManager [101]
android/app/admin/DevicePolicyManager.java:7257: lint: Unresolved link/see tag "android.app.admin.DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY" in android.app.admin.DevicePolicyManager [101]
android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "ACTION_DEVICE_FINANCING_STATE_CHANGED" in android.app.admin.DevicePolicyManager [101]
@@ -71,6 +7,8 @@
android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Drawables DevicePolicyResources.Drawables" in android.app.admin.DevicePolicyManager [101]
android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyManager [101]
android/app/admin/DevicePolicyResourcesManager.java:179: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyResourcesManager [101]
+
+// b/303477132
android/app/appsearch/AppSearchSchema.java:402: lint: Unresolved link/see tag "#getIndexableNestedProperties()" in android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder [101]
android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.AppSearchSession [101]
android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.AppSearchSession [101]
@@ -83,33 +21,8 @@
android/app/appsearch/SearchSpec.java:913: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.SearchSpec.Builder [101]
android/app/appsearch/SearchSpec.java:925: lint: Unresolved link/see tag "Features#VERBATIM_SEARCH" in android.app.appsearch.SearchSpec.Builder [101]
android/app/appsearch/SearchSpec.java:929: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.SearchSpec.Builder [101]
-android/app/job/JobParameters.java:128: lint: Unresolved link/see tag "android.app.job.JobInfo.Builder#setPeriodic(boolean) periodic jobs" in android.app.job.JobParameters [101]
-android/app/sdksandbox/AppOwnedSdkSandboxInterface.java:9: lint: Unresolved link/see tag "SdkSandboxController#getAppOwnedSdkSandboxInterfaces" in android.app.sdksandbox.AppOwnedSdkSandboxInterface [101]
-android/app/sdksandbox/SdkSandboxManager.java:112: lint: Unresolved link/see tag "AppOwnedSdkSandboxInterfaces" in android.app.sdksandbox.SdkSandboxManager [101]
-android/companion/CompanionDeviceService.java:273: lint: Unresolved link/see tag "android.companion.AssociationInfo#isSelfManaged() self-managed" in android.companion.CompanionDeviceService [101]
-android/companion/CompanionDeviceService.java:282: lint: Unresolved link/see tag "android.companion.AssociationInfo#isSelfManaged() self-managed" in android.companion.CompanionDeviceService [101]
-android/companion/virtual/VirtualDevice.java:15: lint: Unresolved link/see tag "android.companion.virtual.VirtualDeviceManager.VirtualDevice VirtualDeviceManager.VirtualDevice" in android.companion.virtual.VirtualDevice [101]
-android/companion/virtual/VirtualDevice.java:70: lint: Unresolved link/see tag "android.companion.virtual.VirtualDeviceParams.Builder#setName(String)" in android.companion.virtual.VirtualDevice [101]
-android/content/AttributionSource.java:291: lint: Unresolved link/see tag "setNextAttributionSource" in android.content.AttributionSource.Builder [101]
-android/content/Context.java:2872: lint: Unresolved link/see tag "android.telephony.MmsManager" in android.content.Context [101]
-android/content/Intent.java:4734: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/content/Intent.java:4760: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/content/Intent.java:4778: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/content/Intent.java:4802: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/credentials/CreateCredentialException.java:22: lint: Unresolved link/see tag "android.credentials.CredentialManager#createCredential(android.credentials.CreateCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#createCredential(CreateCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.CreateCredentialException [101]
-android/credentials/CreateCredentialException.java:101: lint: Unresolved link/see tag "android.credentials.CredentialManager#createCredential(android.credentials.CreateCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#createCredential(CreateCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.CreateCredentialException [101]
-android/credentials/CreateCredentialRequest.java:107: lint: Unresolved link/see tag "androidx.credentials.CreateCredentialRequest" in android.credentials.CreateCredentialRequest.Builder [101]
-android/credentials/CredentialDescription.java:89: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mSupportedElementKeys CredentialDescription#mSupportedElementKeys" in android.credentials.CredentialDescription [101]
-android/credentials/CredentialDescription.java:89: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mType CredentialDescription#mType" in android.credentials.CredentialDescription [101]
-android/credentials/CredentialDescription.java:101: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mSupportedElementKeys CredentialDescription#mSupportedElementKeys" in android.credentials.CredentialDescription [101]
-android/credentials/CredentialDescription.java:101: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mType CredentialDescription#mType" in android.credentials.CredentialDescription [101]
-android/credentials/GetCredentialException.java:22: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.GetCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.GetCredentialException [101]
-android/credentials/GetCredentialException.java:103: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.GetCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver)" in android.credentials.GetCredentialException [101]
-android/credentials/PrepareGetCredentialResponse.java:20: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse [101]
-android/credentials/PrepareGetCredentialResponse.java:68: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse [101]
-android/credentials/PrepareGetCredentialResponse.java:83: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle [101]
-android/graphics/Paint.java:838: lint: Unresolved link/see tag "android.annotation.ColorLong ColorLong" in android.graphics.Paint [101]
-android/graphics/text/LineBreaker.java:246: lint: Unresolved link/see tag "StaticLayout.Builder#setUseBoundsForWidth(boolean)" in android.graphics.text.LineBreaker.Builder [101]
+
+// b/303582215
android/hardware/camera2/CameraCharacteristics.java:2169: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101]
android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101]
android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations tables" in android.hardware.camera2.CameraCharacteristics [101]
@@ -135,161 +48,37 @@
android/hardware/camera2/CaptureRequest.java:1501: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureRequest [101]
android/hardware/camera2/CaptureResult.java:923: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101]
android/hardware/camera2/CaptureResult.java:2337: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101]
-android/hardware/input/InputManager.java:215: lint: Unresolved link/see tag "android.hardware.input.InputManagerGlobal#getInputDevice InputManagerGlobal#getInputDevice" in android.hardware.input.InputManager.InputDeviceListener [101]
-android/inputmethodservice/AbstractInputMethodService.java:155: lint: Unresolved link/see tag "android.app.ActivityThread ActivityThread" in android.inputmethodservice.AbstractInputMethodService [101]
-android/inputmethodservice/InputMethodService.java:1078: lint: Unresolved link/see tag "android.widget.Editor" in android.inputmethodservice.InputMethodService [101]
-android/location/GnssSignalType.java:14: lint: Unresolved link/see tag "android.location.GnssStatus.ConstellationType GnssStatus.ConstellationType" in android.location.GnssSignalType [101]
-android/location/GnssSignalType.java:48: lint: Unresolved link/see tag "android.location.GnssStatus.ConstellationType GnssStatus.ConstellationType" in android.location.GnssSignalType [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ALARM AttributeSdkUsage#USAGE_ALARM" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANT AttributeSdkUsage#USAGE_ASSISTANT" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_GAME AttributeSdkUsage#USAGE_GAME" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_MEDIA AttributeSdkUsage#USAGE_MEDIA" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_NOTIFICATION_EVENT AttributeSdkUsage#USAGE_NOTIFICATION_EVENT" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_UNKNOWN AttributeSdkUsage#USAGE_UNKNOWN" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_VOICE_COMMUNICATION AttributeSdkUsage#USAGE_VOICE_COMMUNICATION" in android.media.AudioAttributes.Builder [101]
-android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING" in android.media.AudioAttributes.Builder [101]
-android/media/AudioFormat.java:963: lint: Unresolved link/see tag "android.media.AudioSystem#OUT_CHANNEL_COUNT_MAX AudioSystem#OUT_CHANNEL_COUNT_MAX" in android.media.AudioFormat.Builder [101]
-android/media/AudioManager.java:275: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
-android/media/AudioManager.java:287: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
-android/media/AudioManager.java:311: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
-android/media/AudioManager.java:313: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
-android/media/AudioMetadata.java:118: lint: Unresolved link/see tag "android.media.AudioPresentation.ContentClassifier One of {@link android.media.AudioPresentation#CONTENT_UNKNOWN AudioPresentation#CONTENT_UNKNOWN}, {@link android.media.AudioPresentation#CONTENT_MAIN AudioPresentation#CONTENT_MAIN}, {@link android.media.AudioPresentation#CONTENT_MUSIC_AND_EFFECTS AudioPresentation#CONTENT_MUSIC_AND_EFFECTS}, {@link android.media.AudioPresentation#CONTENT_VISUALLY_IMPAIRED AudioPresentation#CONTENT_VISUALLY_IMPAIRED}, {@link android.media.AudioPresentation#CONTENT_HEARING_IMPAIRED AudioPresentation#CONTENT_HEARING_IMPAIRED}, {@link android.media.AudioPresentation#CONTENT_DIALOG AudioPresentation#CONTENT_DIALOG}, {@link android.media.AudioPresentation#CONTENT_COMMENTARY AudioPresentation#CONTENT_COMMENTARY}, {@link android.media.AudioPresentation#CONTENT_EMERGENCY AudioPresentation#CONTENT_EMERGENCY}, {@link android.media.AudioPresentation#CONTENT_VOICEOVER AudioPresentation#CONTENT_VOICEOVER}." in android.media.AudioMetadata.Format [101]
-android/media/MediaRouter2.java:162: lint: Unresolved link/see tag "#getInstance(android.content.Context,java.lang.String)" in android.media.MediaRouter2 [101]
-android/media/midi/MidiUmpDeviceService.java:-1: lint: Unresolved link/see tag "#MidiDeviceService" in android.media.midi.MidiUmpDeviceService [101]
-android/media/tv/SectionRequest.java:44: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionRequest [101]
-android/media/tv/SectionResponse.java:39: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionResponse [101]
-android/media/tv/TableRequest.java:48: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.TableRequest [101]
-android/media/tv/TableResponse.java:82: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.TableResponse [101]
-android/net/EthernetNetworkSpecifier.java:21: lint: Unresolved link/see tag "android.net.EthernetManager" in android.net.EthernetNetworkSpecifier [101]
-android/net/eap/EapSessionConfig.java:120: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101]
-android/net/eap/EapSessionConfig.java:135: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101]
-android/net/eap/EapSessionConfig.java:148: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101]
-android/net/eap/EapSessionConfig.java:161: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.Builder [101]
-android/net/eap/EapSessionConfig.java:288: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.EapAkaConfig [101]
-android/net/eap/EapSessionConfig.java:390: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.EapAkaPrimeConfig [101]
-android/net/eap/EapSessionConfig.java:587: lint: Unresolved link/see tag "android.telephony.Annotation.UiccAppType UiccAppType" in android.net.eap.EapSessionConfig.EapSimConfig [101]
-android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.MloLink [101]
-android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.MloLink [101]
-android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_6_GHZ WifiScanner#WIFI_BAND_6_GHZ" in android.net.wifi.MloLink [101]
-android/net/wifi/MloLink.java:32: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_UNSPECIFIED WifiScanner#WIFI_BAND_UNSPECIFIED" in android.net.wifi.MloLink [101]
-android/net/wifi/SoftApConfiguration.java:9: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder SoftApConfiguration.Builder" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/SoftApConfiguration.java:66: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setSsid(java.lang.String) Builder#setSsid(String)" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/SoftApConfiguration.java:85: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setWifiSsid(android.net.wifi.WifiSsid) Builder#setWifiSsid(WifiSsid)" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/SoftApConfiguration.java:96: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setBssid(android.net.MacAddress) Builder#setBssid(MacAddress)" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/SoftApConfiguration.java:107: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setPassphrase(java.lang.String,int) Builder#setPassphrase(String, int)" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/SoftApConfiguration.java:118: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setHiddenSsid(boolean) Builder#setHiddenSsid(boolean)" in android.net.wifi.SoftApConfiguration [101]
-android/net/wifi/WifiManager.java:764: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setBands(int[]) SoftApConfiguration.Builder#setBands(int[])" in android.net.wifi.WifiManager [101]
-android/net/wifi/WifiManager.java:764: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray) SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray)" in android.net.wifi.WifiManager [101]
-android/net/wifi/WifiManager.java:779: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setBands(int[]) SoftApConfiguration.Builder#setBands(int[])" in android.net.wifi.WifiManager [101]
-android/net/wifi/WifiManager.java:779: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray) SoftApConfiguration.Builder#setChannels(android.util.SparseIntArray)" in android.net.wifi.WifiManager [101]
-android/net/wifi/WifiManager.java:2466: lint: Unresolved link/see tag "TelephonyManager#hasCarrierPrivileges()." in android.net.wifi.WifiManager [101]
-android/net/wifi/aware/PublishConfig.java:50: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.PublishConfig [101]
-android/net/wifi/aware/PublishConfig.java:50: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.PublishConfig [101]
-android/net/wifi/aware/PublishConfig.java:249: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.PublishConfig.Builder [101]
-android/net/wifi/aware/PublishConfig.java:249: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.PublishConfig.Builder [101]
-android/net/wifi/aware/SubscribeConfig.java:51: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.SubscribeConfig [101]
-android/net/wifi/aware/SubscribeConfig.java:51: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig [101]
-android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101]
-android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101]
-android/os/BugreportManager.java:146: lint: Unresolved link/see tag "android.os.BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT" in android.os.BugreportManager.BugreportCallback [101]
-android/os/PowerManager.java:796: lint: Unresolved link/see tag "android.os.Temperature" in android.os.PowerManager.OnThermalStatusChangedListener [101]
-android/os/RemoteException.java:49: lint: Unresolved link/see tag "android.os.DeadSystemRuntimeException DeadSystemRuntimeException" in android.os.RemoteException [101]
-android/provider/Settings.java:374: lint: Unresolved link/see tag "android.credentials.CredentialManager#isEnabledCredentialProviderService()" in android.provider.Settings [101]
-android/provider/Settings.java:908: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService" in android.provider.Settings [101]
-android/provider/Settings.java:2181: lint: Unresolved link/see tag "android.app.time.TimeManager" in android.provider.Settings.Global [101]
-android/provider/Settings.java:2195: lint: Unresolved link/see tag "android.app.time.TimeManager" in android.provider.Settings.Global [101]
-android/security/KeyStoreException.java:27: lint: Unresolved link/see tag "android.security.KeyStoreException.PublicErrorCode PublicErrorCode" in android.security.KeyStoreException [101]
-android/service/autofill/FillResponse.java:86: lint: Unresolved link/see tag "setFieldClassificationIds" in android.service.autofill.FillResponse.Builder [101]
-android/service/autofill/SaveInfo.java:623: lint: Unresolved link/see tag "FillRequest.getHints()" in android.service.autofill.SaveInfo.Builder [101]
-android/service/credentials/Action.java:3: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.Action [101]
-android/service/credentials/Action.java:3: lint: Unresolved link/see tag "androidx.credentials.provider.Action" in android.service.credentials.Action [101]
-android/service/credentials/BeginCreateCredentialResponse.java:85: lint: Unresolved link/see tag "Manifest.permission.PROVIDE_REMOTE_CREDENTIALS" in android.service.credentials.BeginCreateCredentialResponse.Builder [101]
-android/service/credentials/BeginGetCredentialResponse.java:80: lint: Unresolved link/see tag "Manifest.permission.PROVIDE_REMOTE_CREDENTIALS" in android.service.credentials.BeginGetCredentialResponse.Builder [101]
-android/service/credentials/CallingAppInfo.java:73: lint: Unresolved link/see tag "android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN" in android.service.credentials.CallingAppInfo [101]
-android/service/credentials/CreateEntry.java:6: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.CreateEntry [101]
-android/service/credentials/CreateEntry.java:6: lint: Unresolved link/see tag "androidx.credentials.provider.CreateEntry" in android.service.credentials.CreateEntry [101]
-android/service/credentials/CredentialEntry.java:11: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.CredentialEntry [101]
-android/service/credentials/CredentialEntry.java:11: lint: Unresolved link/see tag "androidx.credentials.provider.CredentialEntry" in android.service.credentials.CredentialEntry [101]
-android/service/credentials/RemoteEntry.java:13: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.RemoteEntry [101]
-android/service/credentials/RemoteEntry.java:13: lint: Unresolved link/see tag "androidx.credentials.provider.RemoteEntry" in android.service.credentials.RemoteEntry [101]
-android/service/notification/NotificationListenerService.java:417: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101]
-android/service/notification/NotificationListenerService.java:435: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101]
-android/service/notification/NotificationListenerService.java:1155: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101]
-android/service/notification/NotificationListenerService.java:1166: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101]
-android/service/quickaccesswallet/WalletCard.java:285: lint: Unresolved link/see tag "PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS" in android.service.quickaccesswallet.WalletCard.Builder [101]
-android/service/voice/VoiceInteractionSession.java:293: lint: Unresolved link/see tag "android.service.voice.VoiceInteractionService#KEY_SHOW_SESSION_ID VoiceInteractionService#KEY_SHOW_SESSION_ID" in android.service.voice.VoiceInteractionSession [101]
-android/text/DynamicLayout.java:141: lint: Unresolved link/see tag "LineBreakconfig" in android.text.DynamicLayout [101]
-android/text/WordSegmentFinder.java:13: lint: Unresolved link/see tag "android.text.method.WordIterator WordIterator" in android.text.WordSegmentFinder [101]
-android/view/InputDevice.java:71: lint: Unresolved link/see tag "InputManagerGlobal.InputDeviceListener" in android.view.InputDevice [101]
-android/view/PixelCopy.java:468: lint: Unresolved link/see tag "android.view.PixelCopy.CopyResultStatus CopyResultStatus" in android.view.PixelCopy.Result [101]
-android/view/ScrollFeedbackProvider.java:-25: lint: Unresolved link/see tag "InputManager" in android.view.ScrollFeedbackProvider [101]
-android/view/ScrollFeedbackProvider.java:-25: lint: Unresolved link/see tag "InputManager#getInputDeviceIds()" in android.view.ScrollFeedbackProvider [101]
-android/view/SurfaceControl.java:823: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101]
-android/view/SurfaceControl.java:900: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101]
-android/view/SurfaceControl.java:908: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101]
-android/view/View.java:1647: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setAccessibilityPaneTitle(View, CharSequence)" in android.view.View [101]
-android/view/View.java:4669: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setScreenReaderFocusable(View, boolean)" in android.view.View [101]
-android/view/View.java:4712: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setAccessibilityHeading(View, boolean)" in android.view.View [101]
-android/view/WindowManager.java:230: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowManager [101]
-android/view/WindowManager.java:247: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowManager [101]
-android/view/WindowManager.java:822: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106]
-android/view/WindowManager.java:832: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106]
-android/view/WindowMetrics.java:22: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101]
-android/view/WindowMetrics.java:57: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101]
-android/view/WindowMetrics.java:114: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101]
-android/view/WindowMetrics.java:127: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101]
-android/view/accessibility/AccessibilityNodeInfo.java:368: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#addAccessibilityAction(View, AccessibilityNodeInfoCompat.AccessibilityActionCompat)" in android.view.accessibility.AccessibilityNodeInfo [101]
-android/view/accessibility/AccessibilityNodeInfo.java:3246: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#addAccessibilityAction(View, AccessibilityNodeInfoCompat.AccessibilityActionCompat)" in android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction [101]
-android/view/displayhash/DisplayHashResultCallback.java:38: lint: Unresolved link/see tag "android.view.displayhash.DisplayHashResultCallback.DisplayHashErrorCode DisplayHashErrorCode" in android.view.displayhash.DisplayHashResultCallback [101]
-android/view/inputmethod/EditorInfo.java:107: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.EditorInfo [101]
-android/view/inputmethod/EditorInfo.java:122: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.EditorInfo [101]
-android/view/inputmethod/InputMethodManager.java:423: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101]
-android/view/inputmethod/InputMethodManager.java:447: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101]
-android/view/inputmethod/InputMethodManager.java:456: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101]
-android/view/inspector/PropertyReader.java:141: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.view.inspector.PropertyReader [101]
-android/window/BackEvent.java:24: lint: Unresolved link/see tag "android.window.BackMotionEvent BackMotionEvent" in android.window.BackEvent [101]
-android/net/wifi/SoftApConfiguration.java:173: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)" in android.net.wifi.SoftApConfiguration [101]
-android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101]
+// These are javadoc errors for @ChangeId constants, which are problematic to generate documentation
+// for. They're not necessarily errors in the docs themselves but are also a limitation in the tool.
+// Regardless, the docs currently generated for them is not good, but it is also not used directly
+// in production at the moment.
+// The main limitation is that all references must be fully qualified in order to resolve properly
+// (aside from the normal limitatinos of only being able to @link public APIs).
+// See the CompatInfo.java source file in doclava for more information.
+android/net/wifi/SoftApConfiguration.java:171: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)" in android.net.wifi.SoftApConfiguration [101]
android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware#enabledSinceTargetSdkVersion" in android.os.UserManager [101]
-android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "#initialize( PersistableBundle, SharedMemory, SoundTrigger.ModuleProperties)" in android.service.voice.AlwaysOnHotwordDetector [101]
-android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "STATE_HARDWARE_UNAVAILABLE" in android.service.voice.AlwaysOnHotwordDetector [101]
-android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "#STATE_ERROR" in android [101]
+android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101]
+android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "#initialize( PersistableBundle, SharedMemory, SoundTrigger.ModuleProperties)" in android [101]
+android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "STATE_HARDWARE_UNAVAILABLE" in android [101]
+android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "android.service.voice.AlwaysOnHotwordDetector.Callback#onFailure" in android [101]
+android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "android.service.voice.AlwaysOnHotwordDetector.Callback#onUnknownFailure" in android [101]
+android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "android.service.voice.AlwaysOnHotwordDetector#STATE_ERROR" in android [101]
android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onFailure" in android [101]
android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onUnknownFailure" in android [101]
-android/view/animation/AnimationUtils.java:64: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in android.view.animation.AnimationUtils [101]
-android/view/contentcapture/ContentCaptureSession.java:188: lint: Unresolved link/see tag "UPSIDE_DOWN_CAKE" in android.view.contentcapture.ContentCaptureSession [101]
-com/android/internal/policy/PhoneWindow.java:172: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in com.android.internal.policy.PhoneWindow [101]
-
-com/android/server/companion/virtual/VirtualDeviceImpl.java:134: lint: Unresolved link/see tag "DisplayManager" in android [101]
-com/android/server/companion/virtual/VirtualDeviceImpl.java:134: lint: Unresolved link/see tag "VirtualDeviceManager.VirtualDevice" in android [101]
+android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "#STATE_ERROR" in android [101]
+com/android/internal/policy/PhoneWindow.java:172: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in android [101]
com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "IdentifierType#DAB_SID_EXT" in android [101]
com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT" in android [101]
com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "RadioTuner" in android [101]
-com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "ComponentName" in android [101]
-com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "IllegalArgumentException" in android [101]
-com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "MediaSession#setMediaButtonBroadcastReceiver(ComponentName)" in android [101]
-com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "IllegalArgumentException" in android [101]
-com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "MediaSession#setMediaButtonReceiver(PendingIntent)" in android [101]
-com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "PendingIntent" in android [101]
+com/android/server/broadcastradio/aidl/ConversionUtils.java:72: lint: Unresolved link/see tag "com.android.server.broadcastradio.aidl.IdentifierType#DAB_SID_EXT" in android [101]
+com/android/server/broadcastradio/aidl/ConversionUtils.java:72: lint: Unresolved link/see tag "com.android.server.broadcastradio.aidl.ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT" in android [101]
+com/android/server/broadcastradio/aidl/ConversionUtils.java:72: lint: Unresolved link/see tag "com.android.server.broadcastradio.aidl.RadioTuner" in android [101]
+com/android/server/devicepolicy/DevicePolicyManagerService.java:861: lint: Unresolved link/see tag "android.security.IKeyChainService#setGrant" in android [101]
com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "Build.VERSION_CODES#S API 31" in android [101]
com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequireUserAction" in android [101]
-com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101]
com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "Build.VERSION_CODES#UPSIDE_DOWN_CAKE API 34" in android [101]
com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequestUpdateOwnership(boolean)" in android [101]
-com/android/server/pm/PackageInstallerSession.java:358: lint: Unresolved link/see tag "IntentSender" in android [101]
-com/android/server/devicepolicy/DevicePolicyManagerService.java:860: lint: Unresolved link/see tag "android.security.IKeyChainService#setGrant" in android [101]
-
-android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:-4: lint: Invalid tag: @Override [131]
-android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:-1: lint: Invalid tag: @Override [131]
-android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:2: lint: Invalid tag: @Override [131]
-android/os/BatteryStatsManager.java:260: lint: Invalid tag: @Deprecated [131]
-android/os/BatteryStatsManager.java:275: lint: Invalid tag: @Deprecated [131]
-android/view/WindowManager.java:906: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106]
-android/view/WindowManager.java:916: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106]
-
-java/lang/ClassLoader.java:853: lint: Unknown tag: @systemProperty [103]
+com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101]
+com/android/server/pm/PackageInstallerSession.java:330: lint: Unresolved link/see tag "com.android.android.server.pm#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101]
+com/android/server/pm/PackageInstallerSession.java:358: lint: Unresolved link/see tag "IntentSender" in android [101]
\ No newline at end of file
diff --git a/core/api/current.txt b/core/api/current.txt
index 20424be..8c2ba08 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android {
public final class Manifest {
@@ -670,6 +668,7 @@
field public static final int debuggable = 16842767; // 0x101000f
field public static final int defaultFocusHighlightEnabled = 16844130; // 0x1010562
field public static final int defaultHeight = 16844021; // 0x10104f5
+ field @FlaggedApi("android.content.res.default_locale") public static final int defaultLocale;
field public static final int defaultToDeviceProtectedStorage = 16844036; // 0x1010504
field public static final int defaultValue = 16843245; // 0x10101ed
field public static final int defaultWidth = 16844020; // 0x10104f4
@@ -3288,10 +3287,10 @@
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
- method @Deprecated public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl);
- method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
- method @Deprecated public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl);
- method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+ method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl);
+ method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+ method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl);
+ method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method public boolean clearCache();
method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo);
method public final void disableSelf();
@@ -3403,9 +3402,9 @@
field public static final int GLOBAL_ACTION_RECENTS = 3; // 0x3
field public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9; // 0x9
field public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7; // 0x7
- field public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1
- field public static final int OVERLAY_RESULT_INVALID = 2; // 0x2
- field public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0
+ field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1
+ field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INVALID = 2; // 0x2
+ field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0
field public static final String SERVICE_INTERFACE = "android.accessibilityservice.AccessibilityService";
field public static final String SERVICE_META_DATA = "android.accessibilityservice";
field public static final int SHOW_MODE_AUTO = 0; // 0x0
@@ -5741,6 +5740,7 @@
ctor @Deprecated public FragmentBreadCrumbs(android.content.Context, android.util.AttributeSet);
ctor @Deprecated public FragmentBreadCrumbs(android.content.Context, android.util.AttributeSet, int);
method @Deprecated public void onBackStackChanged();
+ method @Deprecated protected void onLayout(boolean, int, int, int, int);
method @Deprecated public void setActivity(android.app.Activity);
method @Deprecated public void setMaxVisible(int);
method @Deprecated public void setOnBreadCrumbClickListener(android.app.FragmentBreadCrumbs.OnBreadCrumbClickListener);
@@ -6189,6 +6189,7 @@
ctor public LocaleConfig(@NonNull android.os.LocaleList);
method public int describeContents();
method @NonNull public static android.app.LocaleConfig fromContextIgnoringOverride(@NonNull android.content.Context);
+ method @FlaggedApi("android.content.res.default_locale") @Nullable public java.util.Locale getDefaultLocale();
method public int getStatus();
method @Nullable public android.os.LocaleList getSupportedLocales();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -9531,7 +9532,7 @@
method @Nullable public CharSequence getDisplayName();
method public int getId();
method public int getSystemDataSyncFlags();
- method @Nullable public String getTag();
+ method @FlaggedApi("android.companion.association_tag") @Nullable public String getTag();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR;
}
@@ -9601,7 +9602,7 @@
method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void attachSystemDataTransport(int, @NonNull java.io.InputStream, @NonNull java.io.OutputStream) throws android.companion.DeviceNotAssociatedException;
method @Nullable public android.content.IntentSender buildAssociationCancellationIntent();
method @Nullable public android.content.IntentSender buildPermissionTransferUserConsentIntent(int) throws android.companion.DeviceNotAssociatedException;
- method public void clearAssociationTag(int);
+ method @FlaggedApi("android.companion.association_tag") public void clearAssociationTag(int);
method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException;
method public void disableSystemDataSyncForTypes(int, int);
method @Deprecated public void disassociate(@NonNull String);
@@ -9611,7 +9612,7 @@
method @NonNull public java.util.List<android.companion.AssociationInfo> getMyAssociations();
method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
method public void requestNotificationAccess(android.content.ComponentName);
- method public void setAssociationTag(int, @NonNull String);
+ method @FlaggedApi("android.companion.association_tag") public void setAssociationTag(int, @NonNull String);
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException;
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
@@ -9789,7 +9790,7 @@
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 @Deprecated @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
- method @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull 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);
method @NonNull public android.content.AttributionSource.Builder setPid(int);
}
@@ -10572,7 +10573,7 @@
public final class ContextParams {
method @Nullable public String getAttributionTag();
method @Nullable public android.content.AttributionSource getNextAttributionSource();
- method @NonNull public boolean shouldRegisterAttributionSource();
+ method @FlaggedApi("android.permission.flags.should_register_attribution_source") @NonNull public boolean shouldRegisterAttributionSource();
}
public static final class ContextParams.Builder {
@@ -10581,7 +10582,7 @@
method @NonNull public android.content.ContextParams build();
method @NonNull public android.content.ContextParams.Builder setAttributionTag(@Nullable String);
method @NonNull public android.content.ContextParams.Builder setNextAttributionSource(@Nullable android.content.AttributionSource);
- method @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean);
+ method @FlaggedApi("android.permission.flags.should_register_attribution_source") @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean);
}
public class ContextWrapper extends android.content.Context {
@@ -11720,7 +11721,7 @@
method @NonNull public void setResourceValue(@NonNull String, @IntRange(from=android.util.TypedValue.TYPE_FIRST_INT, to=android.util.TypedValue.TYPE_LAST_INT) int, int, @Nullable String);
method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String);
method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
- method @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String);
+ method @FlaggedApi("android.content.res.asset_file_descriptor_frro") @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String);
method public void setTargetOverlayable(@Nullable String);
}
@@ -12676,6 +12677,7 @@
method public boolean isDeviceUpgrading();
method public abstract boolean isInstantApp();
method public abstract boolean isInstantApp(@NonNull String);
+ method @FlaggedApi("android.content.pm.quarantined_enabled") public boolean isPackageQuarantined(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @FlaggedApi("android.content.pm.stay_stopped") public boolean isPackageStopped(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method public boolean isPackageSuspended(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method public boolean isPackageSuspended();
@@ -12914,6 +12916,7 @@
field public static final int MATCH_DIRECT_BOOT_UNAWARE = 262144; // 0x40000
field public static final int MATCH_DISABLED_COMPONENTS = 512; // 0x200
field public static final int MATCH_DISABLED_UNTIL_USED_COMPONENTS = 32768; // 0x8000
+ field @FlaggedApi("android.content.pm.quarantined_enabled") public static final long MATCH_QUARANTINED_COMPONENTS = 4294967296L; // 0x100000000L
field public static final int MATCH_SYSTEM_ONLY = 1048576; // 0x100000
field public static final int MATCH_UNINSTALLED_PACKAGES = 8192; // 0x2000
field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L
@@ -13653,6 +13656,7 @@
public interface XmlResourceParser extends org.xmlpull.v1.XmlPullParser android.util.AttributeSet java.lang.AutoCloseable {
method public void close();
+ method public String getAttributeNamespace(int);
}
}
@@ -14352,14 +14356,14 @@
public final class SQLiteDatabase extends android.database.sqlite.SQLiteClosable {
method public void beginTransaction();
method public void beginTransactionNonExclusive();
- method public void beginTransactionReadOnly();
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public void beginTransactionReadOnly();
method public void beginTransactionWithListener(@Nullable android.database.sqlite.SQLiteTransactionListener);
method public void beginTransactionWithListenerNonExclusive(@Nullable android.database.sqlite.SQLiteTransactionListener);
- method public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException;
method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams);
- method @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
method public int delete(@NonNull String, @Nullable String, @Nullable String[]);
method public static boolean deleteDatabase(@NonNull java.io.File);
method public void disableWriteAheadLogging();
@@ -14370,13 +14374,13 @@
method public void execSQL(@NonNull String, @NonNull Object[]) throws android.database.SQLException;
method public static String findEditTable(String);
method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs();
- method public long getLastChangedRowCount();
- method public long getLastInsertRowId();
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getLastChangedRowCount();
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getLastInsertRowId();
method public long getMaximumSize();
method public long getPageSize();
method public String getPath();
method @Deprecated public java.util.Map<java.lang.String,java.lang.String> getSyncedTables();
- method public long getTotalChangedRowCount();
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getTotalChangedRowCount();
method public int getVersion();
method public boolean inTransaction();
method public long insert(@NonNull String, @Nullable String, @Nullable android.content.ContentValues);
@@ -14598,7 +14602,7 @@
method public int update(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues, @Nullable String, @Nullable String[]);
}
- public final class SQLiteRawStatement implements java.io.Closeable {
+ @FlaggedApi("android.database.sqlite.sqlite_apis_35") public final class SQLiteRawStatement implements java.io.Closeable {
method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException;
method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException;
method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException;
@@ -15671,7 +15675,7 @@
public final class Gainmap implements android.os.Parcelable {
ctor public Gainmap(@NonNull android.graphics.Bitmap);
- ctor public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap);
+ ctor @FlaggedApi("com.android.graphics.hwui.flags.gainmap_constructor_with_metadata") public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap);
method public int describeContents();
method @NonNull public float getDisplayRatioForFullHdr();
method @NonNull public float[] getEpsilonHdr();
@@ -16309,7 +16313,7 @@
method public void arcTo(float, float, float, float, float, float, boolean);
method public void close();
method @Deprecated public void computeBounds(@NonNull android.graphics.RectF, boolean);
- method public void computeBounds(@NonNull android.graphics.RectF);
+ method @FlaggedApi("com.android.graphics.flags.exact_compute_bounds") public void computeBounds(@NonNull android.graphics.RectF);
method public void conicTo(float, float, float, float, float);
method public void cubicTo(float, float, float, float, float, float);
method @NonNull public android.graphics.Path.FillType getFillType();
@@ -20405,7 +20409,7 @@
method @Deprecated public boolean isPreviewEnabled();
method @Deprecated public boolean isProximityCorrectionEnabled();
method @Deprecated public boolean isShifted();
- method public void onClick(android.view.View);
+ method @Deprecated public void onClick(android.view.View);
method @Deprecated public void onDetachedFromWindow();
method @Deprecated public void onDraw(android.graphics.Canvas);
method @Deprecated protected boolean onLongPress(android.inputmethodservice.Keyboard.Key);
@@ -24232,7 +24236,7 @@
@Deprecated public class RemoteControlClient.MetadataEditor extends android.media.MediaMetadataEditor {
method @Deprecated public void apply();
- method public Object clone() throws java.lang.CloneNotSupportedException;
+ method @Deprecated public Object clone() throws java.lang.CloneNotSupportedException;
method @Deprecated public android.media.RemoteControlClient.MetadataEditor putBitmap(int, android.graphics.Bitmap) throws java.lang.IllegalArgumentException;
method @Deprecated public android.media.RemoteControlClient.MetadataEditor putLong(int, long) throws java.lang.IllegalArgumentException;
method @Deprecated public android.media.RemoteControlClient.MetadataEditor putObject(int, Object) throws java.lang.IllegalArgumentException;
@@ -25787,15 +25791,15 @@
method public abstract void onDisconnect(android.media.midi.MidiReceiver);
}
- public abstract class MidiUmpDeviceService extends android.app.Service {
+ @FlaggedApi("com.android.media.midi.flags.virtual_ump") public abstract class MidiUmpDeviceService extends android.app.Service {
ctor public MidiUmpDeviceService();
- method @Nullable public final android.media.midi.MidiDeviceInfo getDeviceInfo();
- method @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers();
- method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
- method public void onClose();
- method public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus);
- method @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers();
- field public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService";
+ method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @Nullable public final android.media.midi.MidiDeviceInfo getDeviceInfo();
+ method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers();
+ method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+ method @FlaggedApi("com.android.media.midi.flags.virtual_ump") public void onClose();
+ method @FlaggedApi("com.android.media.midi.flags.virtual_ump") public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus);
+ method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers();
+ field @FlaggedApi("com.android.media.midi.flags.virtual_ump") public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService";
}
}
@@ -27096,6 +27100,7 @@
method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
method public String getSelectedTrack(int);
method public java.util.List<android.media.tv.TvTrackInfo> getTracks(int);
+ method protected void onLayout(boolean, int, int, int, int);
method public boolean onUnhandledInputEvent(android.view.InputEvent);
method public void overrideTvAppAttributionSource(@NonNull android.content.AttributionSource);
method public void reset();
@@ -33094,6 +33099,22 @@
method public void onStateChanged(boolean);
}
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public final class PowerMonitor implements android.os.Parcelable {
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public int describeContents();
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") @NonNull public String getName();
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public int getType();
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") @NonNull public static final android.os.Parcelable.Creator<android.os.PowerMonitor> CREATOR;
+ field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int POWER_MONITOR_TYPE_CONSUMER = 0; // 0x0
+ field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int POWER_MONITOR_TYPE_MEASUREMENT = 1; // 0x1
+ }
+
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public final class PowerMonitorReadings {
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public long getConsumedEnergy(@NonNull android.os.PowerMonitor);
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public long getTimestamp(@NonNull android.os.PowerMonitor);
+ field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int ENERGY_UNAVAILABLE = -1; // 0xffffffff
+ }
+
public class Process {
ctor public Process();
method public static final long getElapsedCpuTime();
@@ -33656,6 +33677,8 @@
}
public class SystemHealthManager {
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getPowerMonitorReadings(@NonNull java.util.List<android.os.PowerMonitor>, @Nullable android.os.Handler, @NonNull java.util.function.Consumer<android.os.PowerMonitorReadings>, @NonNull java.util.function.Consumer<java.lang.RuntimeException>);
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getSupportedPowerMonitors(@Nullable android.os.Handler, @NonNull java.util.function.Consumer<java.util.List<android.os.PowerMonitor>>);
method public android.os.health.HealthStats takeMyUidSnapshot();
method public android.os.health.HealthStats takeUidSnapshot(int);
method public android.os.health.HealthStats[] takeUidSnapshots(int[]);
@@ -43531,7 +43554,6 @@
method public int getLongitude();
method public int getNetworkId();
method public int getSystemId();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR;
}
@@ -43547,7 +43569,6 @@
method @Nullable public String getMncString();
method @Nullable public String getMobileNetworkOperator();
method @Deprecated public int getPsc();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR;
}
@@ -43565,7 +43586,6 @@
method @Nullable public String getMobileNetworkOperator();
method public int getPci();
method public int getTac();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityLte> CREATOR;
}
@@ -43578,7 +43598,6 @@
method @IntRange(from=0, to=3279165) public int getNrarfcn();
method @IntRange(from=0, to=1007) public int getPci();
method @IntRange(from=0, to=16777215) public int getTac();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityNr> CREATOR;
}
@@ -43592,7 +43611,6 @@
method @Nullable public String getMncString();
method @Nullable public String getMobileNetworkOperator();
method public int getUarfcn();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityTdscdma> CREATOR;
}
@@ -43608,7 +43626,6 @@
method @Nullable public String getMobileNetworkOperator();
method public int getPsc();
method public int getUarfcn();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityWcdma> CREATOR;
}
@@ -43692,6 +43709,7 @@
public final class CellSignalStrengthCdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method public int getCdmaDbm();
method public int getCdmaEcio();
@@ -43702,24 +43720,28 @@
method public int getEvdoLevel();
method public int getEvdoSnr();
method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthCdma> CREATOR;
}
public final class CellSignalStrengthGsm extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method public int getBitErrorRate();
method public int getDbm();
method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
method public int getRssi();
method public int getTimingAdvance();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthGsm> CREATOR;
}
public final class CellSignalStrengthLte extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method @IntRange(from=0, to=15) public int getCqi();
method @IntRange(from=1, to=6) public int getCqiTableIndex();
@@ -43730,12 +43752,14 @@
method public int getRssi();
method public int getRssnr();
method public int getTimingAdvance();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthLte> CREATOR;
}
public final class CellSignalStrengthNr extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method @IntRange(from=0, to=15) @NonNull public java.util.List<java.lang.Integer> getCsiCqiReport();
method @IntRange(from=1, to=3) public int getCsiCqiTableIndex();
@@ -43748,26 +43772,31 @@
method public int getSsRsrq();
method public int getSsSinr();
method @IntRange(from=0, to=1282) public int getTimingAdvanceMicros();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthNr> CREATOR;
}
public final class CellSignalStrengthTdscdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method public int getDbm();
method @IntRange(from=0, to=4) public int getLevel();
method public int getRscp();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthTdscdma> CREATOR;
}
public final class CellSignalStrengthWcdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method public int getDbm();
method public int getEcNo();
method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthWcdma> CREATOR;
}
@@ -46555,6 +46584,7 @@
method @Deprecated public int length();
method @Deprecated public static android.text.AlteredCharSequence make(CharSequence, char[], int, int);
method @Deprecated public CharSequence subSequence(int, int);
+ method @Deprecated public String toString();
}
@Deprecated public class AndroidCharacter {
@@ -47006,6 +47036,7 @@
method public void removeSpan(Object);
method public void setSpan(Object, int, int, int);
method public CharSequence subSequence(int, int);
+ method public String toString();
}
public static final class PrecomputedText.Params {
@@ -47135,6 +47166,7 @@
method public void setFilters(android.text.InputFilter[]);
method public void setSpan(Object, int, int, int);
method public CharSequence subSequence(int, int);
+ method public String toString();
method public static android.text.SpannableStringBuilder valueOf(CharSequence);
}
@@ -48775,7 +48807,9 @@
method public boolean containsValue(Object);
method public void ensureCapacity(int);
method public java.util.Set<java.util.Map.Entry<K,V>> entrySet();
+ method public boolean equals(@Nullable Object);
method public V get(Object);
+ method public int hashCode();
method public int indexOfKey(Object);
method public int indexOfValue(Object);
method public boolean isEmpty();
@@ -48807,7 +48841,9 @@
method public boolean contains(Object);
method public boolean containsAll(java.util.Collection<?>);
method public void ensureCapacity(int);
+ method public boolean equals(@Nullable Object);
method public void forEach(java.util.function.Consumer<? super E>);
+ method public int hashCode();
method public int indexOf(Object);
method public boolean isEmpty();
method public java.util.Iterator<E> iterator();
@@ -57546,6 +57582,7 @@
ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet);
ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet, int);
ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet, int, int);
+ method @Deprecated protected void onLayout(boolean, int, int, int, int);
}
@Deprecated public static class AbsoluteLayout.LayoutParams extends android.view.ViewGroup.LayoutParams {
@@ -57624,6 +57661,7 @@
method public long getSelectedItemId();
method public int getSelectedItemPosition();
method public abstract android.view.View getSelectedView();
+ method protected void onLayout(boolean, int, int, int, int);
method public boolean performItemClick(android.view.View, int, long);
method public abstract void setAdapter(T);
method public void setEmptyView(android.view.View);
@@ -58270,6 +58308,7 @@
method public android.widget.FrameLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
method @Deprecated public boolean getConsiderGoneChildrenWhenMeasuring();
method public boolean getMeasureAllChildren();
+ method protected void onLayout(boolean, int, int, int, int);
method public void setMeasureAllChildren(boolean);
}
@@ -58323,6 +58362,7 @@
method public boolean getUseDefaultMargins();
method public boolean isColumnOrderPreserved();
method public boolean isRowOrderPreserved();
+ method protected void onLayout(boolean, int, int, int, int);
method public void setAlignmentMode(int);
method public void setColumnCount(int);
method public void setColumnOrderPreserved(boolean);
@@ -58545,6 +58585,7 @@
method public float getWeightSum();
method public boolean isBaselineAligned();
method public boolean isMeasureWithLargestChildEnabled();
+ method protected void onLayout(boolean, int, int, int, int);
method public void setBaselineAligned(boolean);
method public void setBaselineAlignedChildIndex(int);
method public void setDividerDrawable(android.graphics.drawable.Drawable);
@@ -59093,6 +59134,7 @@
method public android.widget.RelativeLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
method public int getGravity();
method public int getIgnoreGravity();
+ method protected void onLayout(boolean, int, int, int, int);
method public void setGravity(int);
method public void setHorizontalGravity(int);
method public void setIgnoreGravity(int);
@@ -59547,6 +59589,7 @@
method @Deprecated public boolean isMoving();
method @Deprecated public boolean isOpened();
method @Deprecated public void lock();
+ method @Deprecated protected void onLayout(boolean, int, int, int, int);
method @Deprecated public void open();
method @Deprecated public void setOnDrawerCloseListener(android.widget.SlidingDrawer.OnDrawerCloseListener);
method @Deprecated public void setOnDrawerOpenListener(android.widget.SlidingDrawer.OnDrawerOpenListener);
@@ -60193,6 +60236,7 @@
method public boolean hideOverflowMenu();
method public void inflateMenu(@MenuRes int);
method public boolean isOverflowMenuShowing();
+ method protected void onLayout(boolean, int, int, int, int);
method public void setCollapseContentDescription(@StringRes int);
method public void setCollapseContentDescription(@Nullable CharSequence);
method public void setCollapseIcon(@DrawableRes int);
@@ -60347,7 +60391,7 @@
method @Deprecated public android.view.View getZoomControls();
method @Deprecated public boolean isAutoDismissed();
method @Deprecated public boolean isVisible();
- method public boolean onTouch(android.view.View, android.view.MotionEvent);
+ method @Deprecated public boolean onTouch(android.view.View, android.view.MotionEvent);
method @Deprecated public void setAutoDismissed(boolean);
method @Deprecated public void setFocusable(boolean);
method @Deprecated public void setOnZoomListener(android.widget.ZoomButtonsController.OnZoomListener);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 500a12c..2af3c34 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android {
public static final class Manifest.permission {
@@ -8,7 +6,7 @@
field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS";
field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT";
field public static final String MAKE_UID_VISIBLE = "android.permission.MAKE_UID_VISIBLE";
- field public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS";
+ field @FlaggedApi("android.companion.flags.companion_transport_apis") public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS";
}
}
@@ -87,21 +85,21 @@
package android.companion {
public final class CompanionDeviceManager {
- method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnMessageReceivedListener(@NonNull java.util.concurrent.Executor, int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
- method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnTransportsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
- method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnMessageReceivedListener(int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
- method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnTransportsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
- method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void sendMessage(int, @NonNull byte[], @NonNull int[]);
- field public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 1667729539; // 0x63678883
- field public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 1669491075; // 0x63826983
- field public static final int MESSAGE_REQUEST_REMOTE_AUTHENTICATION = 1669494629; // 0x63827765
+ method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnMessageReceivedListener(@NonNull java.util.concurrent.Executor, int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
+ method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnTransportsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
+ method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnMessageReceivedListener(int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
+ method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnTransportsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
+ method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void sendMessage(int, @NonNull byte[], @NonNull int[]);
+ field @FlaggedApi("android.companion.companion_transport_apis") public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 1667729539; // 0x63678883
+ field @FlaggedApi("android.companion.companion_transport_apis") public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 1669491075; // 0x63826983
+ field @FlaggedApi("android.companion.companion_transport_apis") public static final int MESSAGE_REQUEST_REMOTE_AUTHENTICATION = 1669494629; // 0x63827765
}
- public static interface CompanionDeviceManager.OnMessageReceivedListener {
+ @FlaggedApi("android.companion.companion_transport_apis") public static interface CompanionDeviceManager.OnMessageReceivedListener {
method public void onMessageReceived(int, @NonNull byte[]);
}
- public static interface CompanionDeviceManager.OnTransportsChangedListener {
+ @FlaggedApi("android.companion.companion_transport_apis") public static interface CompanionDeviceManager.OnTransportsChangedListener {
method public void onTransportsChanged(@NonNull java.util.List<android.companion.AssociationInfo>);
}
diff --git a/core/api/module-lib-removed.txt b/core/api/module-lib-removed.txt
index 14191eb..d802177 100644
--- a/core/api/module-lib-removed.txt
+++ b/core/api/module-lib-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/core/api/removed.txt b/core/api/removed.txt
index e2b4e4d..5a4be65 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.app {
public class Notification implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7dcc7b2..70f5e2f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android {
public static final class Manifest.permission {
@@ -299,6 +297,7 @@
field public static final String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE";
field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY";
field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST";
+ field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA = "android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";
field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOX_TRIGGER_AUDIO = "android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO";
field public static final String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE";
field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
@@ -313,7 +312,7 @@
field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS";
- field public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS";
+ field @FlaggedApi("android.app.usage.report_usage_stats_permission") public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS";
field @Deprecated public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
@@ -3556,7 +3555,7 @@
field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
- field public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
+ field @FlaggedApi("android.content.pm.archiving") public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";
field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
@@ -4029,7 +4028,7 @@
field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
field public static final int MATCH_ANY_USER = 4194304; // 0x400000
- field public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L
+ field @FlaggedApi("android.content.pm.archiving") public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L
field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000
field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000
@@ -4060,7 +4059,9 @@
}
public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable {
+ method public int describeContents();
method public void onUninstallComplete(@NonNull String, int, @Nullable String);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.UninstallCompleteCallback> CREATOR;
}
@@ -5960,6 +5961,7 @@
public static final class SoundTrigger.Keyphrase implements android.os.Parcelable {
ctor public SoundTrigger.Keyphrase(int, int, @NonNull java.util.Locale, @NonNull String, @Nullable int[]);
+ method public int describeContents();
method public int getId();
method @NonNull public java.util.Locale getLocale();
method public int getRecognitionModes();
@@ -5982,6 +5984,7 @@
public static final class SoundTrigger.KeyphraseSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable {
ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int);
ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[]);
+ method public int describeContents();
method @NonNull public android.hardware.soundtrigger.SoundTrigger.Keyphrase[] getKeyphrases();
method @NonNull public static android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel readFromParcel(@NonNull android.os.Parcel);
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -5989,6 +5992,7 @@
}
public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable {
+ method public int describeContents();
method public int getEnd();
method public int getStart();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -6716,7 +6720,7 @@
method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
method public boolean setUserIdDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
method public String toLogFriendlyString();
- method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules(@NonNull java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>);
+ method @FlaggedApi("com.android.media.audio.flags.audio_policy_update_mixing_rules_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules(@NonNull java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>);
field public static final int FOCUS_POLICY_DUCKING_DEFAULT = 0; // 0x0
field public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; // 0x0
field public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1; // 0x1
@@ -6799,6 +6803,7 @@
public abstract class MusicRecognitionService extends android.app.Service {
ctor public MusicRecognitionService();
+ method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onRecognize(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @NonNull android.media.musicrecognition.MusicRecognitionService.Callback);
}
@@ -6857,6 +6862,7 @@
public abstract class SoundTriggerDetectionService extends android.app.Service {
ctor public SoundTriggerDetectionService();
+ method public final android.os.IBinder onBind(android.content.Intent);
method @MainThread public void onConnected(@NonNull java.util.UUID, @Nullable android.os.Bundle);
method @MainThread public void onDisconnected(@NonNull java.util.UUID, @Nullable android.os.Bundle);
method @MainThread public void onError(@NonNull java.util.UUID, @Nullable android.os.Bundle, int, int);
@@ -7570,6 +7576,7 @@
}
public class MediaEvent extends android.media.tv.tuner.filter.FilterEvent {
+ method protected void finalize();
method public long getAudioHandle();
method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
method public long getAvDataId();
@@ -8964,6 +8971,8 @@
package android.net.metrics {
@Deprecated public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
}
@Deprecated public static final class ApfProgramEvent.Builder {
@@ -8978,6 +8987,8 @@
}
@Deprecated public final class ApfStats implements android.net.metrics.IpConnectivityLog.Event {
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
}
@Deprecated public static final class ApfStats.Builder {
@@ -8996,6 +9007,8 @@
}
@Deprecated public final class DhcpClientEvent implements android.net.metrics.IpConnectivityLog.Event {
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
}
@Deprecated public static final class DhcpClientEvent.Builder {
@@ -9007,7 +9020,9 @@
@Deprecated public final class DhcpErrorEvent implements android.net.metrics.IpConnectivityLog.Event {
ctor @Deprecated public DhcpErrorEvent(int);
+ method @Deprecated public int describeContents();
method @Deprecated public static int errorCodeWithOption(int, int);
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
field @Deprecated public static final int BOOTP_TOO_SHORT = 67174400; // 0x4010000
field @Deprecated public static final int BUFFER_UNDERFLOW = 83951616; // 0x5010000
field @Deprecated public static final int DHCP_BAD_MAGIC_COOKIE = 67239936; // 0x4020000
@@ -9045,6 +9060,8 @@
@Deprecated public final class IpManagerEvent implements android.net.metrics.IpConnectivityLog.Event {
ctor @Deprecated public IpManagerEvent(int, long);
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
field @Deprecated public static final int COMPLETE_LIFECYCLE = 3; // 0x3
field @Deprecated public static final int ERROR_INTERFACE_NOT_FOUND = 8; // 0x8
field @Deprecated public static final int ERROR_INVALID_PROVISIONING = 7; // 0x7
@@ -9057,6 +9074,8 @@
@Deprecated public final class IpReachabilityEvent implements android.net.metrics.IpConnectivityLog.Event {
ctor @Deprecated public IpReachabilityEvent(int);
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
field @Deprecated public static final int NUD_FAILED = 512; // 0x200
field @Deprecated public static final int NUD_FAILED_ORGANIC = 1024; // 0x400
field @Deprecated public static final int PROBE = 256; // 0x100
@@ -9067,6 +9086,8 @@
@Deprecated public final class NetworkEvent implements android.net.metrics.IpConnectivityLog.Event {
ctor @Deprecated public NetworkEvent(int, long);
ctor @Deprecated public NetworkEvent(int);
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
field @Deprecated public static final int NETWORK_CAPTIVE_PORTAL_FOUND = 4; // 0x4
field @Deprecated public static final int NETWORK_CONNECTED = 1; // 0x1
field @Deprecated public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; // 0xc
@@ -9083,6 +9104,8 @@
}
@Deprecated public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event {
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
}
@Deprecated public static final class RaEvent.Builder {
@@ -9097,7 +9120,9 @@
}
@Deprecated public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
+ method @Deprecated public int describeContents();
method @Deprecated @NonNull public static String getProbeName(int);
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
field @Deprecated public static final int DNS_FAILURE = 0; // 0x0
field @Deprecated public static final int DNS_SUCCESS = 1; // 0x1
field @Deprecated public static final int PROBE_DNS = 0; // 0x0
@@ -9513,7 +9538,7 @@
method public int getDeviceType();
method @NonNull public android.os.Bundle getExtras();
method @NonNull public String getModelName();
- method public boolean isBatteryCharging();
+ method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR;
field public static final int DEVICE_TYPE_AUTO = 5; // 0x5
@@ -9527,7 +9552,7 @@
public static final class NetworkProviderInfo.Builder {
ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build();
- method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
+ method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
@@ -11502,6 +11527,7 @@
public abstract class FieldClassificationService extends android.app.Service {
ctor public FieldClassificationService();
+ method public final android.os.IBinder onBind(android.content.Intent);
method public abstract void onClassificationRequest(@NonNull android.service.assist.classification.FieldClassificationRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.assist.classification.FieldClassificationResponse,java.lang.Exception>);
method public void onConnected();
method public void onDisconnected();
@@ -11580,6 +11606,7 @@
method protected final void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]);
method protected void dump(@NonNull java.io.PrintWriter, @NonNull String[]);
method @Nullable public final android.service.autofill.FillEventHistory getFillEventHistory();
+ method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onDisconnected();
method public void onFillRequest(@NonNull android.service.autofill.augmented.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.augmented.FillController, @NonNull android.service.autofill.augmented.FillCallback);
@@ -11618,6 +11645,7 @@
public final class FillWindow implements java.lang.AutoCloseable {
ctor public FillWindow();
+ method public void close();
method public void destroy();
method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long);
}
@@ -11643,6 +11671,7 @@
public final class CarrierMessagingServiceWrapper implements java.lang.AutoCloseable {
ctor public CarrierMessagingServiceWrapper();
method public boolean bindToCarrierMessagingService(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull Runnable);
+ method public void close();
method public void disconnect();
method public void downloadMms(@NonNull android.net.Uri, int, @NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback);
method public void receiveSms(@NonNull android.service.carrier.MessagePdu, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback);
@@ -11693,6 +11722,7 @@
method public final void disableSelf();
method public void onActivityEvent(@NonNull android.service.contentcapture.ActivityEvent);
method public void onActivitySnapshot(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.service.contentcapture.SnapshotData);
+ method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onContentCaptureEvent(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.view.contentcapture.ContentCaptureEvent);
method public void onCreateContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext, @NonNull android.view.contentcapture.ContentCaptureSessionId);
@@ -11731,6 +11761,7 @@
public abstract class ContentSuggestionsService extends android.app.Service {
ctor public ContentSuggestionsService();
+ method public final android.os.IBinder onBind(android.content.Intent);
method public abstract void onClassifyContentSelections(@NonNull android.app.contentsuggestions.ClassificationsRequest, @NonNull android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback);
method public abstract void onNotifyInteraction(@NonNull String, @NonNull android.os.Bundle);
method public abstract void onProcessContextImage(int, @Nullable android.graphics.Bitmap, @NonNull android.os.Bundle);
@@ -11744,6 +11775,7 @@
public abstract class DataLoaderService extends android.app.Service {
ctor public DataLoaderService();
+ method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(@NonNull android.content.pm.DataLoaderParams);
}
@@ -12416,6 +12448,7 @@
public class TraceReportService extends android.app.Service {
ctor public TraceReportService();
+ method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public void onReportTrace(@NonNull android.service.tracing.TraceReportService.TraceParams);
}
@@ -13041,6 +13074,7 @@
method @Nullable public android.content.ComponentName getCallScreeningComponent();
method public boolean isBlocked();
method public boolean isInContacts();
+ method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telecom.Connection.CallFilteringCompletionInfo> CREATOR;
}
@@ -13350,6 +13384,7 @@
method public int getReason();
method public int getTimeoutSeconds();
method public boolean isEnabled();
+ method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR;
field public static final int REASON_ALL = 4; // 0x4
field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5
@@ -13634,6 +13669,7 @@
method public int getDownlinkCapacityKbps();
method public int getType();
method public int getUplinkCapacityKbps();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.LinkCapacityEstimate> CREATOR;
field public static final int INVALID = -1; // 0xffffffff
field public static final int LCE_TYPE_COMBINED = 2; // 0x2
@@ -13643,8 +13679,10 @@
public final class LteVopsSupportInfo extends android.telephony.VopsSupportInfo {
ctor public LteVopsSupportInfo(int, int);
+ method public boolean equals(@Nullable Object);
method public int getEmcBearerSupport();
method public int getVopsSupport();
+ method public int hashCode();
method public boolean isEmergencyServiceFallbackSupported();
method public boolean isEmergencyServiceSupported();
method public boolean isVopsSupported();
@@ -13745,9 +13783,11 @@
public final class NrVopsSupportInfo extends android.telephony.VopsSupportInfo {
ctor public NrVopsSupportInfo(int, int, int);
+ method public boolean equals(@Nullable Object);
method public int getEmcSupport();
method public int getEmfSupport();
method public int getVopsSupport();
+ method public int hashCode();
method public boolean isEmergencyServiceFallbackSupported();
method public boolean isEmergencyServiceSupported();
method public boolean isVopsSupported();
@@ -14860,6 +14900,7 @@
public abstract class QualifiedNetworksService extends android.app.Service {
ctor public QualifiedNetworksService();
+ method public android.os.IBinder onBind(android.content.Intent);
method @NonNull public abstract android.telephony.data.QualifiedNetworksService.NetworkAvailabilityProvider onCreateNetworkAvailabilityProvider(int);
field public static final String QUALIFIED_NETWORKS_SERVICE_INTERFACE = "android.telephony.data.QualifiedNetworksService";
}
@@ -15051,6 +15092,7 @@
public class GbaService extends android.app.Service {
ctor public GbaService();
method public void onAuthenticationRequest(int, int, int, @NonNull android.net.Uri, @NonNull byte[], boolean);
+ method public android.os.IBinder onBind(android.content.Intent);
method public final void reportAuthenticationFailure(int, int) throws java.lang.RuntimeException;
method public final void reportKeysAvailable(int, @NonNull byte[], @NonNull String) throws java.lang.RuntimeException;
field public static final String SERVICE_INTERFACE = "android.telephony.gba.GbaService";
@@ -15553,6 +15595,7 @@
method @Deprecated public android.telephony.ims.stub.ImsRegistrationImplBase getRegistration(int);
method @NonNull public android.telephony.ims.stub.ImsRegistrationImplBase getRegistrationForSubscription(int, int);
method @Nullable public android.telephony.ims.stub.SipTransportImplBase getSipTransport(int);
+ method public android.os.IBinder onBind(android.content.Intent);
method public final void onUpdateSupportedImsFeatures(android.telephony.ims.stub.ImsFeatureConfiguration) throws android.os.RemoteException;
method public android.telephony.ims.stub.ImsFeatureConfiguration querySupportedImsFeatures();
method public void readyForFeatureCreation();
@@ -16698,6 +16741,23 @@
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR;
}
+ @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class NtnSignalStrength implements android.os.Parcelable {
+ ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public NtnSignalStrength(@Nullable android.telephony.satellite.NtnSignalStrength);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getLevel();
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.NtnSignalStrength> CREATOR;
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_GOOD = 3; // 0x3
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_GREAT = 4; // 0x4
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_MODERATE = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_NONE = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_POOR = 1; // 0x1
+ }
+
+ @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface NtnSignalStrengthCallback {
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrength);
+ }
+
@FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class PointingInfo implements android.os.Parcelable {
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteAzimuthDegrees();
@@ -16733,6 +16793,7 @@
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
@@ -16743,6 +16804,7 @@
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
@@ -16751,6 +16813,7 @@
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startSatelliteTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopSatelliteTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 4fb7b6b..a501031 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -251,10 +251,6 @@
New API must be flagged with @FlaggedApi: method android.media.audiopolicy.AudioMixingRule.writeToParcel(android.os.Parcel,int)
UnflaggedApi: android.media.audiopolicy.AudioPolicy#updateMixingRules(java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>):
New API must be flagged with @FlaggedApi: method android.media.audiopolicy.AudioPolicy.updateMixingRules(java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>)
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.NetworkProviderInfo#isBatteryCharging():
- New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.isBatteryCharging()
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder#setBatteryCharging(boolean):
- New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder.setBatteryCharging(boolean)
UnflaggedApi: android.nfc.cardemulation.AidGroup#CONTENTS_FILE_DESCRIPTOR:
New API must be flagged with @FlaggedApi: field android.nfc.cardemulation.AidGroup.CONTENTS_FILE_DESCRIPTOR
UnflaggedApi: android.nfc.cardemulation.AidGroup#PARCELABLE_WRITE_RETURN_VALUE:
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index 1fa2718..aa17df3 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.app {
public class AppOpsManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index eeddeb2..03a58be 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android {
public static final class Manifest.permission {
@@ -543,7 +541,7 @@
field public static final String PERMITTED_INPUT_METHODS_POLICY = "permittedInputMethods";
field public static final String PERSONAL_APPS_SUSPENDED_POLICY = "personalAppsSuspended";
field public static final String SCREEN_CAPTURE_DISABLED_POLICY = "screenCaptureDisabled";
- field public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
+ field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
}
public class DevicePolicyManager {
@@ -853,13 +851,13 @@
method @NonNull public android.companion.AssociationInfo.Builder setRevoked(boolean);
method @NonNull public android.companion.AssociationInfo.Builder setSelfManaged(boolean);
method @NonNull public android.companion.AssociationInfo.Builder setSystemDataSyncFlags(int);
- method @NonNull public android.companion.AssociationInfo.Builder setTag(@Nullable String);
+ method @FlaggedApi("android.companion.association_tag") @NonNull public android.companion.AssociationInfo.Builder setTag(@Nullable String);
method @NonNull public android.companion.AssociationInfo.Builder setTimeApproved(long);
}
public final class CompanionDeviceManager {
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void enableSecureTransport(boolean);
- field public static final int MESSAGE_REQUEST_PING = 1669362552; // 0x63807378
+ field @FlaggedApi("android.companion.companion_transport_apis") public static final int MESSAGE_REQUEST_PING = 1669362552; // 0x63807378
}
public abstract class CompanionDeviceService extends android.app.Service {
@@ -2119,7 +2117,7 @@
public class SharedConnectivityManager {
method @Nullable public static android.net.wifi.sharedconnectivity.app.SharedConnectivityManager create(@NonNull android.content.Context, @NonNull String, @NonNull String);
- method @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
+ method @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api") @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
method @Nullable public android.content.ServiceConnection getServiceConnection();
method public void setService(@Nullable android.os.IInterface);
}
@@ -2150,7 +2148,7 @@
}
public final class BugreportParams {
- field public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7
+ field @FlaggedApi("android.os.bugreport_mode_max_value") public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7
}
public class Build {
@@ -3187,7 +3185,6 @@
field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
field public static final int HAL_SERVICE_MODEM = 3; // 0x3
field public static final int HAL_SERVICE_NETWORK = 4; // 0x4
- field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int HAL_SERVICE_SATELLITE = 8; // 0x8
field public static final int HAL_SERVICE_SIM = 5; // 0x5
field public static final int HAL_SERVICE_VOICE = 6; // 0x6
field public static final android.util.Pair HAL_VERSION_UNKNOWN;
@@ -3587,8 +3584,8 @@
field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
field public static final int FLAG_SLIPPERY = 536870912; // 0x20000000
field public CharSequence accessibilityTitle;
- field public float preferredMaxDisplayRefreshRate;
- field public float preferredMinDisplayRefreshRate;
+ field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMaxDisplayRefreshRate;
+ field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMinDisplayRefreshRate;
field public int privateFlags;
}
@@ -3618,7 +3615,6 @@
public final class AccessibilityWindowInfo implements android.os.Parcelable {
method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
- field public static final int UNDEFINED_WINDOW_ID = -1; // 0xffffffff
}
}
@@ -3801,14 +3797,14 @@
public final class InputMethodManager {
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void addVirtualStylusIdForTestSession();
method public int getDisplayId();
- method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle);
- method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
+ method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle);
+ method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
method public boolean hasActiveInputConnection(@Nullable android.view.View);
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean hasPendingImeVisibilityRequests();
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isCurrentRootView(@NonNull android.view.View);
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown();
- method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle);
+ method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long);
field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L
}
@@ -3946,7 +3942,9 @@
package android.window {
public final class BackNavigationInfo implements android.os.Parcelable {
+ method public int describeContents();
method @NonNull public static String typeToString(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.window.BackNavigationInfo> CREATOR;
field public static final String KEY_TRIGGER_BACK = "TriggerBack";
field public static final int TYPE_CALLBACK = 4; // 0x4
@@ -4007,11 +4005,13 @@
}
public final class TaskFragmentCreationParams implements android.os.Parcelable {
+ method public int describeContents();
method @NonNull public android.os.IBinder getFragmentToken();
method @NonNull public android.graphics.Rect getInitialRelativeBounds();
method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizer();
method @NonNull public android.os.IBinder getOwnerToken();
method public int getWindowingMode();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentCreationParams> CREATOR;
}
@@ -4023,6 +4023,7 @@
}
public final class TaskFragmentInfo implements android.os.Parcelable {
+ method public int describeContents();
method public boolean equalsForTaskFragmentOrganizer(@Nullable android.window.TaskFragmentInfo);
method @NonNull public java.util.List<android.os.IBinder> getActivities();
method @NonNull public java.util.List<android.os.IBinder> getActivitiesRequestedInTaskFragment();
@@ -4036,6 +4037,7 @@
method public boolean isEmpty();
method public boolean isTaskClearedForReuse();
method public boolean isVisible();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentInfo> CREATOR;
}
@@ -4058,6 +4060,8 @@
}
public final class TaskFragmentOrganizerToken implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR;
}
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 93e39d5..105e764 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -257,8 +257,6 @@
New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.loadSoundModel(android.hardware.soundtrigger.SoundTrigger.SoundModel)
UnflaggedApi: android.media.soundtrigger.SoundTriggerManager.Model#getSoundModel():
New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.Model.getSoundModel()
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.SharedConnectivityManager#getBroadcastReceiver():
- New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.SharedConnectivityManager.getBroadcastReceiver()
UnflaggedApi: android.os.BatteryManager#BATTERY_PLUGGED_ANY:
New API must be flagged with @FlaggedApi: field android.os.BatteryManager.BATTERY_PLUGGED_ANY
UnflaggedApi: android.os.BugreportParams#BUGREPORT_MODE_MAX_VALUE:
diff --git a/core/api/test-removed.txt b/core/api/test-removed.txt
index 14191eb..d802177 100644
--- a/core/api/test-removed.txt
+++ b/core/api/test-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 3370c12..1000612 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -23,6 +23,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.CheckResult;
import android.annotation.ColorInt;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -793,6 +794,7 @@
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
@IntDef(
prefix = {"OVERLAY_RESULT_"},
value = {
@@ -803,6 +805,7 @@
public @interface AttachOverlayResult {}
/** Result code indicating the overlay was successfully attached. */
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
public static final int OVERLAY_RESULT_SUCCESS = 0;
/**
@@ -810,6 +813,7 @@
* error and not
* because of problems with the input.
*/
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1;
/**
@@ -817,6 +821,7 @@
* specified display or
* window id was invalid.
*/
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
public static final int OVERLAY_RESULT_INVALID = 2;
private int mConnectionId = AccessibilityInteractionClient.NO_ID;
@@ -3506,11 +3511,7 @@
* @param displayId the display to which the SurfaceControl should be attached.
* @param sc the SurfaceControl containing the overlay content
*
- * @deprecated Use
- * {@link #attachAccessibilityOverlayToDisplay(int, SurfaceControl, Executor, IntConsumer)}
- * instead.
*/
- @Deprecated
public void attachAccessibilityOverlayToDisplay(int displayId, @NonNull SurfaceControl sc) {
Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
AccessibilityInteractionClient.getInstance(this)
@@ -3547,6 +3548,7 @@
* @see #OVERLAY_RESULT_INVALID
* @see #OVERLAY_RESULT_INTERNAL_ERROR
*/
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
public void attachAccessibilityOverlayToDisplay(
int displayId,
@NonNull SurfaceControl sc,
@@ -3581,11 +3583,7 @@
* @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
* @param sc the SurfaceControl containing the overlay content
*
- * @deprecated Use
- * {@link #attachAccessibilityOverlayToWindow(int, SurfaceControl, Executor,IntConsumer)}
- * instead.
*/
- @Deprecated
public void attachAccessibilityOverlayToWindow(
int accessibilityWindowId, @NonNull SurfaceControl sc) {
Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
@@ -3623,6 +3621,7 @@
* @see #OVERLAY_RESULT_INVALID
* @see #OVERLAY_RESULT_INTERNAL_ERROR
*/
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
public void attachAccessibilityOverlayToWindow(
int accessibilityWindowId,
@NonNull SurfaceControl sc,
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 895dde7..f2c0051 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -2073,9 +2073,7 @@
* Allow a {@link PendingIntent} to use the privilege of its creator to start background
* activities.
*
- * @param mode the {@link android.app.ComponentOptions.BackgroundActivityStartMode} being set
- * @throws IllegalArgumentException is the value is not a valid
- * {@link android.app.ComponentOptions.BackgroundActivityStartMode}
+ * @param mode the mode being set
*/
@NonNull
public ActivityOptions setPendingIntentCreatorBackgroundActivityStartMode(
@@ -2088,7 +2086,7 @@
* Returns the mode to start background activities granted by the creator of the
* {@link PendingIntent}.
*
- * @return the {@link android.app.ComponentOptions.BackgroundActivityStartMode} currently set
+ * @return the mode currently set
*/
public @BackgroundActivityStartMode int getPendingIntentCreatorBackgroundActivityStartMode() {
return mPendingIntentCreatorBackgroundActivityStartMode;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 17637df..ecbc9b1 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1487,13 +1487,13 @@
AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
/**
- * Allows the assistant app to get the training data from the trusted process to improve the
- * hotword training model.
+ * Allows the privileged assistant app to receive the training data from the sandboxed hotword
+ * detection service.
*
* @hide
*/
- public static final int OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA =
- AppProtoEnums.APP_OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA;
+ public static final int OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
+ AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1641,7 +1641,7 @@
OPSTR_CAMERA_SANDBOXED,
OPSTR_RECORD_AUDIO_SANDBOXED,
OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
- OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA
+ OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA
})
public @interface AppOpString {}
@@ -2262,13 +2262,13 @@
"android:receive_sandbox_trigger_audio";
/**
- * Allows the assistant app to get the training data from the trusted process to improve
- * the hotword training model.
+ * Allows the privileged assistant app to receive training data from the sandboxed hotword
+ * detection service.
*
* @hide
*/
- public static final String OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA =
- "android:receive_trusted_process_training_data";
+ public static final String OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
+ "android:RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
@@ -2381,7 +2381,8 @@
OP_FOREGROUND_SERVICE_SPECIAL_USE,
OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
OP_USE_FULL_SCREEN_INTENT,
- OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
+ OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+ OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2814,9 +2815,11 @@
"RECEIVE_SANDBOX_TRIGGER_AUDIO")
.setPermission(Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO)
.setDefaultMode(AppOpsManager.MODE_DEFAULT).build(),
- new AppOpInfo.Builder(OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA,
- OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA,
- "RECEIVE_TRUSTED_PROCESS_TRAINING_DATA").build()
+ new AppOpInfo.Builder(OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
+ OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
+ "RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA")
+ .setPermission(Manifest.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA)
+ .setDefaultMode(AppOpsManager.MODE_DEFAULT).build()
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 9549ebf..41b4004 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -843,8 +843,7 @@
* considered to be in the same delivery group as this iff it has the same {@code namespace}
* and {@code key}.
*
- * <p> If neither matching key using this API nor matching filter using
- * {@link #setDeliveryGroupMatchingFilter(IntentFilter)} is specified, then by default
+ * <p> If not matching key using this API then by default
* {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
*/
@NonNull
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index a538247..08c18c8 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3483,12 +3483,10 @@
// only do this if the user already has more than one preferred locale
if (r.getConfiguration().getLocales().size() > 1) {
- LocaleConfig lc = LocaleConfig.fromContextIgnoringOverride(this);
- mResourcesManager.setLocaleList(lc != null
- && lc.getSupportedLocales() != null
- && !lc.getSupportedLocales().isEmpty()
- ? lc.getSupportedLocales()
- : null);
+ LocaleConfig lc = getUserId() < 0
+ ? LocaleConfig.fromContextIgnoringOverride(this)
+ : new LocaleConfig(this);
+ mResourcesManager.setLocaleConfig(lc);
}
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 8fea03b..ebf183f 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -50,7 +50,6 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.security.net.config.NetworkSecurityConfigProvider;
-import android.sysprop.VndkProperties;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
@@ -901,14 +900,10 @@
}
// Similar to vendor apks, we should add /product/lib for apks from product partition
- // when product apps are marked as unbundled. We cannot use the same way from vendor
- // to check if lib path exists because there is possibility that /product/lib would not
- // exist from legacy device while product apks are bundled. To make this clear, we use
- // "ro.product.vndk.version" property. If the property is defined, we regard all product
- // apks as unbundled.
+ // when product apps are marked as unbundled. Product is separated as long as the
+ // partition exists, so it can be handled with same approach from the vendor partition.
if (mApplicationInfo.getCodePath() != null
- && mApplicationInfo.isProduct()
- && VndkProperties.product_vndk_version().isPresent()) {
+ && mApplicationInfo.isProduct()) {
isBundledApp = false;
}
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 0857c96..369a781 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -16,11 +16,12 @@
package android.app;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -31,6 +32,7 @@
import android.util.Slog;
import android.util.Xml;
+import com.android.internal.R;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParserException;
@@ -40,7 +42,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -67,6 +69,8 @@
public static final String TAG_LOCALE_CONFIG = "locale-config";
public static final String TAG_LOCALE = "locale";
private LocaleList mLocales;
+
+ private Locale mDefaultLocale;
private int mStatus = STATUS_NOT_SPECIFIED;
/**
@@ -133,15 +137,14 @@
return;
}
}
- int resId = 0;
Resources res = context.getResources();
+ //Get the resource id
+ int resId = context.getApplicationInfo().getLocaleConfigRes();
+ if (resId == 0) {
+ mStatus = STATUS_NOT_SPECIFIED;
+ return;
+ }
try {
- //Get the resource id
- resId = new ApplicationInfo(context.getApplicationInfo()).getLocaleConfigRes();
- if (resId == 0) {
- mStatus = STATUS_NOT_SPECIFIED;
- return;
- }
//Get the parser to read XML data
XmlResourceParser parser = res.getXml(resId);
parseLocaleConfig(parser, res);
@@ -195,8 +198,17 @@
XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG);
int outerDepth = parser.getDepth();
AttributeSet attrs = Xml.asAttributeSet(parser);
- // LinkedHashSet to preserve insertion order
- Set<String> localeNames = new LinkedHashSet<>();
+
+ String defaultLocale = null;
+ if (android.content.res.Flags.defaultLocale()) {
+ TypedArray att = res.obtainAttributes(
+ attrs, com.android.internal.R.styleable.LocaleConfig);
+ defaultLocale = att.getString(
+ R.styleable.LocaleConfig_defaultLocale);
+ att.recycle();
+ }
+
+ Set<String> localeNames = new HashSet<>();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (TAG_LOCALE.equals(parser.getName())) {
final TypedArray attributes = res.obtainAttributes(
@@ -211,6 +223,15 @@
}
mStatus = STATUS_SUCCESS;
mLocales = LocaleList.forLanguageTags(String.join(",", localeNames));
+ if (defaultLocale != null) {
+ if (localeNames.contains(defaultLocale)) {
+ mDefaultLocale = Locale.forLanguageTag(defaultLocale);
+ } else {
+ Slog.w(TAG, "Default locale specified that is not contained in the list: "
+ + defaultLocale);
+ mStatus = STATUS_PARSING_FAILED;
+ }
+ }
}
/**
@@ -226,6 +247,17 @@
}
/**
+ * Returns the default locale if specified, otherwise null
+ *
+ * @return The default Locale or null
+ */
+ @SuppressLint("UseIcu")
+ @FlaggedApi(android.content.res.Flags.FLAG_DEFAULT_LOCALE)
+ public @Nullable Locale getDefaultLocale() {
+ return mDefaultLocale;
+ }
+
+ /**
* Get the status of reading the resource file where the LocaleConfig was stored.
*
* <p>Distinguish "the application didn't provide the resource file" from "the application
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index dd7db23..94e1292 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1514,13 +1514,13 @@
/**
* {@link #extras} key: the color used as a hint for the Answer action button of a
- * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
+ * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}.
*/
public static final String EXTRA_ANSWER_COLOR = "android.answerColor";
/**
* {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a
- * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
+ * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}.
*/
public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
@@ -1704,7 +1704,7 @@
private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
/**
- * {@link }: No semantic action defined.
+ * No semantic action defined.
*/
public static final int SEMANTIC_ACTION_NONE = 0;
@@ -7923,7 +7923,7 @@
* (via {@link Notification.Builder#setShortcutId(String)}) are displayed in a dedicated
* conversation section in the shade above non-conversation alerting and silence notifications.
* To be a valid conversation shortcut, the shortcut must be a
- * {@link ShortcutInfo#setLongLived()} dynamic or cached sharing shortcut.
+ * {@link ShortcutInfo.Builder#setLongLived(boolean)} dynamic or cached sharing shortcut.
*
* <p>
* This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 1ecb5d3..6009c29 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -120,9 +120,9 @@
private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>();
/**
- * The list of locales the app declares it supports.
+ * The localeConfig of the app.
*/
- private LocaleList mLocaleList = LocaleList.getEmptyLocaleList();
+ private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList());
private static class ApkKey {
public final String path;
@@ -1612,18 +1612,19 @@
}
/**
- * Returns the LocaleList current set
+ * Returns the LocaleConfig current set
*/
- public LocaleList getLocaleList() {
- return mLocaleList;
+ public LocaleConfig getLocaleConfig() {
+ return mLocaleConfig;
}
/**
- * Sets the LocaleList of app's supported locales
+ * Sets the LocaleConfig of the app
*/
- public void setLocaleList(LocaleList localeList) {
- if ((localeList != null) && !localeList.isEmpty()) {
- mLocaleList = localeList;
+ public void setLocaleConfig(LocaleConfig localeConfig) {
+ if ((localeConfig != null) && (localeConfig.getSupportedLocales() != null)
+ && !localeConfig.getSupportedLocales().isEmpty()) {
+ mLocaleConfig = localeConfig;
}
}
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index ad0af72..84b1ca5 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -16,10 +16,13 @@
package android.app.admin;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import android.os.UserManager;
+
import java.util.Objects;
/**
@@ -164,6 +167,7 @@
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
@TestApi
public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java
index c760298..113a6dd 100644
--- a/core/java/android/app/admin/SystemUpdatePolicy.java
+++ b/core/java/android/app/admin/SystemUpdatePolicy.java
@@ -125,7 +125,7 @@
*
* <p>The system limits each update to one 30-day postponement. The period begins when the
* system first postpones the update and setting new {@code TYPE_POSTPONE} policies won’t extend
- * the period. If, after 30 days the update isn’t installed (through policy changes), the system
+ * the period. If, after 30 days the update isn't installed (through policy changes), the system
* prompts the user to install the update.
*
* <p><strong>Note</strong>: Device manufacturers or carriers might choose to exempt important
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java b/core/java/android/app/admin/flags/FlagUtils.java
similarity index 68%
rename from services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
rename to core/java/android/app/admin/flags/FlagUtils.java
index 7e17ef11..7c3c3d5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
+++ b/core/java/android/app/admin/flags/FlagUtils.java
@@ -14,15 +14,20 @@
* limitations under the License.
*/
-package com.android.server.devicepolicy.flags;
+package android.app.admin.flags;
-import static com.android.server.devicepolicy.flags.Flags.devicePolicySizeTrackingEnabled;
-import static com.android.server.devicepolicy.flags.Flags.policyEngineMigrationV2Enabled;
+import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
+import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
+import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
import android.os.Binder;
+/**
+ *
+ * @hide
+ */
public final class FlagUtils {
- private FlagUtils(){}
+ private FlagUtils() {}
public static boolean isPolicyEngineMigrationV2Enabled() {
return Binder.withCleanCallingIdentity(() -> {
@@ -35,4 +40,10 @@
return devicePolicySizeTrackingEnabled();
});
}
+
+ public static boolean isOnboardingBugreportV2Enabled() {
+ return Binder.withCleanCallingIdentity(() -> {
+ return onboardingBugreportV2Enabled();
+ });
+ }
}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
new file mode 100644
index 0000000..c145c02
--- /dev/null
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -0,0 +1,22 @@
+package: "android.app.admin.flags"
+
+flag {
+ name: "policy_engine_migration_v2_enabled"
+ namespace: "enterprise"
+ description: "V2 of the policy engine migrations for Android V"
+ bug: "289520697"
+}
+
+flag {
+ name: "device_policy_size_tracking_enabled"
+ namespace: "enterprise"
+ description: "Add feature to track the total policy size and have a max threshold."
+ bug: "281543351"
+}
+
+flag {
+ name: "onboarding_bugreport_v2_enabled"
+ namespace: "enterprise"
+ description: "Add feature to track required changes for enabled V2 of auto-capturing of onboarding bug reports."
+ bug: "302517677"
+}
\ No newline at end of file
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 15bd1dc..e268968 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -651,6 +651,7 @@
// POJO used to override some autofill-related values when the node is parcelized.
// Not written to parcel.
AutofillOverlay mAutofillOverlay;
+ boolean mIsCredential;
int mX;
int mY;
@@ -799,6 +800,7 @@
if (autofillFlags != 0) {
mSanitized = in.readInt() == 1;
+ mIsCredential = in.readInt() == 1;
mImportantForAutofill = in.readInt();
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) {
@@ -1033,6 +1035,7 @@
if (autofillFlags != 0) {
out.writeInt(mSanitized ? 1 : 0);
+ out.writeInt(mIsCredential ? 1 : 0);
out.writeInt(mImportantForAutofill);
writeSensitive = mSanitized || !sanitizeOnWrite;
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) {
@@ -1246,6 +1249,19 @@
}
/**
+ * @return whether the node is a credential.
+ *
+ * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+ * not for assist purposes.
+ * TODO(b/303677885): add TestApi
+ *
+ * @hide
+ */
+ public boolean isCredential() {
+ return mIsCredential;
+ }
+
+ /**
* Gets the {@link android.text.InputType} bits of this structure.
*
* @return bits as defined by {@link android.text.InputType}.
@@ -2183,6 +2199,11 @@
}
@Override
+ public void setIsCredential(boolean isCredential) {
+ mNode.mIsCredential = isCredential;
+ }
+
+ @Override
public void setReceiveContentMimeTypes(@Nullable String[] mimeTypes) {
mNode.mReceiveContentMimeTypes = mimeTypes;
}
@@ -2498,7 +2519,9 @@
+ ", value=" + node.getAutofillValue()
+ ", sanitized=" + node.isSanitized()
+ ", important=" + node.getImportantForAutofill()
- + ", visibility=" + node.getVisibility());
+ + ", visibility=" + node.getVisibility()
+ + ", isCredential=" + node.isCredential()
+ );
}
final int NCHILDREN = node.getChildCount();
diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java
new file mode 100644
index 0000000..9828133
--- /dev/null
+++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java
@@ -0,0 +1,193 @@
+/*
+ * 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.app.servertransaction;
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ClientTransactionHandler;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.util.MergedConfiguration;
+import android.view.IWindow;
+import android.view.InsetsState;
+import android.window.ClientWindowFrames;
+
+import java.util.Objects;
+
+/**
+ * Message to deliver window resize info.
+ * @hide
+ */
+public class WindowStateResizeItem extends ClientTransactionItem {
+
+ private IWindow mWindow;
+ private ClientWindowFrames mFrames;
+ private boolean mReportDraw;
+ private MergedConfiguration mConfiguration;
+ private InsetsState mInsetsState;
+ private boolean mForceLayout;
+ private boolean mAlwaysConsumeSystemBars;
+ private int mDisplayId;
+ private int mSyncSeqId;
+ private boolean mDragResizing;
+
+ @Override
+ public void execute(@NonNull ClientTransactionHandler client,
+ @NonNull PendingTransactionActions pendingActions) {
+ try {
+ mWindow.resized(mFrames, mReportDraw, mConfiguration, mInsetsState, mForceLayout,
+ mAlwaysConsumeSystemBars, mDisplayId, mSyncSeqId, mDragResizing);
+ } catch (RemoteException e) {
+ // Should be a local call.
+ throw new RuntimeException(e);
+ }
+ }
+
+ // ObjectPoolItem implementation
+
+ private WindowStateResizeItem() {}
+
+ /** Obtains an instance initialized with provided params. */
+ public static WindowStateResizeItem obtain(@NonNull IWindow window,
+ @NonNull ClientWindowFrames frames, boolean reportDraw,
+ @NonNull MergedConfiguration configuration, @NonNull InsetsState insetsState,
+ boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
+ boolean dragResizing) {
+ WindowStateResizeItem instance =
+ ObjectPool.obtain(WindowStateResizeItem.class);
+ if (instance == null) {
+ instance = new WindowStateResizeItem();
+ }
+ instance.mWindow = requireNonNull(window);
+ instance.mFrames = requireNonNull(frames);
+ instance.mReportDraw = reportDraw;
+ instance.mConfiguration = requireNonNull(configuration);
+ instance.mInsetsState = requireNonNull(insetsState);
+ instance.mForceLayout = forceLayout;
+ instance.mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+ instance.mDisplayId = displayId;
+ instance.mSyncSeqId = syncSeqId;
+ instance.mDragResizing = dragResizing;
+
+ return instance;
+ }
+
+ @Override
+ public void recycle() {
+ mWindow = null;
+ mFrames = null;
+ mReportDraw = false;
+ mConfiguration = null;
+ mInsetsState = null;
+ mForceLayout = false;
+ mAlwaysConsumeSystemBars = false;
+ mDisplayId = INVALID_DISPLAY;
+ mSyncSeqId = -1;
+ mDragResizing = false;
+ ObjectPool.recycle(this);
+ }
+
+ // Parcelable implementation
+
+ /** Writes to Parcel. */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeStrongBinder(mWindow.asBinder());
+ dest.writeTypedObject(mFrames, flags);
+ dest.writeBoolean(mReportDraw);
+ dest.writeTypedObject(mConfiguration, flags);
+ dest.writeTypedObject(mInsetsState, flags);
+ dest.writeBoolean(mForceLayout);
+ dest.writeBoolean(mAlwaysConsumeSystemBars);
+ dest.writeInt(mDisplayId);
+ dest.writeInt(mSyncSeqId);
+ dest.writeBoolean(mDragResizing);
+ }
+
+ /** Reads from Parcel. */
+ private WindowStateResizeItem(@NonNull Parcel in) {
+ mWindow = IWindow.Stub.asInterface(in.readStrongBinder());
+ mFrames = in.readTypedObject(ClientWindowFrames.CREATOR);
+ mReportDraw = in.readBoolean();
+ mConfiguration = in.readTypedObject(MergedConfiguration.CREATOR);
+ mInsetsState = in.readTypedObject(InsetsState.CREATOR);
+ mForceLayout = in.readBoolean();
+ mAlwaysConsumeSystemBars = in.readBoolean();
+ mDisplayId = in.readInt();
+ mSyncSeqId = in.readInt();
+ mDragResizing = in.readBoolean();
+ }
+
+ public static final @NonNull Creator<WindowStateResizeItem> CREATOR = new Creator<>() {
+ public WindowStateResizeItem createFromParcel(@NonNull Parcel in) {
+ return new WindowStateResizeItem(in);
+ }
+
+ public WindowStateResizeItem[] newArray(int size) {
+ return new WindowStateResizeItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final WindowStateResizeItem other = (WindowStateResizeItem) o;
+ return Objects.equals(mWindow, other.mWindow)
+ && Objects.equals(mFrames, other.mFrames)
+ && mReportDraw == other.mReportDraw
+ && Objects.equals(mConfiguration, other.mConfiguration)
+ && Objects.equals(mInsetsState, other.mInsetsState)
+ && mForceLayout == other.mForceLayout
+ && mAlwaysConsumeSystemBars == other.mAlwaysConsumeSystemBars
+ && mDisplayId == other.mDisplayId
+ && mSyncSeqId == other.mSyncSeqId
+ && mDragResizing == other.mDragResizing;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Objects.hashCode(mWindow);
+ result = 31 * result + Objects.hashCode(mFrames);
+ result = 31 * result + (mReportDraw ? 1 : 0);
+ result = 31 * result + Objects.hashCode(mConfiguration);
+ result = 31 * result + Objects.hashCode(mInsetsState);
+ result = 31 * result + (mForceLayout ? 1 : 0);
+ result = 31 * result + (mAlwaysConsumeSystemBars ? 1 : 0);
+ result = 31 * result + mDisplayId;
+ result = 31 * result + mSyncSeqId;
+ result = 31 * result + (mDragResizing ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "WindowStateResizeItem{window=" + mWindow
+ + ", reportDrawn=" + mReportDraw
+ + ", configuration=" + mConfiguration
+ + "}";
+ }
+}
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index afe87de..f401a76 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -2,8 +2,14 @@
flag {
name: "user_interaction_type_api"
- namespace: "power_optimization"
+ namespace: "backstage_power"
description: "Feature flag for user interaction event report/query API"
bug: "296061232"
}
+flag {
+ name: "report_usage_stats_permission"
+ namespace: "backstage_power"
+ description: "Feature flag for the new REPORT_USAGE_STATS permission."
+ bug: "296056771"
+}
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 6393c45..8b09bdf 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -144,6 +144,7 @@
* @return the tag of this association.
* @see CompanionDeviceManager#setAssociationTag(int, String)
*/
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@Nullable
public String getTag() {
return mTag;
@@ -459,6 +460,7 @@
}
/** @hide */
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@TestApi
@NonNull
public Builder setTag(@Nullable String tag) {
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index a84845a..70811bb 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -23,6 +23,7 @@
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -216,12 +217,14 @@
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@TestApi public static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
/**
* Message header assigned to the remote authentication handshakes.
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
public static final int MESSAGE_REQUEST_REMOTE_AUTHENTICATION = 0x63827765; // ?RMA
/**
@@ -229,6 +232,7 @@
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 0x63678883; // ?CXS
/**
@@ -236,6 +240,7 @@
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
@@ -873,6 +878,7 @@
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
public interface OnTransportsChangedListener {
/**
@@ -892,6 +898,7 @@
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void addOnTransportsChangedListener(
@@ -913,6 +920,7 @@
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void removeOnTransportsChangedListener(
@@ -934,6 +942,7 @@
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void sendMessage(int messageType, @NonNull byte[] data, @NonNull int[] associationIds) {
@@ -951,6 +960,7 @@
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
public interface OnMessageReceivedListener {
/**
@@ -964,6 +974,7 @@
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void addOnMessageReceivedListener(
@@ -983,6 +994,7 @@
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void removeOnMessageReceivedListener(int messageType,
@@ -1423,6 +1435,7 @@
* of the companion device recorded by CompanionDeviceManager
* @param tag the tag of this association
*/
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@UserHandleAware
public void setAssociationTag(int associationId, @NonNull String tag) {
Objects.requireNonNull(tag, "tag cannot be null");
@@ -1447,6 +1460,7 @@
* of the companion device recorded by CompanionDeviceManager
* @see CompanionDeviceManager#setAssociationTag(int, String)
*/
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@UserHandleAware
public void clearAssociationTag(int associationId) {
try {
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 03e75e9..570ecaa 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -161,16 +161,16 @@
public static final int DEVICE_EVENT_BT_DISCONNECTED = 3;
/**
- * A companion app for a {@link AssociationInfo#isSelfManaged() self-managed} device will
- * receive the callback {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a
- * device has appeared on its own.
+ * A companion app for a self-managed device will receive the callback
+ * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has appeared on its
+ * own.
*/
public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4;
/**
- * A companion app for a {@link AssociationInfo#isSelfManaged() self-managed} device will
- * receive the callback {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a
- * device has disappeared on its own.
+ * A companion app for a self-managed device will receive the callback
+ * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has disappeared on
+ * its own.
*/
public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5;
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index b9e5609..4f9c849 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -5,4 +5,18 @@
namespace: "companion"
description: "Controls if the new Builder is exposed to test apis."
bug: "296251481"
+}
+
+flag {
+ name: "companion_transport_apis"
+ namespace: "companion"
+ description: "Grants access to the companion transport apis."
+ bug: "288297505"
+}
+
+flag {
+ name: "association_tag"
+ namespace: "companion"
+ description: "Enable Association tag APIs "
+ bug: "289241123"
}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index 0af4c92..93a3e78 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -33,9 +33,6 @@
*
* <p>Read-only device representation exposing the properties of an existing virtual device.
*
- * <p class="note">Not to be confused with {@link VirtualDeviceManager.VirtualDevice}, which is used
- * by the virtual device creator and allows them to manage the device.
- *
* @see VirtualDeviceManager#registerVirtualDeviceListener
*/
public final class VirtualDevice implements Parcelable {
@@ -120,8 +117,6 @@
/**
* Returns the name of the virtual device (optionally) provided during its creation.
- *
- * @see VirtualDeviceParams.Builder#setName(String)
*/
public @Nullable String getName() {
return mName;
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
index eaa1792..14c7997 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensor.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -115,6 +115,11 @@
parcel.writeStrongBinder(mToken);
}
+ @Override
+ public String toString() {
+ return "VirtualSensor{ mType=" + mType + ", mName='" + mName + "' }";
+ }
+
/**
* Send a sensor event to the system.
*/
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
index bf78dd0..b9451a7 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
@@ -46,15 +46,15 @@
* <pre>
* VirtualSensorDirectChannelWriter writer = new VirtualSensorDirectChannelWriter();
* VirtualSensorDirectChannelCallback callback = new VirtualSensorDirectChannelCallback() {
- * @Override
+ * {@literal @}Override
* public void onDirectChannelCreated(int channelHandle, SharedMemory sharedMemory) {
* writer.addChannel(channelHandle, sharedMemory);
* }
- * @Override
+ * {@literal @}Override
* public void onDirectChannelDestroyed(int channelHandle);
* writer.removeChannel(channelHandle);
* }
- * @Override
+ * {@literal @}Override
* public void onDirectChannelConfigured(int channelHandle, VirtualSensor sensor, int rateLevel,
* int reportToken)
* if (!writer.configureChannel(channelHandle, sensor, rateLevel, reportToken)) {
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index bfc1eec..62fbcaf 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -730,7 +730,7 @@
/**
* The next app to receive the permission protected data.
*
- * @deprecated Use {@link setNextAttributionSource} instead.
+ * @deprecated Use {@link #setNextAttributionSource} instead.
*/
@Deprecated
public @NonNull Builder setNext(@Nullable AttributionSource value) {
@@ -744,6 +744,7 @@
/**
* The next app to receive the permission protected data.
*/
+ @FlaggedApi(Flags.FLAG_SET_NEXT_ATTRIBUTION_SOURCE)
public @NonNull Builder setNextAttributionSource(@NonNull AttributionSource value) {
checkNotUsed();
mBuilderFieldsSet |= 0x20;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b6a98a5..59bb73b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -323,7 +323,7 @@
// Make sure no flag uses the sign bit (most significant bit) of the long integer,
// to avoid future confusion.
BIND_BYPASS_USER_NETWORK_RESTRICTIONS,
- BIND_FILTER_OUT_QUARANTINED_COMPONENTS,
+ BIND_MATCH_QUARANTINED_COMPONENTS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface BindServiceFlagsLongBits {}
@@ -703,7 +703,7 @@
*
* @hide
*/
- public static final long BIND_FILTER_OUT_QUARANTINED_COMPONENTS = 0x2_0000_0000L;
+ public static final long BIND_MATCH_QUARANTINED_COMPONENTS = 0x2_0000_0000L;
/**
@@ -4357,7 +4357,6 @@
* @see android.telephony.CarrierConfigManager
* @see #EUICC_SERVICE
* @see android.telephony.euicc.EuiccManager
- * @see android.telephony.MmsManager
* @see #INPUT_METHOD_SERVICE
* @see android.view.inputmethod.InputMethodManager
* @see #UI_MODE_SERVICE
diff --git a/core/java/android/content/ContextParams.java b/core/java/android/content/ContextParams.java
index 988a9c0..b844d35 100644
--- a/core/java/android/content/ContextParams.java
+++ b/core/java/android/content/ContextParams.java
@@ -16,7 +16,10 @@
package android.content;
+import static android.permission.flags.Flags.FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE;
+
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -102,6 +105,7 @@
* registered.
*/
@NonNull
+ @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE)
public boolean shouldRegisterAttributionSource() {
return mShouldRegisterAttributionSource;
}
@@ -179,6 +183,7 @@
* created should be registered.
*/
@NonNull
+ @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE)
public Builder setShouldRegisterAttributionSource(boolean shouldRegister) {
mShouldRegisterAttributionSource = shouldRegister;
return this;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 5f4c05f..bb9cc0b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -64,6 +64,7 @@
import android.os.ResultReceiver;
import android.os.ShellCommand;
import android.os.StrictMode;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.provider.ContactsContract.QuickContact;
@@ -2796,6 +2797,8 @@
* started and is no longer considered stopped.
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+ * <li> {@link #EXTRA_TIME} containing the {@link SystemClock#elapsedRealtime()
+ * elapsed realtime} of when the package was unstopped.
* </ul>
*
* <p class="note">This is a protected intent that can only be sent by the system.
@@ -2869,9 +2872,15 @@
*
* <p class="note">This is a protected intent that can only be sent
* by the system.
+ * <p>
+ * Starting in {@link Build.VERSION_CODES#VANILLA_ICE_CREAM Android V}, an extra timestamp
+ * {@link #EXTRA_TIME} is included with this broadcast to indicate the exact time the package
+ * was restarted, in {@link SystemClock#elapsedRealtime() elapsed realtime}.
+ * </p>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
+
/**
* Broadcast Action: The user has cleared the data of a package. This should
* be preceded by {@link #ACTION_PACKAGE_RESTARTED}, after which all of
@@ -3929,6 +3938,8 @@
* {@link #ACTION_BOOT_COMPLETED} is sent. This is sent as a foreground
* broadcast, since it is part of a visible user interaction; be as quick
* as possible when handling it.
+ *
+ * <p><b>Note:</b> This broadcast is not sent to the system user.
*/
public static final String ACTION_USER_INITIALIZE =
"android.intent.action.USER_INITIALIZE";
@@ -4180,7 +4191,7 @@
* new state of quiet mode. This is only sent to registered receivers, not manifest receivers.
*
* <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_AVAILABLE} but functions as a
- * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+ * generic broadcast for all profile users.
*/
@FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
public static final String ACTION_PROFILE_AVAILABLE =
@@ -4194,7 +4205,7 @@
* new state of quiet mode. This is only sent to registered receivers, not manifest receivers.
*
* <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_UNAVAILABLE} but functions as
- * a generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+ * a generic broadcast for all profile users.
*/
@FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
public static final String ACTION_PROFILE_UNAVAILABLE =
@@ -4222,7 +4233,7 @@
* that was removed.
*
* <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_REMOVED} but functions as a
- * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+ * generic broadcast for all profile users.
* It is sent in addition to the {@link #ACTION_MANAGED_PROFILE_REMOVED} broadcast when a
* managed user is removed.
*
@@ -4242,7 +4253,7 @@
* that was added.
*
* <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_ADDED} but functions as a
- * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+ * generic broadcast for all profile users.
* It is sent in addition to the {@link #ACTION_MANAGED_PROFILE_ADDED} broadcast when a
* managed user is added.
*
@@ -5323,6 +5334,7 @@
* @hide
*/
@SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
// ---------------------------------------------------------------------
@@ -6575,8 +6587,8 @@
= "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
/**
- * Optional extra specifying a time in milliseconds since the Epoch. The value must be
- * non-negative.
+ * Optional extra specifying a time in milliseconds. The timebase depends on the Intent
+ * including this extra. The value must be non-negative.
* <p>
* Type: long
* </p>
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index c4547b8..df2d7e7 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -16,6 +16,7 @@
package android.content.om;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -546,6 +547,7 @@
* @param configuration The string representation of the config this overlay is enabled for
*/
@NonNull
+ @FlaggedApi(android.content.res.Flags.FLAG_ASSET_FILE_DESCRIPTOR_FRRO)
public void setResourceValue(
@NonNull String resourceName,
@NonNull AssetFileDescriptor value,
diff --git a/core/java/android/content/pm/ArchivedActivityParcel.aidl b/core/java/android/content/pm/ArchivedActivityParcel.aidl
index 7ab7ed1..74953ff 100644
--- a/core/java/android/content/pm/ArchivedActivityParcel.aidl
+++ b/core/java/android/content/pm/ArchivedActivityParcel.aidl
@@ -16,9 +16,12 @@
package android.content.pm;
+import android.content.ComponentName;
+
/** @hide */
parcelable ArchivedActivityParcel {
String title;
+ ComponentName originalComponentName;
// PNG compressed bitmaps.
byte[] iconBitmap;
byte[] monochromeIconBitmap;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 45338bb..b37da84 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -838,7 +838,7 @@
GET_DISABLED_COMPONENTS,
GET_DISABLED_UNTIL_USED_COMPONENTS,
GET_UNINSTALLED_PACKAGES,
- FILTER_OUT_QUARANTINED_COMPONENTS,
+ MATCH_QUARANTINED_COMPONENTS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ComponentInfoFlagsBits {}
@@ -863,7 +863,7 @@
GET_DISABLED_UNTIL_USED_COMPONENTS,
GET_UNINSTALLED_PACKAGES,
MATCH_CLONE_PROFILE,
- FILTER_OUT_QUARANTINED_COMPONENTS,
+ MATCH_QUARANTINED_COMPONENTS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ResolveInfoFlagsBits {}
@@ -1252,12 +1252,15 @@
*/
// TODO(b/278553670) Unhide and update @links before launch.
@SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32;
/**
- * @hide
+ * Querying flag: always match components of packages in quarantined state.
+ * @see #isPackageQuarantined
*/
- public static final long FILTER_OUT_QUARANTINED_COMPONENTS = 0x100000000L;
+ @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
+ public static final long MATCH_QUARANTINED_COMPONENTS = 0x100000000L;
/**
* Flag for {@link #addCrossProfileIntentFilter}: if this flag is set: when
@@ -1477,6 +1480,7 @@
INSTALL_ALLOW_DOWNGRADE,
INSTALL_STAGED,
INSTALL_REQUEST_UPDATE_OWNERSHIP,
+ INSTALL_IGNORE_DEXOPT_PROFILE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface InstallFlags {}
@@ -1709,6 +1713,18 @@
public static final int INSTALL_ARCHIVED = 1 << 27;
/**
+ * If set, all dexopt profiles are ignored by dexopt during the installation, including the
+ * profile in the DM file and the profile embedded in the APK file. If an invalid profile is
+ * provided during installation, no warning will be reported by {@code adb install}.
+ *
+ * This option does not affect later dexopt operations (e.g., background dexopt and manual `pm
+ * compile` invocations).
+ *
+ * @hide
+ */
+ public static final int INSTALL_IGNORE_DEXOPT_PROFILE = 1 << 28;
+
+ /**
* Flag parameter for {@link #installPackage} to force a non-staged update of an APEX. This is
* a development-only feature and should not be used on end user devices.
*
@@ -4605,6 +4621,16 @@
public static final String FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS =
"android.software.wallet_location_based_suggestions";
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * the rotary encoder hardware to support rotating bezel on watch.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_ROTARY_ENCODER_LOW_RES =
+ "android.hardware.rotaryencoder.lowres";
+
/** @hide */
public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
@@ -9891,12 +9917,16 @@
/**
* Query if an app is currently quarantined.
+ * A misbehaving app can be quarantined by e.g. a system of another privileged entity.
+ * Quarantined apps are similar to disabled, but still visible in e.g. Launcher.
+ * Only activities of such apps can still be queried, but not services etc.
+ * Quarantined apps can't be bound to, and won't receive broadcasts.
+ * They can't be resolved, unless {@link #MATCH_QUARANTINED_COMPONENTS} specified.
*
* @return {@code true} if the given package is quarantined, {@code false} otherwise
* @throws NameNotFoundException if the package could not be found.
- *
- * @hide
*/
+ @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
public boolean isPackageQuarantined(@NonNull String packageName) throws NameNotFoundException {
throw new UnsupportedOperationException("isPackageQuarantined not implemented");
}
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index b2cc070..db12728 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -50,3 +50,11 @@
description: "Feature flag to enable the features that rely on new ART Service APIs that are in the VIC version of the ART module."
bug: "304741685"
}
+
+flag {
+ name: "sdk_lib_independence"
+ namespace: "package_manager_service"
+ description: "Feature flag to keep app working even if its declared sdk-library dependency is unavailable."
+ bug: "295827951"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 5cc3b92..c7790bd 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -27,6 +27,8 @@
import android.annotation.RawRes;
import android.annotation.StyleRes;
import android.annotation.StyleableRes;
+import android.app.LocaleConfig;
+import android.app.ResourcesManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
@@ -426,38 +428,59 @@
String[] selectedLocales = null;
String defaultLocale = null;
+ LocaleConfig lc = ResourcesManager.getInstance().getLocaleConfig();
if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
if (locales.size() > 1) {
- String[] availableLocales;
- // The LocaleList has changed. We must query the AssetManager's
- // available Locales and figure out the best matching Locale in the new
- // LocaleList.
- availableLocales = mAssets.getNonSystemLocales();
- if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
- // No app defined locales, so grab the system locales.
- availableLocales = mAssets.getLocales();
- if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
- availableLocales = null;
+ if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) {
+ Locale[] intersection =
+ locales.getIntersection(lc.getSupportedLocales());
+ mConfiguration.setLocales(new LocaleList(intersection));
+ selectedLocales = new String[intersection.length];
+ for (int i = 0; i < intersection.length; i++) {
+ selectedLocales[i] =
+ adjustLanguageTag(intersection[i].toLanguageTag());
}
- }
+ defaultLocale =
+ adjustLanguageTag(lc.getDefaultLocale().toLanguageTag());
+ } else {
+ String[] availableLocales;
+ // The LocaleList has changed. We must query the AssetManager's
+ // available Locales and figure out the best matching Locale in the new
+ // LocaleList.
+ availableLocales = mAssets.getNonSystemLocales();
+ if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+ // No app defined locales, so grab the system locales.
+ availableLocales = mAssets.getLocales();
+ if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+ availableLocales = null;
+ }
+ }
- if (availableLocales != null) {
- final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
- availableLocales);
- if (bestLocale != null) {
- selectedLocales = new String[]{
- adjustLanguageTag(bestLocale.toLanguageTag())};
- if (!bestLocale.equals(locales.get(0))) {
- mConfiguration.setLocales(
- new LocaleList(bestLocale, locales));
+ if (availableLocales != null) {
+ final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
+ availableLocales);
+ if (bestLocale != null) {
+ selectedLocales = new String[]{
+ adjustLanguageTag(bestLocale.toLanguageTag())};
+ if (!bestLocale.equals(locales.get(0))) {
+ mConfiguration.setLocales(
+ new LocaleList(bestLocale, locales));
+ }
}
}
}
}
}
if (selectedLocales == null) {
- selectedLocales = new String[]{
- adjustLanguageTag(locales.get(0).toLanguageTag())};
+ if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) {
+ selectedLocales = new String[locales.size()];
+ for (int i = 0; i < locales.size(); i++) {
+ selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
+ }
+ } else {
+ selectedLocales = new String[]{
+ adjustLanguageTag(locales.get(0).toLanguageTag())};
+ }
}
if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
new file mode 100644
index 0000000..1b8eb07
--- /dev/null
+++ b/core/java/android/content/res/flags.aconfig
@@ -0,0 +1,17 @@
+package: "android.content.res"
+
+flag {
+ name: "default_locale"
+ namespace: "resource_manager"
+ description: "Feature flag for default locale in LocaleConfig"
+ bug: "117306409"
+ # fixed_read_only or device wont boot because of permission issues accessing flags during boot
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "asset_file_descriptor_frro"
+ namespace: "resource_manager"
+ description: "Feature flag for passing in an AssetFileDescriptor to create an frro"
+ bug: "304478666"
+}
diff --git a/core/java/android/credentials/CreateCredentialException.java b/core/java/android/credentials/CreateCredentialException.java
index c344004..8f07d19 100644
--- a/core/java/android/credentials/CreateCredentialException.java
+++ b/core/java/android/credentials/CreateCredentialException.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Activity;
+import android.content.Context;
import android.os.CancellationSignal;
import android.os.OutcomeReceiver;
@@ -28,8 +28,8 @@
/**
* Represents an error encountered during the
- * {@link CredentialManager#createCredential(CreateCredentialRequest,
- * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
+ * {@link CredentialManager#createCredential(Context, CreateCredentialRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} operation.
*/
public class CreateCredentialException extends Exception {
/**
@@ -41,7 +41,7 @@
/**
* The error type value for when no create options are available from any provider(s),
- * for the given {@link CredentialManager#createCredential(CreateCredentialRequest, Activity,
+ * for the given {@link CredentialManager#createCredential(Context, CreateCredentialRequest,
* CancellationSignal, Executor, OutcomeReceiver)} request.
*/
@NonNull
diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java
index db71624..755e659 100644
--- a/core/java/android/credentials/CredentialDescription.java
+++ b/core/java/android/credentials/CredentialDescription.java
@@ -155,8 +155,7 @@
}
/**
- * {@link CredentialDescription#mType} and
- * {@link CredentialDescription#mSupportedElementKeys} are enough for hashing. Constructor
+ * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for hashing. Constructor
* enforces {@link CredentialEntry} to have the same type and
* {@link android.app.slice.Slice} contained by the entry can not be hashed.
*/
@@ -166,8 +165,7 @@
}
/**
- * {@link CredentialDescription#mType} and
- * {@link CredentialDescription#mSupportedElementKeys} are enough for equality check.
+ * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for equality check.
*/
@Override
public boolean equals(Object obj) {
diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java
index 8503072..38fbd72 100644
--- a/core/java/android/credentials/CredentialProviderInfo.java
+++ b/core/java/android/credentials/CredentialProviderInfo.java
@@ -128,6 +128,11 @@
@TestApi
@FlaggedApi(Flags.FLAG_SETTINGS_ACTIVITY_ENABLED)
public CharSequence getSettingsActivity() {
+ // Add a manual check to make sure this returns null if
+ // the flag is not enabled.
+ if (!Flags.settingsActivityEnabled()) {
+ return null;
+ }
return mSettingsActivity;
}
diff --git a/core/java/android/credentials/GetCredentialException.java b/core/java/android/credentials/GetCredentialException.java
index 720c53b..0421d1f 100644
--- a/core/java/android/credentials/GetCredentialException.java
+++ b/core/java/android/credentials/GetCredentialException.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Activity;
+import android.content.Context;
import android.os.CancellationSignal;
import android.os.OutcomeReceiver;
@@ -28,8 +28,8 @@
/**
* Represents an error encountered during the
- * {@link CredentialManager#getCredential(GetCredentialRequest,
- * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
+ * {@link CredentialManager#getCredential(Context, GetCredentialRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} operation.
*/
public class GetCredentialException extends Exception {
/**
@@ -41,7 +41,7 @@
/**
* The error type value for when no credential is found available for the given {@link
- * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal,
+ * CredentialManager#getCredential(Context, GetCredentialRequest, CancellationSignal,
* Executor, OutcomeReceiver)} request.
*/
@NonNull
diff --git a/core/java/android/credentials/PrepareGetCredentialResponse.java b/core/java/android/credentials/PrepareGetCredentialResponse.java
index 056b18a..212f571 100644
--- a/core/java/android/credentials/PrepareGetCredentialResponse.java
+++ b/core/java/android/credentials/PrepareGetCredentialResponse.java
@@ -22,7 +22,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.IntentSender;
@@ -36,7 +35,7 @@
/**
* A response object that prefetches user app credentials and provides metadata about them. It can
* then be used to issue the full credential retrieval flow via the
- * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal,
+ * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal,
* Executor, OutcomeReceiver)} method to perform the remaining flows such as consent collection
* and credential selection, to officially retrieve a credential.
*/
@@ -44,7 +43,7 @@
/**
* A handle that represents a pending get-credential operation. Pass this handle to {@link
- * CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal,
+ * CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal,
* Executor, OutcomeReceiver)} to perform the remaining flows to officially retrieve a
* credential.
*/
@@ -144,7 +143,7 @@
/**
* Returns a handle that represents this pending get-credential operation. Pass this handle to
- * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity,
+ * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle,
* CancellationSignal, Executor, OutcomeReceiver)} to perform the remaining flows to officially
* retrieve a credential.
*/
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 746f2f2..8a4f678 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -16,6 +16,7 @@
package android.database.sqlite;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -700,6 +701,7 @@
* }
* </pre>
*/
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public void beginTransactionReadOnly() {
beginTransactionWithListenerReadOnly(null);
}
@@ -783,6 +785,7 @@
* }
* </pre>
*/
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public void beginTransactionWithListenerReadOnly(
@Nullable SQLiteTransactionListener transactionListener) {
beginTransaction(transactionListener, SQLiteSession.TRANSACTION_MODE_DEFERRED);
@@ -2221,6 +2224,7 @@
* @throws IllegalStateException if a transaction is not in progress.
* @throws SQLiteException if the SQL cannot be compiled.
*/
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
@NonNull
public SQLiteRawStatement createRawStatement(@NonNull String sql) {
Objects.requireNonNull(sql);
@@ -2240,6 +2244,7 @@
* @return The ROWID of the last row to be inserted under this connection.
* @throws IllegalStateException if there is no current transaction.
*/
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public long getLastInsertRowId() {
return getThreadSession().getLastInsertRowId();
}
@@ -2253,6 +2258,7 @@
* @return The number of rows changed by the most recent sql statement
* @throws IllegalStateException if there is no current transaction.
*/
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public long getLastChangedRowCount() {
return getThreadSession().getLastChangedRowCount();
}
@@ -2280,6 +2286,7 @@
* @return The number of rows changed on the current connection.
* @throws IllegalStateException if there is no current transaction.
*/
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public long getTotalChangedRowCount() {
return getThreadSession().getTotalChangedRowCount();
}
diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java
index 165f181..33f602b 100644
--- a/core/java/android/database/sqlite/SQLiteRawStatement.java
+++ b/core/java/android/database/sqlite/SQLiteRawStatement.java
@@ -16,6 +16,7 @@
package android.database.sqlite;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -70,6 +71,7 @@
*
* @see <a href="http://sqlite.org/c3ref/stmt.html">sqlite3_stmt</a>
*/
+@FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public final class SQLiteRawStatement implements Closeable {
private static final String TAG = "SQLiteRawStatement";
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
new file mode 100644
index 0000000..62a5123
--- /dev/null
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.database.sqlite"
+
+flag {
+ name: "sqlite_apis_35"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "SQLite APIs held back for Android 15"
+ bug: "279043253"
+}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index f033f97..bcf447b 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -16,6 +16,7 @@
package android.hardware;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -27,6 +28,8 @@
import android.util.Log;
import android.util.SparseArray;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -1809,6 +1812,41 @@
protected abstract boolean cancelTriggerSensorImpl(TriggerEventListener listener,
Sensor sensor, boolean disable);
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({DATA_INJECTION, REPLAY_DATA_INJECTION, HAL_BYPASS_REPLAY_DATA_INJECTION})
+ public @interface DataInjectionMode {}
+ /**
+ * This mode is only used for testing purposes. Not all HALs support this mode. In this mode,
+ * the HAL ignores the sensor data provided by physical sensors and accepts the data that is
+ * injected from the SensorService as if it were the real sensor data. This mode is primarily
+ * used for testing various algorithms like vendor provided SensorFusion, Step Counter and
+ * Step Detector etc. Typically, in this mode, there is a client app which injects
+ * sensor data into the HAL. Normal apps can register and unregister for any sensor
+ * that supports injection. Registering to sensors that do not support injection will
+ * give an error.
+ * This is the default data injection mode.
+ * @hide
+ */
+ public static final int DATA_INJECTION = 1;
+ /**
+ * Mostly equivalent to DATA_INJECTION with the difference being that the injected data is
+ * delivered to all requesting apps rather than just the package allowed to inject data.
+ * This mode is only allowed to be used on development builds.
+ * @hide
+ */
+ public static final int REPLAY_DATA_INJECTION = 3;
+ /**
+ * Like REPLAY_DATA_INJECTION but injected data is not sent into the HAL. It is stored in a
+ * buffer in the platform and played back to all requesting apps.
+ * This is useful for playing back sensor data to test platform components without
+ * relying on the HAL to support data injection.
+ * @hide
+ */
+ public static final int HAL_BYPASS_REPLAY_DATA_INJECTION = 4;
+
/**
* For testing purposes only. Not for third party applications.
@@ -1833,13 +1871,47 @@
*/
@SystemApi
public boolean initDataInjection(boolean enable) {
- return initDataInjectionImpl(enable);
+ return initDataInjectionImpl(enable, DATA_INJECTION);
+ }
+
+ /**
+ * For testing purposes only. Not for third party applications.
+ *
+ * Initialize data injection mode and create a client for data injection. SensorService should
+ * already be operating in one of DATA_INJECTION, REPLAY_DATA_INJECTION or
+ * HAL_BYPASS_REPLAY_DATA_INJECTION modes for this call succeed. To set SensorService in
+ * a Data Injection mode, use one of:
+ *
+ * <ul>
+ * <li>adb shell dumpsys sensorservice data_injection</li>
+ * <li>adb shell dumpsys sensorservice replay_data_injection package_name</li>
+ * <li>adb shell dumpsys sensorservice hal_bypass_replay_data_injection package_name</li>
+ * </ul>
+ *
+ * Typically this is done using a host side test. This mode is expected to be used
+ * only for testing purposes. See {@link DataInjectionMode} for details of each data injection
+ * mode. Once this method succeeds, the test can call
+ * {@link #injectSensorData(Sensor, float[], int, long)} to inject sensor data into the HAL.
+ * To put SensorService back into normal mode, use "adb shell dumpsys sensorservice enable"
+ *
+ * @param enable True to initialize a client in a data injection mode.
+ * False to clean up the native resources.
+ *
+ * @param mode One of DATA_INJECTION, REPLAY_DATA_INJECTION or HAL_BYPASS_DATA_INJECTION.
+ * See {@link DataInjectionMode} for details.
+ *
+ * @return true if the HAL supports data injection and false
+ * otherwise.
+ * @hide
+ */
+ public boolean initDataInjection(boolean enable, @DataInjectionMode int mode) {
+ return initDataInjectionImpl(enable, mode);
}
/**
* @hide
*/
- protected abstract boolean initDataInjectionImpl(boolean enable);
+ protected abstract boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode);
/**
* For testing purposes only. Not for third party applications.
@@ -1871,9 +1943,6 @@
if (sensor == null) {
throw new IllegalArgumentException("sensor cannot be null");
}
- if (!sensor.isDataInjectionSupported()) {
- throw new IllegalArgumentException("sensor does not support data injection");
- }
if (values == null) {
throw new IllegalArgumentException("sensor data cannot be null");
}
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index dfd3802..40e03db 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -90,6 +90,8 @@
private static native void nativeGetRuntimeSensors(
long nativeInstance, int deviceId, List<Sensor> list);
private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
+ private static native boolean nativeIsReplayDataInjectionEnabled(long nativeInstance);
+ private static native boolean nativeIsHalBypassReplayDataInjectionEnabled(long nativeInstance);
private static native int nativeCreateDirectChannel(
long nativeInstance, int deviceId, long size, int channelType, int fd,
@@ -384,20 +386,41 @@
}
}
- protected boolean initDataInjectionImpl(boolean enable) {
+ protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {
synchronized (sLock) {
+ boolean isDataInjectionModeEnabled = false;
if (enable) {
- boolean isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance);
+ switch (mode) {
+ case DATA_INJECTION:
+ isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance);
+ break;
+ case REPLAY_DATA_INJECTION:
+ isDataInjectionModeEnabled = nativeIsReplayDataInjectionEnabled(
+ mNativeInstance);
+ break;
+ case HAL_BYPASS_REPLAY_DATA_INJECTION:
+ isDataInjectionModeEnabled = nativeIsHalBypassReplayDataInjectionEnabled(
+ mNativeInstance);
+ break;
+ default:
+ break;
+ }
// The HAL does not support injection OR SensorService hasn't been set in DI mode.
if (!isDataInjectionModeEnabled) {
- Log.e(TAG, "Data Injection mode not enabled");
+ Log.e(TAG, "The correct Data Injection mode has not been enabled");
return false;
}
+ if (sInjectEventQueue != null && sInjectEventQueue.getDataInjectionMode() != mode) {
+ // The inject event queue has been initialized for a different type of DI
+ // close it and create a new one
+ sInjectEventQueue.dispose();
+ sInjectEventQueue = null;
+ }
// Initialize a client for data_injection.
if (sInjectEventQueue == null) {
try {
sInjectEventQueue = new InjectEventQueue(
- mMainLooper, this, mContext.getPackageName());
+ mMainLooper, this, mode, mContext.getPackageName());
} catch (RuntimeException e) {
Log.e(TAG, "Cannot create InjectEventQueue: " + e);
}
@@ -421,6 +444,12 @@
Log.e(TAG, "Data injection mode not activated before calling injectSensorData");
return false;
}
+ if (sInjectEventQueue.getDataInjectionMode() != HAL_BYPASS_REPLAY_DATA_INJECTION
+ && !sensor.isDataInjectionSupported()) {
+ // DI mode and Replay DI mode require support from the sensor HAL
+ // HAL Bypass mode doesn't require this.
+ throw new IllegalArgumentException("sensor does not support data injection");
+ }
int ret = sInjectEventQueue.injectSensorData(sensor.getHandle(), values, accuracy,
timestamp);
// If there are any errors in data injection clean up the native resources.
@@ -825,6 +854,8 @@
protected static final int OPERATING_MODE_NORMAL = 0;
protected static final int OPERATING_MODE_DATA_INJECTION = 1;
+ protected static final int OPERATING_MODE_REPLAY_DATA_INJECTION = 3;
+ protected static final int OPERATING_MODE_HAL_BYPASS_REPLAY_DATA_INJECTION = 4;
BaseEventQueue(Looper looper, SystemSensorManager manager, int mode, String packageName) {
if (packageName == null) packageName = "";
@@ -1134,8 +1165,12 @@
}
final class InjectEventQueue extends BaseEventQueue {
- public InjectEventQueue(Looper looper, SystemSensorManager manager, String packageName) {
- super(looper, manager, OPERATING_MODE_DATA_INJECTION, packageName);
+
+ private int mMode;
+ public InjectEventQueue(Looper looper, SystemSensorManager manager,
+ @DataInjectionMode int mode, String packageName) {
+ super(looper, manager, mode, packageName);
+ mMode = mode;
}
int injectSensorData(int handle, float[] values, int accuracy, long timestamp) {
@@ -1161,6 +1196,10 @@
protected void removeSensorEvent(Sensor sensor) {
}
+
+ int getDataInjectionMode() {
+ return mMode;
+ }
}
protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index 99f3d15..53a9a75 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -59,7 +59,8 @@
@IntDef(prefix = {"BRIGHTNESS_MAX_REASON_"}, value = {
BRIGHTNESS_MAX_REASON_NONE,
- BRIGHTNESS_MAX_REASON_THERMAL
+ BRIGHTNESS_MAX_REASON_THERMAL,
+ BRIGHTNESS_MAX_REASON_POWER_IC
})
@Retention(RetentionPolicy.SOURCE)
public @interface BrightnessMaxReason {}
@@ -74,6 +75,11 @@
*/
public static final int BRIGHTNESS_MAX_REASON_THERMAL = 1;
+ /**
+ * Maximum brightness is restricted due to power throttling.
+ */
+ public static final int BRIGHTNESS_MAX_REASON_POWER_IC = 2;
+
/** Brightness */
public final float brightness;
@@ -144,6 +150,8 @@
return "none";
case BRIGHTNESS_MAX_REASON_THERMAL:
return "thermal";
+ case BRIGHTNESS_MAX_REASON_POWER_IC:
+ return "power IC";
}
return "invalid";
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index f347909..aeddd0c 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -31,6 +31,7 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.app.ActivityThread;
import android.app.KeyguardManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -775,7 +776,8 @@
*/
public void registerDisplayListener(@NonNull DisplayListener listener,
@Nullable Handler handler, @EventsMask long eventsMask) {
- mGlobal.registerDisplayListener(listener, handler, eventsMask);
+ mGlobal.registerDisplayListener(listener, handler, eventsMask,
+ ActivityThread.currentPackageName());
}
/**
@@ -1819,6 +1821,12 @@
String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data";
/**
+ * Key for the power throttling data as a String formatted, from the display
+ * device config.
+ */
+ String KEY_POWER_THROTTLING_DATA = "power_throttling_data";
+
+ /**
* Key for new power controller feature flag. If enabled new DisplayPowerController will
* be used.
* Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 6d6085b..2b5f5ee 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -25,6 +25,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.app.ActivityThread;
import android.app.PropertyInvalidatedCache;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -45,8 +46,11 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Trace;
+import android.sysprop.DisplayProperties;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayAdjustments;
@@ -72,7 +76,13 @@
*/
public final class DisplayManagerGlobal {
private static final String TAG = "DisplayManager";
- private static final boolean DEBUG = false;
+
+ private static final String EXTRA_LOGGING_PACKAGE_NAME =
+ DisplayProperties.debug_vri_package().orElse(null);
+ private static String sCurrentPackageName = ActivityThread.currentPackageName();
+ private static boolean sExtraDisplayListenerLogging = initExtraLogging();
+
+ private static final boolean DEBUG = false || sExtraDisplayListenerLogging;
// True if display info and display ids should be cached.
//
@@ -130,6 +140,8 @@
@VisibleForTesting
public DisplayManagerGlobal(IDisplayManager dm) {
mDm = dm;
+ initExtraLogging();
+
try {
mWideColorSpace =
ColorSpace.get(
@@ -321,12 +333,14 @@
* If null, listener will use the handler for the current thread, and if still null,
* the handler for the main thread.
* If that is still null, a runtime exception will be thrown.
+ * @param packageName of the calling package.
*/
public void registerDisplayListener(@NonNull DisplayListener listener,
- @Nullable Handler handler, @EventsMask long eventsMask) {
+ @Nullable Handler handler, @EventsMask long eventsMask, String packageName) {
Looper looper = getLooperForHandler(handler);
Handler springBoard = new Handler(looper);
- registerDisplayListener(listener, new HandlerExecutor(springBoard), eventsMask);
+ registerDisplayListener(listener, new HandlerExecutor(springBoard), eventsMask,
+ packageName);
}
/**
@@ -334,9 +348,11 @@
*
* @param listener The listener that will be called when display changes occur.
* @param executor Executor for the thread that will be receiving the callbacks. Cannot be null.
+ * @param eventsMask Mask of events to be listened to.
+ * @param packageName of the calling package.
*/
public void registerDisplayListener(@NonNull DisplayListener listener,
- @NonNull Executor executor, @EventsMask long eventsMask) {
+ @NonNull Executor executor, @EventsMask long eventsMask, String packageName) {
if (listener == null) {
throw new IllegalArgumentException("listener must not be null");
}
@@ -345,15 +361,22 @@
throw new IllegalArgumentException("The set of events to listen to must not be empty.");
}
+ if (extraLogging()) {
+ Slog.i(TAG, "Registering Display Listener: "
+ + Long.toBinaryString(eventsMask) + ", packageName: " + packageName);
+ }
+
synchronized (mLock) {
int index = findDisplayListenerLocked(listener);
if (index < 0) {
- mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, eventsMask));
+ mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, eventsMask,
+ packageName));
registerCallbackIfNeededLocked();
} else {
mDisplayListeners.get(index).setEventsMask(eventsMask);
}
updateCallbackIfNeededLocked();
+ maybeLogAllDisplayListeners();
}
}
@@ -362,6 +385,10 @@
throw new IllegalArgumentException("listener must not be null");
}
+ if (extraLogging()) {
+ Slog.i(TAG, "Unregistering Display Listener: " + listener);
+ }
+
synchronized (mLock) {
int index = findDisplayListenerLocked(listener);
if (index >= 0) {
@@ -371,6 +398,18 @@
updateCallbackIfNeededLocked();
}
}
+ maybeLogAllDisplayListeners();
+ }
+
+ private void maybeLogAllDisplayListeners() {
+ if (!extraLogging()) {
+ return;
+ }
+
+ Slog.i(TAG, "Currently Registered Display Listeners:");
+ for (int i = 0; i < mDisplayListeners.size(); i++) {
+ Slog.i(TAG, i + ": " + mDisplayListeners.get(i));
+ }
}
private static Looper getLooperForHandler(@Nullable Handler handler) {
@@ -1148,15 +1187,20 @@
private final DisplayInfo mDisplayInfo = new DisplayInfo();
private final Executor mExecutor;
private AtomicLong mGenerationId = new AtomicLong(1);
+ private final String mPackageName;
DisplayListenerDelegate(DisplayListener listener, @NonNull Executor executor,
- @EventsMask long eventsMask) {
+ @EventsMask long eventsMask, String packageName) {
mExecutor = executor;
mListener = listener;
mEventsMask = eventsMask;
+ mPackageName = packageName;
}
public void sendDisplayEvent(int displayId, @DisplayEvent int event, DisplayInfo info) {
+ if (extraLogging()) {
+ Slog.i(TAG, "Sending Display Event: " + eventToString(event));
+ }
long generationId = mGenerationId.get();
Message msg = Message.obtain(null, event, displayId, 0, info);
mExecutor.execute(() -> {
@@ -1177,11 +1221,20 @@
}
private void handleMessage(Message msg) {
+ if (extraLogging()) {
+ Slog.i(TAG, "DLD(" + eventToString(msg.what)
+ + ", display=" + msg.arg1
+ + ", mEventsMask=" + Long.toBinaryString(mEventsMask)
+ + ", mPackageName=" + mPackageName
+ + ", msg.obj=" + msg.obj
+ + ", listener=" + mListener.getClass() + ")");
+ }
if (DEBUG) {
Trace.beginSection(
- "DisplayListenerDelegate(" + eventToString(msg.what)
+ TextUtils.trimToSize(
+ "DLD(" + eventToString(msg.what)
+ ", display=" + msg.arg1
- + ", listener=" + mListener.getClass() + ")");
+ + ", listener=" + mListener.getClass() + ")", 127));
}
switch (msg.what) {
case EVENT_DISPLAY_ADDED:
@@ -1193,6 +1246,10 @@
if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) {
DisplayInfo newInfo = (DisplayInfo) msg.obj;
if (newInfo != null && !newInfo.equals(mDisplayInfo)) {
+ if (extraLogging()) {
+ Slog.i(TAG, "Sending onDisplayChanged: Display Changed. Info: "
+ + newInfo);
+ }
mDisplayInfo.copyFrom(newInfo);
mListener.onDisplayChanged(msg.arg1);
}
@@ -1228,6 +1285,11 @@
Trace.endSection();
}
}
+
+ @Override
+ public String toString() {
+ return "mask: {" + mEventsMask + "}, for " + mListener.getClass();
+ }
}
/**
@@ -1353,4 +1415,20 @@
}
return "UNKNOWN";
}
+
+
+ private static boolean initExtraLogging() {
+ if (sCurrentPackageName == null) {
+ sCurrentPackageName = ActivityThread.currentPackageName();
+ sExtraDisplayListenerLogging = !TextUtils.isEmpty(EXTRA_LOGGING_PACKAGE_NAME)
+ && EXTRA_LOGGING_PACKAGE_NAME.equals(sCurrentPackageName);
+ }
+ // TODO: b/306170135 - return sExtraDisplayListenerLogging instead
+ return true;
+ }
+
+ private static boolean extraLogging() {
+ // TODO: b/306170135 - return sExtraDisplayListenerLogging & package name check instead
+ return true;
+ }
}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 0221296..02304b5 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -1074,6 +1074,14 @@
*/
public interface FaceDetectionCallback {
void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric);
+
+ /**
+ * An error has occurred with face detection.
+ *
+ * This callback signifies that this operation has been completed, and
+ * no more callbacks should be expected.
+ */
+ default void onDetectionError(int errorMsgId) {}
}
/**
@@ -1373,6 +1381,9 @@
} else if (mRemovalCallback != null) {
mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
getErrorString(mContext, errMsgId, vendorCode));
+ } else if (mFaceDetectionCallback != null) {
+ mFaceDetectionCallback.onDetectionError(errMsgId);
+ mFaceDetectionCallback = null;
}
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 5bfda70..935157a 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -462,6 +462,14 @@
* Invoked when a fingerprint has been detected.
*/
void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric);
+
+ /**
+ * An error has occurred with fingerprint detection.
+ *
+ * This callback signifies that this operation has been completed, and
+ * no more callbacks should be expected.
+ */
+ default void onDetectionError(int errorMsgId) {}
}
/**
@@ -1484,6 +1492,9 @@
? mRemoveTracker.mSingleFingerprint : null;
mRemovalCallback.onRemovalError(fp, clientErrMsgId,
getErrorString(mContext, errMsgId, vendorCode));
+ } else if (mFingerprintDetectionCallback != null) {
+ mFingerprintDetectionCallback.onDetectionError(errMsgId);
+ mFingerprintDetectionCallback = null;
}
}
diff --git a/core/java/android/hardware/input/InputDeviceSensorManager.java b/core/java/android/hardware/input/InputDeviceSensorManager.java
index aa55e54..05024ea 100644
--- a/core/java/android/hardware/input/InputDeviceSensorManager.java
+++ b/core/java/android/hardware/input/InputDeviceSensorManager.java
@@ -644,7 +644,7 @@
}
@Override
- protected boolean initDataInjectionImpl(boolean enable) {
+ protected boolean initDataInjectionImpl(boolean enable, int mode) {
return false;
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index ff1a6ac..7b8419e 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1373,7 +1373,7 @@
public interface InputDeviceListener {
/**
* Called whenever an input device has been added to the system.
- * Use {@link InputManagerGlobal#getInputDevice} to get more information about the device.
+ * Use {@link #getInputDevice(int)} to get more information about the device.
*
* @param deviceId The id of the input device that was added.
*/
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 582c5c0..4c2bbc1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -3231,7 +3231,7 @@
}
/**
- * Called when the user tapped or clicked an {@link android.widget.Editor}.
+ * Called when the user tapped or clicked an editor.
* This can be useful when IME makes a decision of showing Virtual keyboard based on what
* {@link MotionEvent#getToolType(int)} was used to click the editor.
* e.g. when toolType is {@link MotionEvent#TOOL_TYPE_STYLUS}, IME may choose to show a
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 8482945..e85b7bf 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1854,7 +1854,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public HistoryItem next;
- // The time of this event in milliseconds, as per SystemClock.elapsedRealtime().
+ // The time of this event in milliseconds, as per MonotonicClock.monotonicTime().
@UnsupportedAppUsage
public long time;
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 49d7e8b..32840d4 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -300,6 +300,7 @@
* @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis()
* @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis()
*/
+ // TODO(b/298459065): switch to monotonic clock
public Builder aggregateSnapshots(long fromTimestamp, long toTimestamp) {
mFromTimestamp = fromTimestamp;
mToTimestamp = toTimestamp;
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 086b0e5..58def6e 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -143,10 +143,11 @@
*/
public void onError(@BugreportErrorCode int errorCode) {}
- /** Called when taking bugreport finishes successfully.
+ /**
+ * Called when taking bugreport finishes successfully.
*
* <p>This callback will be invoked if the
- * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set.
+ * {@code BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set.
*/
public void onFinished() {}
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index 47ad72f..e8ad303 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.annotation.TestApi;
@@ -133,6 +134,7 @@
* The maximum value of supported bugreport mode.
* @hide
*/
+ @FlaggedApi(android.os.Flags.FLAG_BUGREPORT_MODE_MAX_VALUE)
@TestApi
public static final int BUGREPORT_MODE_MAX_VALUE = BUGREPORT_MODE_ONBOARDING;
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index 82cdd28..d7e440b 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -153,21 +153,21 @@
/**
* Find the intersection between this LocaleList and another
- * @return a String array of the Locales in both LocaleLists
+ * @return an array of the Locales in both LocaleLists
* {@hide}
*/
@NonNull
- public String[] getIntersection(@NonNull LocaleList other) {
- List<String> intersection = new ArrayList<>();
+ public Locale[] getIntersection(@NonNull LocaleList other) {
+ List<Locale> intersection = new ArrayList<>();
for (Locale l1 : mList) {
for (Locale l2 : other.mList) {
if (matchesLanguageAndScript(l2, l1)) {
- intersection.add(l1.toLanguageTag());
+ intersection.add(l1);
break;
}
}
}
- return intersection.toArray(new String[0]);
+ return intersection.toArray(new Locale[0]);
}
/**
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 4174c1c..fce715a 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -2670,7 +2670,7 @@
/**
* Called when overall thermal throttling status changed.
- * @param status defined in {@link android.os.Temperature}.
+ * @param status the status
*/
void onThermalStatusChanged(@ThermalStatus int status);
}
diff --git a/core/java/android/os/PowerMonitor.java b/core/java/android/os/PowerMonitor.java
index 5fb0df7..8c5f2cd 100644
--- a/core/java/android/os/PowerMonitor.java
+++ b/core/java/android/os/PowerMonitor.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -23,12 +24,19 @@
import java.lang.annotation.RetentionPolicy;
/**
- * A PowerMonitor represents either a Channel aka ODPM rail (on-device power monitor) or an
- * EnergyConsumer, as defined in
- * <a href="https://android.googlesource.com/platform/hardware/interfaces/+/refs/heads/main/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats">android.hardware.power.stats</a>
- *
- * @hide
+ * A PowerMonitor represents either an ODPM rail (on-device power rail monitor) or a modeled
+ * energy consumer.
+ * <p/>
+ * ODPM rail names are device-specific. No assumptions should be made about the names and
+ * exact purpose of ODPM rails across different device models. A rail name may be something
+ * like "S2S_VDD_G3D"; specific knowledge of the device hardware is required to interpret
+ * the corresponding power monitor data.
+ * <p/>
+ * Energy consumer have more human-readable names, e.g. "GPU", "MODEM" etc. However, developers
+ * must be extra cautious about using energy consumers across different device models,
+ * as their exact implementations are also hardware dependent and are customized by OEMs.
*/
+@FlaggedApi("com.android.server.power.optimization.power_monitor_api")
public final class PowerMonitor implements Parcelable {
/**
@@ -36,9 +44,9 @@
* power rail measurement, or modeled in some fashion. For example, an energy consumer may
* represent a combination of multiple rails or a portion of a rail shared between subsystems,
* e.g. WiFi and Bluetooth are often handled by the same chip, powered by a shared rail.
- * Some consumer names are standardized (see android.hardware.power.stats.EnergyConsumerType),
- * others are not.
+ * Some consumer names are standardized, others are not.
*/
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
public static final int POWER_MONITOR_TYPE_CONSUMER = 0;
/**
@@ -46,6 +54,7 @@
* no assumptions can be made about the source of those measurements across different devices,
* even if they have the same name.
*/
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
public static final int POWER_MONITOR_TYPE_MEASUREMENT = 1;
/** @hide */
@@ -64,38 +73,60 @@
* @hide
*/
public final int index;
+
@PowerMonitorType
- public final int type;
+ private final int mType;
@NonNull
- public final String name;
+ private final String mName;
/**
* @hide
*/
public PowerMonitor(int index, int type, @NonNull String name) {
this.index = index;
- this.type = type;
- this.name = name;
+ this.mType = type;
+ this.mName = name;
+ }
+
+ /**
+ * Returns the type of the power monitor.
+ */
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
+ @PowerMonitorType
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the name of the power monitor, either a power rail or an energy consumer.
+ */
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
+ @NonNull
+ public String getName() {
+ return mName;
}
private PowerMonitor(Parcel in) {
index = in.readInt();
- type = in.readInt();
- name = in.readString();
+ mType = in.readInt();
+ mName = in.readString8();
}
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(index);
- dest.writeInt(type);
- dest.writeString(name);
+ dest.writeInt(mType);
+ dest.writeString8(mName);
}
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
@Override
public int describeContents() {
return 0;
}
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
@NonNull
public static final Creator<PowerMonitor> CREATOR = new Creator<>() {
@Override
diff --git a/core/java/android/os/PowerMonitorReadings.java b/core/java/android/os/PowerMonitorReadings.java
index e767059..bb677d5 100644
--- a/core/java/android/os/PowerMonitorReadings.java
+++ b/core/java/android/os/PowerMonitorReadings.java
@@ -17,6 +17,7 @@
package android.os;
import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import java.util.Arrays;
@@ -24,10 +25,10 @@
/**
* A collection of energy measurements from Power Monitors.
- *
- * @hide
*/
+@FlaggedApi("com.android.server.power.optimization.power_monitor_api")
public final class PowerMonitorReadings {
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
public static final int ENERGY_UNAVAILABLE = -1;
@NonNull
@@ -41,7 +42,7 @@
Comparator.comparingInt(pm -> pm.index);
/**
- * @param powerMonitors array of power monitor (ODPM) rails, sorted by PowerMonitor.index
+ * @param powerMonitors array of power monitor, sorted by PowerMonitor.index
* @hide
*/
public PowerMonitorReadings(@NonNull PowerMonitor[] powerMonitors,
@@ -56,7 +57,8 @@
* Does not persist across reboots.
* Represents total energy: both on-battery and plugged-in.
*/
- public long getConsumedEnergyUws(@NonNull PowerMonitor powerMonitor) {
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
+ public long getConsumedEnergy(@NonNull PowerMonitor powerMonitor) {
int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);
if (offset >= 0) {
return mEnergyUws[offset];
@@ -67,6 +69,7 @@
/**
* Elapsed realtime, in milliseconds, when the snapshot was taken.
*/
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
@ElapsedRealtimeLong
public long getTimestamp(@NonNull PowerMonitor powerMonitor) {
int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);
@@ -84,7 +87,7 @@
if (i != 0) {
sb.append(", ");
}
- sb.append(mPowerMonitors[i].name)
+ sb.append(mPowerMonitors[i].getName())
.append(" = ").append(mEnergyUws[i])
.append(" (").append(mTimestampsMs[i]).append(')');
}
diff --git a/core/java/android/os/RemoteException.java b/core/java/android/os/RemoteException.java
index 970f419..ace5f7e 100644
--- a/core/java/android/os/RemoteException.java
+++ b/core/java/android/os/RemoteException.java
@@ -66,7 +66,7 @@
/**
* Rethrow this exception when we know it came from the system server. This
* gives us an opportunity to throw a nice clean
- * {@link DeadSystemRuntimeException} signal to avoid spamming logs with
+ * {@code DeadSystemRuntimeException} signal to avoid spamming logs with
* misleading stack traces.
* <p>
* Apps making calls into the system server may end up persisting internal
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 98f9dff..5078dc35 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -140,6 +140,31 @@
*/
public static final int USAGE_MEDIA = 0x10 | USAGE_CLASS_MEDIA;
+ /** @hide */
+ @IntDef(prefix = { "CATEGORY_" }, value = {
+ CATEGORY_UNKNOWN,
+ CATEGORY_KEYBOARD,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Category {}
+
+ /**
+ * Category value when the vibration category is unknown.
+ *
+ * @hide
+ */
+ public static final int CATEGORY_UNKNOWN = 0x0;
+
+ /**
+ * Category value for keyboard vibrations.
+ *
+ * <p>Most typical keyboard vibrations are haptic feedback for virtual keyboard key
+ * press/release, for example.
+ *
+ * @hide
+ */
+ public static final int CATEGORY_KEYBOARD = 1;
+
/**
* @hide
*/
@@ -147,7 +172,8 @@
FLAG_BYPASS_INTERRUPTION_POLICY,
FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF,
FLAG_INVALIDATE_SETTINGS_CACHE,
- FLAG_PIPELINED_EFFECT
+ FLAG_PIPELINED_EFFECT,
+ FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE
})
@Retention(RetentionPolicy.SOURCE)
public @interface Flag{}
@@ -167,6 +193,8 @@
* {@link android.view.HapticFeedbackConstants#FLAG_IGNORE_GLOBAL_SETTING} and
* {@link AudioAttributes#FLAG_BYPASS_MUTE}.
*
+ * <p>Only privileged apps can ignore user settings, and this flag will be ignored otherwise.
+ *
* @hide
*/
public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF = 1 << 1;
@@ -199,12 +227,31 @@
public static final int FLAG_PIPELINED_EFFECT = 1 << 3;
/**
+ * Flag requesting that this vibration effect to be played without applying the user
+ * intensity setting to scale the vibration.
+ *
+ * <p>The user setting is still applied to enable/disable the vibration, but the vibration
+ * effect strength will not be scaled based on the enabled setting value.
+ *
+ * <p>This is intended to be used on scenarios where the system needs to enforce a specific
+ * strength for the vibration effect, regardless of the user preference. Only privileged apps
+ * can ignore user settings, and this flag will be ignored otherwise.
+ *
+ * <p>If you need to bypass the user setting when it's disabling vibrations then this also
+ * needs the flag {@link #FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF} to be set.
+ *
+ * @hide
+ */
+ public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE = 1 << 4;
+
+ /**
* All flags supported by vibrator service, update it when adding new flag.
* @hide
*/
public static final int FLAG_ALL_SUPPORTED =
FLAG_BYPASS_INTERRUPTION_POLICY | FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF
- | FLAG_INVALIDATE_SETTINGS_CACHE | FLAG_PIPELINED_EFFECT;
+ | FLAG_INVALIDATE_SETTINGS_CACHE | FLAG_PIPELINED_EFFECT
+ | FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
/** Creates a new {@link VibrationAttributes} instance with given usage. */
public static @NonNull VibrationAttributes createForUsage(@Usage int usage) {
@@ -214,12 +261,14 @@
private final int mUsage;
private final int mFlags;
private final int mOriginalAudioUsage;
+ private final int mCategory;
private VibrationAttributes(@Usage int usage, @AudioAttributes.AttributeUsage int audioUsage,
- @Flag int flags) {
+ @Flag int flags, @Category int category) {
mUsage = usage;
mOriginalAudioUsage = audioUsage;
mFlags = flags & FLAG_ALL_SUPPORTED;
+ mCategory = category;
}
/**
@@ -248,6 +297,20 @@
}
/**
+ * Return the vibration category.
+ *
+ * <p>Vibration categories describe the source of the vibration, and it can be combined with
+ * the vibration usage to best match to a user setting, e.g. a vibration with usage touch and
+ * category keyboard can be used to control keyboard haptic feedback independently.
+ *
+ * @hide
+ */
+ @Category
+ public int getCategory() {
+ return mCategory;
+ }
+
+ /**
* Check whether a flag is set
* @return true if a flag is set and false otherwise
*/
@@ -298,12 +361,14 @@
dest.writeInt(mUsage);
dest.writeInt(mOriginalAudioUsage);
dest.writeInt(mFlags);
+ dest.writeInt(mCategory);
}
private VibrationAttributes(Parcel src) {
mUsage = src.readInt();
mOriginalAudioUsage = src.readInt();
mFlags = src.readInt();
+ mCategory = src.readInt();
}
public static final @NonNull Parcelable.Creator<VibrationAttributes>
@@ -326,12 +391,12 @@
}
VibrationAttributes rhs = (VibrationAttributes) o;
return mUsage == rhs.mUsage && mOriginalAudioUsage == rhs.mOriginalAudioUsage
- && mFlags == rhs.mFlags;
+ && mFlags == rhs.mFlags && mCategory == rhs.mCategory;
}
@Override
public int hashCode() {
- return Objects.hash(mUsage, mOriginalAudioUsage, mFlags);
+ return Objects.hash(mUsage, mOriginalAudioUsage, mFlags, mCategory);
}
@Override
@@ -340,6 +405,7 @@
+ "mUsage=" + usageToString()
+ ", mAudioUsage= " + AudioAttributes.usageToString(mOriginalAudioUsage)
+ ", mFlags=" + mFlags
+ + ", mCategory=" + categoryToString()
+ '}';
}
@@ -376,6 +442,23 @@
}
}
+ /** @hide */
+ public String categoryToString() {
+ return categoryToString(mCategory);
+ }
+
+ /** @hide */
+ public static String categoryToString(@Category int category) {
+ switch (category) {
+ case CATEGORY_UNKNOWN:
+ return "UNKNOWN";
+ case CATEGORY_KEYBOARD:
+ return "KEYBOARD";
+ default:
+ return "unknown category " + category;
+ }
+ }
+
/**
* Builder class for {@link VibrationAttributes} objects.
* By default, all information is set to UNKNOWN.
@@ -384,6 +467,7 @@
private int mUsage = USAGE_UNKNOWN;
private int mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN;
private int mFlags = 0x0;
+ private int mCategory = CATEGORY_UNKNOWN;
/**
* Constructs a new Builder with the defaults.
@@ -399,6 +483,7 @@
mUsage = vib.mUsage;
mOriginalAudioUsage = vib.mOriginalAudioUsage;
mFlags = vib.mFlags;
+ mCategory = vib.mCategory;
}
}
@@ -464,7 +549,8 @@
* @return a new {@link VibrationAttributes} object
*/
public @NonNull VibrationAttributes build() {
- VibrationAttributes ans = new VibrationAttributes(mUsage, mOriginalAudioUsage, mFlags);
+ VibrationAttributes ans = new VibrationAttributes(
+ mUsage, mOriginalAudioUsage, mFlags, mCategory);
return ans;
}
@@ -480,6 +566,19 @@
}
/**
+ * Sets the attribute describing the category of the corresponding vibration.
+ *
+ * @param category The category for the vibration
+ * @return the same Builder instance.
+ *
+ * @hide
+ */
+ public @NonNull Builder setCategory(@Category int category) {
+ mCategory = category;
+ return this;
+ }
+
+ /**
* Sets only the flags specified in the bitmask, leaving the other supported flag values
* unchanged in the builder.
*
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 99c9925..2fc2414 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -184,6 +184,16 @@
}
/**
+ * Whether the keyboard vibration is enabled by default.
+ *
+ * @return {@code true} if the keyboard vibration is default enabled, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isDefaultKeyboardVibrationEnabled() {
+ return getConfig().isDefaultKeyboardVibrationEnabled();
+ }
+
+ /**
* Return the ID of this vibrator.
*
* @return A non-negative integer representing the id of the vibrator controlled by this
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 37559b3..c4521c0 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -16,7 +16,7 @@
flag {
name: "remove_app_profiler_pss_collection"
- namespace: "android_platform_power_optimization"
+ namespace: "backstage_power"
description: "Replaces background PSS collection in AppProfiler with RSS"
bug: "297542292"
}
@@ -27,3 +27,10 @@
description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion."
bug: "299069460"
}
+
+flag {
+ name: "bugreport_mode_max_value"
+ namespace: "telephony"
+ description: "Introduce a constant as maximum value of bugreport mode."
+ bug: "305067125"
+}
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index dfc43f4..2d53341 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -16,6 +16,7 @@
package android.os.health;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
@@ -24,7 +25,6 @@
import android.os.BatteryStats;
import android.os.Build;
import android.os.Bundle;
-import android.os.ConditionVariable;
import android.os.Handler;
import android.os.IPowerStatsService;
import android.os.PowerMonitor;
@@ -157,39 +157,15 @@
}
/**
- * Returns a list of supported power monitors, which include raw ODPM rails and
- * modeled energy consumers. If ODPM is unsupported by PowerStats HAL, this method returns
- * an empty array.
+ * Asynchronously retrieves a list of supported {@link PowerMonitor}'s, which include raw ODPM
+ * (on-device power rail monitor) rails and modeled energy consumers. If ODPM is unsupported
+ * on this device this method delivers an empty list.
*
- * @hide
- */
- @NonNull
- public List<PowerMonitor> getSupportedPowerMonitors() {
- synchronized (mPowerMonitorsLock) {
- if (mPowerMonitorsInfo != null) {
- return mPowerMonitorsInfo;
- }
- }
- ConditionVariable lock = new ConditionVariable();
- // Populate mPowerMonitorsInfo by side-effect
- getSupportedPowerMonitors(null, unused -> lock.open());
- lock.block();
-
- synchronized (mPowerMonitorsLock) {
- return mPowerMonitorsInfo;
- }
- }
-
- /**
- * Asynchronously retrieves a list of supported power monitors, see
- * {@link #getSupportedPowerMonitors()}
- *
- * @param handler optional Handler to deliver the callback. If not supplied, the callback
- * may be invoked on an arbitrary thread.
+ * @param handler optional Handler to deliver the callback. If not supplied, the callback
+ * may be invoked on an arbitrary thread.
* @param onResult callback for the result
- *
- * @hide
*/
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
public void getSupportedPowerMonitors(@Nullable Handler handler,
@NonNull Consumer<List<PowerMonitor>> onResult) {
final List<PowerMonitor> result;
@@ -229,35 +205,6 @@
}
}
- /**
- * Retrieves the accumulated power consumption reported by the specified power monitors.
- *
- * @param powerMonitors power monitors to be returned.
- *
- * @hide
- */
- @NonNull
- public PowerMonitorReadings getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors) {
- PowerMonitorReadings[] outReadings = new PowerMonitorReadings[1];
- RuntimeException[] outException = new RuntimeException[1];
- ConditionVariable lock = new ConditionVariable();
- getPowerMonitorReadings(powerMonitors, null,
- pms -> {
- outReadings[0] = pms;
- lock.open();
- },
- error -> {
- outException[0] = error;
- lock.open();
- }
- );
- lock.block();
- if (outException[0] != null) {
- throw outException[0];
- }
- return outReadings[0];
- }
-
private static final Comparator<PowerMonitor> POWER_MONITOR_COMPARATOR =
Comparator.comparingInt(pm -> pm.index);
@@ -270,9 +217,8 @@
* may be invoked on an arbitrary thread.
* @param onSuccess callback for the result
* @param onError callback invoked in case of an error
- *
- * @hide
*/
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
public void getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors,
@Nullable Handler handler, @NonNull Consumer<PowerMonitorReadings> onSuccess,
@NonNull Consumer<RuntimeException> onError) {
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index bde334a..92e4967 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -65,6 +65,8 @@
@VibrationIntensity
private final int mDefaultRingVibrationIntensity;
+ private final boolean mDefaultKeyboardVibrationEnabled;
+
/** @hide */
public VibrationConfig(@Nullable Resources resources) {
mHapticChannelMaxVibrationAmplitude = loadFloat(resources,
@@ -76,6 +78,8 @@
mIgnoreVibrationsOnWirelessCharger = loadBoolean(resources,
com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false);
+ mDefaultKeyboardVibrationEnabled = loadBoolean(resources,
+ com.android.internal.R.bool.config_defaultKeyboardVibrationEnabled, true);
mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources,
com.android.internal.R.integer.config_defaultAlarmVibrationIntensity);
@@ -157,6 +161,14 @@
return mIgnoreVibrationsOnWirelessCharger;
}
+ /**
+ * Whether keyboard vibration settings is enabled by default.
+ * @hide
+ */
+ public boolean isDefaultKeyboardVibrationEnabled() {
+ return mDefaultKeyboardVibrationEnabled;
+ }
+
/** Get the default vibration intensity for given usage. */
@VibrationIntensity
public int getDefaultVibrationIntensity(@VibrationAttributes.Usage int usage) {
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 88f62f3..69d86a6 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -27,3 +27,20 @@
description: "Enables the APIs for vibration serialization/deserialization."
bug: "245129509"
}
+
+flag {
+ namespace: "haptics"
+ name: "haptic_feedback_vibration_oem_customization_enabled"
+ description: "Enables OEMs/devices to customize vibrations for haptic feedback"
+ # Make read only. This is because the flag is used only once, and this could happen before
+ # the read-write flag values propagate to the device.
+ is_fixed_read_only: true
+ bug: "291128479"
+}
+
+flag {
+ namespace: "haptics"
+ name: "keyboard_category_enabled"
+ description: "Enables the independent keyboard vibration settings feature"
+ bug: "289107579"
+}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index d8534dd..3f06a91 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -21,3 +21,17 @@
description: "enable role controller in system server"
bug: "302562590"
}
+
+flag {
+ name: "set_next_attribution_source"
+ namespace: "permissions"
+ description: "enable AttributionSource.setNextAttributionSource"
+ bug: "304478648"
+}
+
+flag {
+ name: "should_register_attribution_source"
+ namespace: "permissions"
+ description: "enable the shouldRegisterAttributionSource API"
+ bug: "305057691"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b19a034..f0906b1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1800,7 +1800,6 @@
* Input: Nothing.
* <p>
* Output: Nothing.
- * @see android.service.notification.NotificationAssistantService
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_NOTIFICATION_ASSISTANT_SETTINGS =
@@ -2507,8 +2506,8 @@
* to the caller package.
* <p>
* <b>NOTE: </b> Applications should call
- * {@link android.credentials.CredentialManager#isEnabledCredentialProviderService()}
- * and only use this action to start an activity if they return {@code false}.
+ * {@link android.credentials.CredentialManager#isEnabledCredentialProviderService(
+ * ComponentName)} and only use this action to start an activity if they return {@code false}.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CREDENTIAL_PROVIDER =
@@ -5130,6 +5129,14 @@
"hardware_haptic_feedback_intensity";
/**
+ * Whether keyboard vibration feedback is enabled. The value is boolean (1 or 0).
+ *
+ * @hide
+ */
+ @Readable
+ public static final String KEYBOARD_VIBRATION_ENABLED = "keyboard_vibration_enabled";
+
+ /**
* Ringer volume. This is used internally, changing this value will not
* change the volume. See AudioManager.
*
@@ -12375,7 +12382,7 @@
* Value to specify if the device's UTC system clock should be set automatically, e.g. using
* telephony signals like NITZ, or other sources like GNSS or NTP.
*
- * <p>Prefer {@link android.app.time.TimeManager} API calls to determine the state of
+ * <p>Prefer {@code android.app.time.TimeManager} API calls to determine the state of
* automatic time detection instead of directly observing this setting as it may be ignored
* by the time_detector service under various conditions.
*
@@ -12388,7 +12395,7 @@
* Value to specify if the device's time zone system property should be set automatically,
* e.g. using telephony signals like MCC and NITZ, or other mechanisms like the location.
*
- * <p>Prefer {@link android.app.time.TimeManager} API calls to determine the state of
+ * <p>Prefer {@code android.app.time.TimeManager} API calls to determine the state of
* automatic time zone detection instead of directly observing this setting as it may be
* ignored by the time_zone_detector service under various conditions.
*
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 3fb94ee..7ea74d3 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -406,7 +406,7 @@
*
* Call this when a field has been detected with a type.
*
- * Altough similiarly named with {@link setFieldClassificationIds},
+ * Altough similiarly named with {@link #setFieldClassificationIds},
* it provides a different functionality - setFieldClassificationIds should
* be used when a field is only suspected to be Autofillable.
* This method should be used when a field is certainly Autofillable
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 954cc96..53706cd3 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -813,7 +813,7 @@
* If no {@link #Builder(int, AutofillId[]) required ids},
* or {@link #setOptionalIds(AutofillId[]) optional ids}, or {@link #FLAG_DELAY_SAVE}
* were set, Save Dialog will only be triggered if platform detection is enabled, which
- * is indicated when {@link FillRequest.getHints()} is not empty.
+ * is indicated when {@link FillRequest#getHints()} is not empty.
*/
public SaveInfo build() {
throwIfDestroyed();
diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
index df93433..5d96f69 100644
--- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
@@ -143,7 +143,7 @@
*
* <p> Note that as a provider service you will only be able to set a remote entry if :
* - Provider service possesses the
- * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission.
+ * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission.
* - Provider service is configured as the provider that can provide remote entries.
*
* If the above conditions are not met, setting back {@link BeginCreateCredentialResponse}
diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java
index 5ed06ac..ae6ca25 100644
--- a/core/java/android/service/credentials/BeginGetCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java
@@ -160,7 +160,7 @@
*
* <p> Note that as a provider service you will only be able to set a remote entry if :
* - Provider service possesses the
- * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission.
+ * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission.
* - Provider service is configured as the provider that can provide remote entries.
*
* If the above conditions are not met, setting back {@link BeginGetCredentialResponse}
diff --git a/core/java/android/service/credentials/CallingAppInfo.java b/core/java/android/service/credentials/CallingAppInfo.java
index e755581..c3524c5 100644
--- a/core/java/android/service/credentials/CallingAppInfo.java
+++ b/core/java/android/service/credentials/CallingAppInfo.java
@@ -103,7 +103,7 @@
* of other applications.
*
* Android system makes sure that only applications that poses the permission
- * {@link android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on
+ * {@link android.Manifest.permission#CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on
* the incoming {@link android.credentials.GetCredentialRequest} or
* {@link android.credentials.CreateCredentialRequest}.
*/
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 1cfff14..759953e 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -896,8 +896,7 @@
* <p>This method will throw a security exception if you don't have access to notifications
* for the given user.</p>
* <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
- * device} or be the {@link NotificationAssistantService notification assistant} in order to
- * use this method.
+ * device} or be the notification assistant in order to use this method.
*
* @param pkg The package to retrieve channels for.
*/
@@ -920,8 +919,7 @@
* <p>This method will throw a security exception if you don't have access to notifications
* for the given user.</p>
* <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
- * device} or be the {@link NotificationAssistantService notification assistant} in order to
- * use this method.
+ * device} or be the notification assistant in order to use this method.
*
* @param pkg The package to retrieve channel groups for.
*/
@@ -2001,7 +1999,7 @@
/**
* Returns a list of smart {@link Notification.Action} that can be added by the
- * {@link NotificationAssistantService}
+ * notification assistant.
*/
public @NonNull List<Notification.Action> getSmartActions() {
return mSmartActions == null ? Collections.emptyList() : mSmartActions;
@@ -2022,8 +2020,7 @@
}
/**
- * Returns a list of smart replies that can be added by the
- * {@link NotificationAssistantService}
+ * Returns a list of smart replies that can be added by the notification assistant.
*/
public @NonNull List<CharSequence> getSmartReplies() {
return mSmartReplies == null ? Collections.emptyList() : mSmartReplies;
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index c82a4ca..2a4cbaf 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -28,8 +28,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -59,8 +57,7 @@
* @hide
*/
public NotificationRankingUpdate(Parcel in) {
- if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+ if (Flags.rankingUpdateAshmem()) {
// Recover the ranking map from the SharedMemory and store it in mapParcel.
final Parcel mapParcel = Parcel.obtain();
ByteBuffer buffer = null;
@@ -176,8 +173,7 @@
*/
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
- if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+ if (Flags.rankingUpdateAshmem()) {
final Parcel mapParcel = Parcel.obtain();
ArrayList<NotificationListenerService.Ranking> marshalableRankings = new ArrayList<>();
Bundle smartActionsBundle = new Bundle();
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
new file mode 100644
index 0000000..2931435
--- /dev/null
+++ b/core/java/android/service/notification/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.service.notification"
+
+flag {
+ name: "ranking_update_ashmem"
+ namespace: "systemui"
+ description: "This flag controls moving ranking update contents into ashmem"
+ bug: "284297289"
+}
+
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index 4a4fd04..bf129c5 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -372,9 +372,9 @@
}
/**
- * Set of locations this card might be useful at. If {@link
- * PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is enabled, the card might be
- * shown to the user when a user is near one of these locations.
+ * Set of locations this card might be useful at. If
+ * {@link android.content.pm.PackageManager#FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is
+ * enabled, the card might be shown to the user when a user is near one of these locations.
*/
@NonNull
public Builder setCardLocations(@NonNull List<Location> cardLocations) {
diff --git a/core/java/android/service/rotationresolver/OWNERS b/core/java/android/service/rotationresolver/OWNERS
index 5b57fc7..dce874d 100644
--- a/core/java/android/service/rotationresolver/OWNERS
+++ b/core/java/android/service/rotationresolver/OWNERS
@@ -1,9 +1,7 @@
# Bug component: 814982
asalo@google.com
-augale@google.com
eejiang@google.com
payamp@google.com
siddikap@google.com
-svetoslavganov@google.com
tgadh@google.com
diff --git a/core/java/android/service/voice/AbstractDetector.java b/core/java/android/service/voice/AbstractDetector.java
index 7af7fe6..db97d4f 100644
--- a/core/java/android/service/voice/AbstractDetector.java
+++ b/core/java/android/service/voice/AbstractDetector.java
@@ -199,8 +199,12 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
+ Consumer<AbstractDetector> onDestroyListener;
synchronized (mLock) {
- mOnDestroyListener.accept(this);
+ onDestroyListener = mOnDestroyListener;
+ }
+ if (onDestroyListener != null) {
+ onDestroyListener.accept(this);
}
}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 3f41c56..d280621 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -520,7 +520,7 @@
@NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,
@NonNull @CallbackExecutor Executor executor,
@NonNull AlwaysOnHotwordDetector.Callback callback) {
- // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
+ // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
Objects.requireNonNull(keyphrase);
Objects.requireNonNull(locale);
@@ -546,6 +546,10 @@
@NonNull SoundTrigger.ModuleProperties moduleProperties,
@NonNull @CallbackExecutor Executor executor,
@NonNull AlwaysOnHotwordDetector.Callback callback) {
+ // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+ // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale,
+ // SoundTrigger.ModuleProperties, AlwaysOnHotwordDetector.Callback)} and replace with the
+ // permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
Objects.requireNonNull(keyphrase);
Objects.requireNonNull(locale);
@@ -612,6 +616,11 @@
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
@SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
+ // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+ // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
+ // AlwaysOnHotwordDetector.Callback)} and replace with the permission
+ // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
+
return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
/* supportHotwordDetectionService= */ true, options, sharedMemory,
/* modulProperties */ null, /* executor= */ null, callback);
@@ -663,7 +672,11 @@
@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,
@NonNull @CallbackExecutor Executor executor,
@NonNull AlwaysOnHotwordDetector.Callback callback) {
- // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
+ // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
+ // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+ // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
+ // Executor, AlwaysOnHotwordDetector.Callback)} and replace with the permission
+ // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
Objects.requireNonNull(keyphrase);
Objects.requireNonNull(locale);
@@ -690,6 +703,10 @@
@NonNull SoundTrigger.ModuleProperties moduleProperties,
@NonNull @CallbackExecutor Executor executor,
@NonNull AlwaysOnHotwordDetector.Callback callback) {
+ // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+ // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale, PersistableBundle,
+ // SharedMemory, SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)}
+ // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
Objects.requireNonNull(keyphrase);
Objects.requireNonNull(locale);
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index cabcae3..d40b39e 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -1809,7 +1809,7 @@
* in milliseconds of the KeyEvent that triggered Assistant and
* Intent.EXTRA_ASSIST_INPUT_DEVICE_ID (android.intent.extra.ASSIST_INPUT_DEVICE_ID)
* referring to the device that sent the request. Starting from Android 14, the system will
- * add {@link VoiceInteractionService#KEY_SHOW_SESSION_ID}, the Bundle is not null. But the
+ * add {@link #KEY_SHOW_SESSION_ID}, the Bundle is not null. But the
* application should handle null case before Android 14.
* @param showFlags The show flags originally provided to
* {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index 3ed13bb..35834fd 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -767,7 +767,7 @@
try {
mService.startListening(recognizerIntent, mListener, mContext.getAttributionSource());
if (DBG) Log.d(TAG, "service start listening command succeeded");
- } catch (final RemoteException e) {
+ } catch (final Exception e) {
Log.e(TAG, "startListening() failed", e);
mListener.onError(ERROR_CLIENT);
}
@@ -781,7 +781,7 @@
try {
mService.stopListening(mListener);
if (DBG) Log.d(TAG, "service stop listening command succeeded");
- } catch (final RemoteException e) {
+ } catch (final Exception e) {
Log.e(TAG, "stopListening() failed", e);
mListener.onError(ERROR_CLIENT);
}
@@ -795,7 +795,7 @@
try {
mService.cancel(mListener, /*isShutdown*/ false);
if (DBG) Log.d(TAG, "service cancel command succeeded");
- } catch (final RemoteException e) {
+ } catch (final Exception e) {
Log.e(TAG, "cancel() failed", e);
mListener.onError(ERROR_CLIENT);
}
@@ -830,7 +830,7 @@
mContext.getAttributionSource(),
new InternalSupportCallback(callbackExecutor, recognitionSupportCallback));
if (DBG) Log.d(TAG, "service support command succeeded");
- } catch (final RemoteException e) {
+ } catch (final Exception e) {
Log.e(TAG, "checkRecognitionSupport() failed", e);
callbackExecutor.execute(() -> recognitionSupportCallback.onError(ERROR_CLIENT));
}
@@ -850,7 +850,7 @@
mService.triggerModelDownload(
recognizerIntent, mContext.getAttributionSource(), null);
if (DBG) Log.d(TAG, "triggerModelDownload() without a listener");
- } catch (final RemoteException e) {
+ } catch (final Exception e) {
Log.e(TAG, "triggerModelDownload() without a listener failed", e);
mListener.onError(ERROR_CLIENT);
}
@@ -862,7 +862,7 @@
recognizerIntent, mContext.getAttributionSource(),
new InternalModelDownloadListener(callbackExecutor, modelDownloadListener));
if (DBG) Log.d(TAG, "triggerModelDownload() with a listener");
- } catch (final RemoteException e) {
+ } catch (final Exception e) {
Log.e(TAG, "triggerModelDownload() with a listener failed", e);
callbackExecutor.execute(() -> modelDownloadListener.onError(ERROR_CLIENT));
}
@@ -889,7 +889,7 @@
if (mService != null) {
try {
mService.cancel(mListener, /*isShutdown*/ true);
- } catch (final RemoteException e) {
+ } catch (final Exception e) {
// Not important
}
}
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 8862f1d..a0cd074 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -1274,7 +1274,7 @@
}
/**
- * Gets the {@link LineBreakconfig} used in this DynamicLayout.
+ * Gets the {@link LineBreakConfig} used in this DynamicLayout.
* Use this only to consult the LineBreakConfig's properties and not
* to change them.
*
diff --git a/core/java/android/text/WordSegmentFinder.java b/core/java/android/text/WordSegmentFinder.java
index be002f3..b0a70ea 100644
--- a/core/java/android/text/WordSegmentFinder.java
+++ b/core/java/android/text/WordSegmentFinder.java
@@ -24,7 +24,7 @@
/**
* Implementation of {@link SegmentFinder} using words as the text segment. Word boundaries are
- * found using {@link WordIterator}. Whitespace characters are excluded, so they are not included in
+ * found using {@code WordIterator}. Whitespace characters are excluded, so they are not included in
* any text segments.
*
* <p>For example, the text "Hello, World!" would be subdivided into four text segments: "Hello",
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 2906d86..766e924 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -200,6 +200,12 @@
public static final String SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION =
"settings_remote_device_credential_validation";
+ /**
+ * Flag to enable/disable to start treating any calls to "suspend" an app as "quarantine".
+ * @hide
+ */
+ public static final String SETTINGS_TREAT_PAUSE_AS_QUARANTINE =
+ "settings_treat_pause_as_quarantine";
private static final Map<String, String> DEFAULT_FLAGS;
@@ -247,6 +253,7 @@
DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
// TODO: b/298454866 Replace with Trunk Stable Feature Flag
DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false");
+ DEFAULT_FLAGS.put(SETTINGS_TREAT_PAUSE_AS_QUARANTINE, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
@@ -264,6 +271,7 @@
PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA);
PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA_PHASE2);
PERSISTENT_FLAGS.add(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM);
+ PERSISTENT_FLAGS.add(SETTINGS_TREAT_PAUSE_AS_QUARANTINE);
}
/**
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index 1ed5d3f..71d382e 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -92,6 +92,12 @@
* SurfaceView Surface, the buffer producer will already have access to the transform hint and
* no additional work is needed.
*
+ * If the root surface is not available, the API will return {@code BUFFER_TRANSFORM_IDENTITY}.
+ * The caller should register a listener to listen for any changes. @see
+ * {@link #addOnBufferTransformHintChangedListener(OnBufferTransformHintChangedListener)}.
+ * Warning: Calling this API in Android 14 (API Level 34) or earlier will crash if the root
+ * surface is not available.
+ *
* @see HardwareBuffer
*/
default @SurfaceControl.BufferTransform int getBufferTransformHint() {
diff --git a/core/java/android/view/ContentRecordingSession.java b/core/java/android/view/ContentRecordingSession.java
index a89f795..dc41b70 100644
--- a/core/java/android/view/ContentRecordingSession.java
+++ b/core/java/android/view/ContentRecordingSession.java
@@ -52,6 +52,12 @@
*/
public static final int RECORD_CONTENT_TASK = 1;
+ /** Full screen sharing (app is not selected). */
+ public static final int TARGET_UID_FULL_SCREEN = -1;
+
+ /** Can't report (e.g. side loaded app). */
+ public static final int TARGET_UID_UNKNOWN = -2;
+
/**
* Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
* recorded content rendered to its surface.
@@ -89,27 +95,36 @@
*/
private boolean mWaitingForConsent = false;
+ /** UID of the package that is captured if selected. */
+ private int mTargetUid = TARGET_UID_UNKNOWN;
+
/**
* Default instance, with recording the display.
*/
private ContentRecordingSession() {
}
- /**
- * Returns an instance initialized for recording the indicated display.
- */
+ /** Returns an instance initialized for recording the indicated display. */
public static ContentRecordingSession createDisplaySession(int displayToMirror) {
- return new ContentRecordingSession().setDisplayToRecord(displayToMirror)
- .setContentToRecord(RECORD_CONTENT_DISPLAY);
+ return new ContentRecordingSession()
+ .setDisplayToRecord(displayToMirror)
+ .setContentToRecord(RECORD_CONTENT_DISPLAY)
+ .setTargetUid(TARGET_UID_FULL_SCREEN);
}
- /**
- * Returns an instance initialized for task recording.
- */
+ /** Returns an instance initialized for task recording. */
public static ContentRecordingSession createTaskSession(
@NonNull IBinder taskWindowContainerToken) {
- return new ContentRecordingSession().setContentToRecord(RECORD_CONTENT_TASK)
- .setTokenToRecord(taskWindowContainerToken);
+ return createTaskSession(taskWindowContainerToken, TARGET_UID_UNKNOWN);
+ }
+
+ /** Returns an instance initialized for task recording. */
+ public static ContentRecordingSession createTaskSession(
+ @NonNull IBinder taskWindowContainerToken, int targetUid) {
+ return new ContentRecordingSession()
+ .setContentToRecord(RECORD_CONTENT_TASK)
+ .setTokenToRecord(taskWindowContainerToken)
+ .setTargetUid(targetUid);
}
/**
@@ -175,13 +190,33 @@
}
}
+ @IntDef(prefix = "TARGET_UID_", value = {
+ TARGET_UID_FULL_SCREEN,
+ TARGET_UID_UNKNOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface TargetUid {}
+
+ @DataClass.Generated.Member
+ public static String targetUidToString(@TargetUid int value) {
+ switch (value) {
+ case TARGET_UID_FULL_SCREEN:
+ return "TARGET_UID_FULL_SCREEN";
+ case TARGET_UID_UNKNOWN:
+ return "TARGET_UID_UNKNOWN";
+ default: return Integer.toHexString(value);
+ }
+ }
+
@DataClass.Generated.Member
/* package-private */ ContentRecordingSession(
int virtualDisplayId,
@RecordContent int contentToRecord,
int displayToRecord,
@Nullable IBinder tokenToRecord,
- boolean waitingForConsent) {
+ boolean waitingForConsent,
+ int targetUid) {
this.mVirtualDisplayId = virtualDisplayId;
this.mContentToRecord = contentToRecord;
@@ -196,6 +231,7 @@
this.mDisplayToRecord = displayToRecord;
this.mTokenToRecord = tokenToRecord;
this.mWaitingForConsent = waitingForConsent;
+ this.mTargetUid = targetUid;
// onConstructed(); // You can define this method to get a callback
}
@@ -251,6 +287,14 @@
}
/**
+ * UID of the package that is captured if selected.
+ */
+ @DataClass.Generated.Member
+ public int getTargetUid() {
+ return mTargetUid;
+ }
+
+ /**
* Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
* recorded content rendered to its surface.
*/
@@ -314,6 +358,15 @@
return this;
}
+ /**
+ * UID of the package that is captured if selected.
+ */
+ @DataClass.Generated.Member
+ public @NonNull ContentRecordingSession setTargetUid( int value) {
+ mTargetUid = value;
+ return this;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -325,7 +378,8 @@
"contentToRecord = " + recordContentToString(mContentToRecord) + ", " +
"displayToRecord = " + mDisplayToRecord + ", " +
"tokenToRecord = " + mTokenToRecord + ", " +
- "waitingForConsent = " + mWaitingForConsent +
+ "waitingForConsent = " + mWaitingForConsent + ", " +
+ "targetUid = " + mTargetUid +
" }";
}
@@ -346,7 +400,8 @@
&& mContentToRecord == that.mContentToRecord
&& mDisplayToRecord == that.mDisplayToRecord
&& java.util.Objects.equals(mTokenToRecord, that.mTokenToRecord)
- && mWaitingForConsent == that.mWaitingForConsent;
+ && mWaitingForConsent == that.mWaitingForConsent
+ && mTargetUid == that.mTargetUid;
}
@Override
@@ -361,6 +416,7 @@
_hash = 31 * _hash + mDisplayToRecord;
_hash = 31 * _hash + java.util.Objects.hashCode(mTokenToRecord);
_hash = 31 * _hash + Boolean.hashCode(mWaitingForConsent);
+ _hash = 31 * _hash + mTargetUid;
return _hash;
}
@@ -378,6 +434,7 @@
dest.writeInt(mContentToRecord);
dest.writeInt(mDisplayToRecord);
if (mTokenToRecord != null) dest.writeStrongBinder(mTokenToRecord);
+ dest.writeInt(mTargetUid);
}
@Override
@@ -397,6 +454,7 @@
int contentToRecord = in.readInt();
int displayToRecord = in.readInt();
IBinder tokenToRecord = (flg & 0x8) == 0 ? null : (IBinder) in.readStrongBinder();
+ int targetUid = in.readInt();
this.mVirtualDisplayId = virtualDisplayId;
this.mContentToRecord = contentToRecord;
@@ -412,6 +470,7 @@
this.mDisplayToRecord = displayToRecord;
this.mTokenToRecord = tokenToRecord;
this.mWaitingForConsent = waitingForConsent;
+ this.mTargetUid = targetUid;
// onConstructed(); // You can define this method to get a callback
}
@@ -442,6 +501,7 @@
private int mDisplayToRecord;
private @Nullable IBinder mTokenToRecord;
private boolean mWaitingForConsent;
+ private int mTargetUid;
private long mBuilderFieldsSet = 0L;
@@ -513,10 +573,21 @@
return this;
}
+ /**
+ * UID of the package that is captured if selected.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTargetUid(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20;
+ mTargetUid = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull ContentRecordingSession build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x20; // Mark builder used
+ mBuilderFieldsSet |= 0x40; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mVirtualDisplayId = INVALID_DISPLAY;
@@ -533,17 +604,21 @@
if ((mBuilderFieldsSet & 0x10) == 0) {
mWaitingForConsent = false;
}
+ if ((mBuilderFieldsSet & 0x20) == 0) {
+ mTargetUid = TARGET_UID_UNKNOWN;
+ }
ContentRecordingSession o = new ContentRecordingSession(
mVirtualDisplayId,
mContentToRecord,
mDisplayToRecord,
mTokenToRecord,
- mWaitingForConsent);
+ mWaitingForConsent,
+ mTargetUid);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x20) != 0) {
+ if ((mBuilderFieldsSet & 0x40) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -551,10 +626,10 @@
}
@DataClass.Generated(
- time = 1683628463074L,
+ time = 1697456140720L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/ContentRecordingSession.java",
- inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\nprivate int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate boolean mWaitingForConsent\npublic static android.view.ContentRecordingSession createDisplaySession(int)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
+ inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\npublic static final int TARGET_UID_FULL_SCREEN\npublic static final int TARGET_UID_UNKNOWN\nprivate int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate boolean mWaitingForConsent\nprivate int mTargetUid\npublic static android.view.ContentRecordingSession createDisplaySession(int)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder,int)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 0b2b6ce..17c7fcc 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -26,6 +26,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
+import android.app.ActivityThread;
import android.app.KeyguardManager;
import android.app.WindowConfiguration;
import android.compat.annotation.UnsupportedAppUsage;
@@ -1366,7 +1367,8 @@
// form of the larger DISPLAY_CHANGED event
mGlobal.registerDisplayListener(toRegister, executor,
DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED
- | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED,
+ ActivityThread.currentPackageName());
}
}
@@ -2654,6 +2656,7 @@
if (displayId == getDisplayId()) {
float newRatio = getHdrSdrRatio();
if (newRatio != mLastReportedRatio) {
+ mLastReportedRatio = newRatio;
mListener.accept(Display.this);
}
}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index d554514..11180ae 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -54,6 +54,10 @@
*/
void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor);
+ /**
+ * Please dispatch through WindowStateResizeItem instead of directly calling this method from
+ * the system server.
+ */
void resized(in ClientWindowFrames frames, boolean reportDraw,
in MergedConfiguration newMergedConfiguration, in InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index c35b690..9f886c8 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -778,7 +778,8 @@
* Each gamepad or joystick is given a unique, positive controller number when initially
* configured by the system. This number may change due to events such as device disconnects /
* reconnects or user initiated reassignment. Any change in number will trigger an event that
- * can be observed by registering an {@link InputManagerGlobal.InputDeviceListener}.
+ * can be observed by registering an
+ * {@link android.hardware.input.InputManager.InputDeviceListener}.
* </p>
* <p>
* All input devices which are not gamepads or joysticks will be assigned a controller number
diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java
index 78716f5..0ba4148 100644
--- a/core/java/android/view/ScrollFeedbackProvider.java
+++ b/core/java/android/view/ScrollFeedbackProvider.java
@@ -17,7 +17,6 @@
package android.view;
import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
import android.view.flags.Flags;
/**
@@ -43,7 +42,8 @@
* the scroll event. If calling this method in response to a {@link MotionEvent}, use the device ID
* that is reported by the event, which can be obtained using {@link MotionEvent#getDeviceId()}.
* Otherwise, use a valid ID that is obtained from {@link InputDevice#getId()}, or from an
- * {@link InputManager} instance ({@link InputManager#getInputDeviceIds()} gives all the valid input
+ * {@link android.hardware.input.InputManager} instance
+ * ({@link android.hardware.input.InputManager#getInputDeviceIds()} gives all the valid input
* device IDs).
*
* <li><p><b>source</b>: should always be the {@link InputDevice} source that generated the scroll
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b080b71..2f3d73a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -263,7 +263,7 @@
private static native void nativeSetDefaultFrameRateCompatibility(long transactionObj,
long nativeObject, int compatibility);
private static native void nativeSetFrameRateCategory(
- long transactionObj, long nativeObject, int category);
+ long transactionObj, long nativeObject, int category, boolean smoothSwitchOnly);
private static native void nativeSetFrameRateSelectionStrategy(
long transactionObj, long nativeObject, int strategy);
private static native long nativeGetHandle(long nativeObject);
@@ -3709,6 +3709,10 @@
* @param sc The SurfaceControl to specify the frame rate category of.
* @param category The frame rate category of this surface. The category value may influence
* the system's choice of display frame rate.
+ * @param smoothSwitchOnly Set to {@code true} to indicate the display frame rate should not
+ * change if changing it would cause jank. Else {@code false}.
+ * This parameter is ignored when {@code category} is
+ * {@link Surface#FRAME_RATE_CATEGORY_DEFAULT}.
*
* @return This transaction object.
*
@@ -3717,10 +3721,10 @@
* @hide
*/
@NonNull
- public Transaction setFrameRateCategory(
- @NonNull SurfaceControl sc, @Surface.FrameRateCategory int category) {
+ public Transaction setFrameRateCategory(@NonNull SurfaceControl sc,
+ @Surface.FrameRateCategory int category, boolean smoothSwitchOnly) {
checkPreconditions(sc);
- nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category);
+ nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category, smoothSwitchOnly);
return this;
}
@@ -4266,8 +4270,7 @@
* be somewhat arbitrary, and so there are some somewhat arbitrary decisions in
* this API as well.
* <p>
- * @param sc The {@link SurfaceControl} to set the
- * {@link TrustedPresentationCallback} on
+ * @param sc The {@link SurfaceControl} to set the callback on
* @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.
@@ -4300,10 +4303,9 @@
}
/**
- * Clears the {@link TrustedPresentationCallback} for a specific {@link SurfaceControl}
+ * Clears the callback for a specific {@link SurfaceControl}
*
- * @param sc The SurfaceControl that the {@link TrustedPresentationCallback} should be
- * cleared from
+ * @param sc The SurfaceControl that the callback should be cleared from
* @return This transaction
*/
@NonNull
diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index 67ac811..0f35048 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -78,6 +78,11 @@
for (int i = 0; i < size; i++) {
final Map.Entry<SurfaceControl, Long> entry = entries.get(i);
final SurfaceControl sc = entry.getKey();
+ if (sc == null) {
+ // Just skip if the key has since been removed from the weak hash map
+ continue;
+ }
+
final long timeRegistered = entry.getValue();
pw.print(" ");
pw.print(sc.getName());
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 16318e0..49b16c7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -19,6 +19,8 @@
import static android.content.res.Resources.ID_NULL;
import static android.os.Trace.TRACE_TAG_APP;
import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
+import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
+import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
@@ -27,6 +29,7 @@
import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH;
import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+import static android.view.flags.Flags.toolkitSetFrameRate;
import static android.view.flags.Flags.viewVelocityApi;
import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
@@ -114,6 +117,7 @@
import android.text.InputType;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.FloatProperty;
import android.util.LayoutDirection;
import android.util.Log;
@@ -5509,6 +5513,11 @@
private ViewTranslationResponse mViewTranslationResponse;
/**
+ * A threshold value to determine the frame rate category of the View based on the size.
+ */
+ private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
+
+ /**
* Simple constructor to use when creating a view from code.
*
* @param context The Context the view is running in, through which it can
@@ -9308,6 +9317,7 @@
structure.setAutofillType(autofillType);
structure.setAutofillHints(getAutofillHints());
structure.setAutofillValue(getAutofillValue());
+ structure.setIsCredential(isCredential());
}
structure.setImportantForAutofill(getImportantForAutofill());
structure.setReceiveContentMimeTypes(getReceiveContentMimeTypes());
@@ -20182,6 +20192,9 @@
return;
}
+ // For VRR to vote the preferred frame rate
+ votePreferredFrameRate();
+
// Reset content capture caches
mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
mContentCaptureSessionCached = false;
@@ -20284,6 +20297,8 @@
*/
protected void damageInParent() {
if (mParent != null && mAttachInfo != null) {
+ // For VRR to vote the preferred frame rate
+ votePreferredFrameRate();
mParent.onDescendantInvalidated(this, this);
}
}
@@ -32980,6 +32995,40 @@
return null;
}
+ private float getSizePercentage() {
+ if (mResources == null || getAlpha() == 0 || getVisibility() != VISIBLE) {
+ return 0;
+ }
+
+ DisplayMetrics displayMetrics = mResources.getDisplayMetrics();
+ int screenSize = displayMetrics.widthPixels
+ * displayMetrics.heightPixels;
+ int viewSize = getWidth() * getHeight();
+
+ if (screenSize == 0 || viewSize == 0) {
+ return 0f;
+ }
+ return (float) viewSize / screenSize;
+ }
+
+ private int calculateFrameRateCategory() {
+ float sizePercentage = getSizePercentage();
+
+ if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
+ return FRAME_RATE_CATEGORY_LOW;
+ } else {
+ return FRAME_RATE_CATEGORY_NORMAL;
+ }
+ }
+
+ private void votePreferredFrameRate() {
+ // use toolkitSetFrameRate flag to gate the change
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (toolkitSetFrameRate() && viewRootImpl != null && getSizePercentage() > 0) {
+ viewRootImpl.votePreferredFrameRateCategory(calculateFrameRateCategory());
+ }
+ }
+
/**
* Set the current velocity of the View, we only track positive value.
* We will use the velocity information to adjust the frame rate when applicable.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cf8a5b4..50eeed8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -24,6 +24,8 @@
import static android.view.Display.INVALID_DISPLAY;
import static android.view.InputDevice.SOURCE_CLASS_NONE;
import static android.view.InsetsSource.ID_IME;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -74,7 +76,10 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
@@ -87,6 +92,7 @@
import static android.view.accessibility.Flags.forceInvertColor;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
+import static android.view.flags.Flags.toolkitSetFrameRate;
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
@@ -732,6 +738,7 @@
private SurfaceControl mBoundsLayer;
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final Transaction mTransaction = new Transaction();
+ private final Transaction mFrameRateTransaction = new Transaction();
@UnsupportedAppUsage
boolean mAdded;
@@ -955,6 +962,34 @@
private AccessibilityWindowAttributes mAccessibilityWindowAttributes;
+ /*
+ * for Variable Refresh Rate project
+ */
+
+ // The preferred frame rate category of the view that
+ // could be updated on a frame-by-frame basis.
+ private int mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ // The preferred frame rate category of the last frame that
+ // could be used to lower frame rate after touch boost
+ private int mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ // The preferred frame rate of the view that is mainly used for
+ // touch boosting, view velocity handling, and TextureView.
+ private float mPreferredFrameRate = 0;
+ // Used to check if there were any view invalidations in
+ // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME).
+ private boolean mHasInvalidation = false;
+ // Used to check if it is in the touch boosting period.
+ private boolean mIsFrameRateBoosting = false;
+ // Used to check if there is a message in the message queue
+ // for idleness handling.
+ private boolean mHasIdledMessage = false;
+ // time for touch boost period.
+ private static final int FRAME_RATE_TOUCH_BOOST_TIME = 1500;
+ // time for checking idle status periodically.
+ private static final int FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS = 500;
+ // time for revaluating the idle status before lowering the frame rate.
+ private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500;
+
/**
* A temporary object used so relayoutWindow can return the latest SyncSeqId
* system. The SyncSeqId system was designed to work without synchronous relayout
@@ -1016,7 +1051,8 @@
mDisplay = display;
mBasePackageName = context.getBasePackageName();
final String name = DisplayProperties.debug_vri_package().orElse(null);
- mExtraDisplayListenerLogging = !TextUtils.isEmpty(name) && name.equals(mBasePackageName);
+ // TODO: b/306170135 - return to using textutils check on package name.
+ mExtraDisplayListenerLogging = true;
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
@@ -1549,7 +1585,8 @@
mHandler,
DisplayManager.EVENT_FLAG_DISPLAY_ADDED
| DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED,
+ mBasePackageName);
}
/**
@@ -2002,6 +2039,10 @@
Slog.i(mTag, "DisplayState - old: " + oldDisplayState
+ ", new: " + newDisplayState);
}
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.traceCounter(Trace.TRACE_TAG_WINDOW_MANAGER,
+ "vri#screenState[" + mTag + "] state=", newDisplayState);
+ }
if (oldDisplayState != newDisplayState) {
mAttachInfo.mDisplayState = newDisplayState;
pokeDrawLockIfNeeded();
@@ -3917,6 +3958,12 @@
mWmsRequestSyncGroupState = WMS_SYNC_NONE;
}
}
+
+ // For the variable refresh rate project.
+ setPreferredFrameRate(mPreferredFrameRate);
+ setPreferredFrameRateCategory(mPreferredFrameRateCategory);
+ mLastPreferredFrameRateCategory = mPreferredFrameRateCategory;
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
}
private void createSyncIfNeeded() {
@@ -5969,6 +6016,8 @@
private static final int MSG_REPORT_KEEP_CLEAR_RECTS = 36;
private static final int MSG_PAUSED_FOR_SYNC_TIMEOUT = 37;
private static final int MSG_DECOR_VIEW_GESTURE_INTERCEPTION = 38;
+ private static final int MSG_TOUCH_BOOST_TIMEOUT = 39;
+ private static final int MSG_CHECK_INVALIDATION_IDLE = 40;
final class ViewRootHandler extends Handler {
@Override
@@ -6264,6 +6313,32 @@
mNumPausedForSync = 0;
scheduleTraversals();
break;
+ case MSG_TOUCH_BOOST_TIMEOUT:
+ /**
+ * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
+ */
+ mIsFrameRateBoosting = false;
+ setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
+ mLastPreferredFrameRateCategory));
+ break;
+ case MSG_CHECK_INVALIDATION_IDLE:
+ if (!mHasInvalidation && !mIsFrameRateBoosting) {
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ setPreferredFrameRateCategory(mPreferredFrameRateCategory);
+ mHasIdledMessage = false;
+ } else {
+ /**
+ * If there is no invalidation within a certain period,
+ * we consider the display is idled.
+ * We then set the frame rate catetogry to NO_PREFERENCE.
+ * Note that SurfaceFlinger also has a mechanism to lower the refresh rate
+ * if there is no updates of the buffer.
+ */
+ mHasInvalidation = false;
+ mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE,
+ FRAME_RATE_IDLENESS_REEVALUATE_TIME);
+ }
+ break;
}
}
}
@@ -7206,6 +7281,7 @@
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
+ final int action = event.getAction();
boolean handled = mHandwritingInitiator.onTouchEvent(event);
if (handled) {
// If handwriting is started, toolkit doesn't receive ACTION_UP.
@@ -7226,6 +7302,22 @@
scheduleConsumeBatchedInputImmediately();
}
}
+
+ // For the variable refresh rate project
+ if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
+ // set the frame rate to the maximum value.
+ mIsFrameRateBoosting = true;
+ setPreferredFrameRateCategory(mPreferredFrameRateCategory);
+ }
+ /**
+ * We want to lower the refresh rate when MotionEvent.ACTION_UP,
+ * MotionEvent.ACTION_CANCEL is detected.
+ * Not using ACTION_MOVE to avoid checking and sending messages too frequently.
+ */
+ if (mIsFrameRateBoosting && (action == MotionEvent.ACTION_UP
+ || action == MotionEvent.ACTION_CANCEL)) {
+ sendDelayedEmptyMessage(MSG_TOUCH_BOOST_TIMEOUT, FRAME_RATE_TOUCH_BOOST_TIME);
+ }
return handled ? FINISH_HANDLED : FORWARD;
}
@@ -10547,6 +10639,8 @@
MergedConfiguration mergedConfiguration, InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
boolean dragResizing) {
+ // Although this is a AIDL method, it will only be triggered in local process through
+ // either WindowStateResizeItem or WindowlessWindowManager.
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState,
@@ -11438,7 +11532,11 @@
@Override
public @SurfaceControl.BufferTransform int getBufferTransformHint() {
- return mSurfaceControl.getTransformHint();
+ if (mSurfaceControl.isValid()) {
+ return mSurfaceControl.getTransformHint();
+ } else {
+ return SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
+ }
}
@Override
@@ -11807,4 +11905,94 @@
@NonNull Consumer<Boolean> listener) {
t.clearTrustedPresentationCallback(getSurfaceControl());
}
+
+ private void setPreferredFrameRateCategory(int preferredFrameRateCategory) {
+ if (!shouldSetFrameRateCategory()) {
+ return;
+ }
+
+ int frameRateCategory = mIsFrameRateBoosting
+ ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory;
+
+ try {
+ mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
+ frameRateCategory, false).apply();
+ } catch (Exception e) {
+ Log.e(mTag, "Unable to set frame rate category", e);
+ }
+
+ if (mPreferredFrameRateCategory != FRAME_RATE_CATEGORY_NO_PREFERENCE && !mHasIdledMessage) {
+ // Check where the display is idled periodically.
+ // If so, set the frame rate category to NO_PREFERENCE
+ mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE,
+ FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS);
+ mHasIdledMessage = true;
+ }
+ }
+
+ private void setPreferredFrameRate(float preferredFrameRate) {
+ if (!shouldSetFrameRate()) {
+ return;
+ }
+
+ try {
+ mFrameRateTransaction.setFrameRate(mSurfaceControl,
+ preferredFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).apply();
+ } catch (Exception e) {
+ Log.e(mTag, "Unable to set frame rate", e);
+ }
+ }
+
+ private void sendDelayedEmptyMessage(int message, int delayedTime) {
+ mHandler.removeMessages(message);
+
+ mHandler.sendEmptyMessageDelayed(message, delayedTime);
+ }
+
+ private boolean shouldSetFrameRateCategory() {
+ // use toolkitSetFrameRate flag to gate the change
+ return mSurface.isValid() && toolkitSetFrameRate();
+ }
+
+ private boolean shouldSetFrameRate() {
+ // use toolkitSetFrameRate flag to gate the change
+ return mPreferredFrameRate > 0 && toolkitSetFrameRate();
+ }
+
+ private boolean shouldTouchBoost(int motionEventAction, int windowType) {
+ boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN
+ || motionEventAction == MotionEvent.ACTION_MOVE
+ || motionEventAction == MotionEvent.ACTION_UP;
+ boolean desiredType = windowType == TYPE_BASE_APPLICATION || windowType == TYPE_APPLICATION
+ || windowType == TYPE_APPLICATION_STARTING || windowType == TYPE_DRAWN_APPLICATION;
+ // use toolkitSetFrameRate flag to gate the change
+ return desiredAction && desiredType && toolkitSetFrameRate();
+ }
+
+ /**
+ * Allow Views to vote for the preferred frame rate category
+ *
+ * @param frameRateCategory the preferred frame rate category of a View
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+ public void votePreferredFrameRateCategory(int frameRateCategory) {
+ mPreferredFrameRateCategory = Math.max(mPreferredFrameRateCategory, frameRateCategory);
+ mHasInvalidation = true;
+ }
+
+ /**
+ * Get the value of mPreferredFrameRateCategory
+ */
+ @VisibleForTesting
+ public int getPreferredFrameRateCategory() {
+ return mPreferredFrameRateCategory;
+ }
+
+ /**
+ * Get the value of mPreferredFrameRate
+ */
+ @VisibleForTesting
+ public float getPreferredFrameRate() {
+ return mPreferredFrameRate;
+ }
}
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 2c2ae06..bb2c7c8 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -397,6 +397,13 @@
public void setImportantForAutofill(@AutofillImportance int mode) {}
/**
+ * Sets whether the node is a credential. See {@link View#isCredential}.
+ *
+ * @hide
+ */
+ public void setIsCredential(boolean isCredential) {}
+
+ /**
* Sets the MIME types accepted by this view. See {@link View#getReceiveContentMimeTypes()}.
*
* <p>Should only be set when the node is used for Autofill or Content Capture purposes - it
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 6c8f542..4f03ce9 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -17,6 +17,7 @@
package android.view;
import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT;
+import static android.view.flags.Flags.FLAG_WM_DISPLAY_REFRESH_RATE_TEST;
import static android.view.View.STATUS_BAR_DISABLE_BACK;
import static android.view.View.STATUS_BAR_DISABLE_CLOCK;
import static android.view.View.STATUS_BAR_DISABLE_EXPAND;
@@ -1685,7 +1686,7 @@
* orientation (e.g. with {@link android.app.Activity#setRequestedOrientation(int)}). This
* listener gives application an opportunity to selectively react to device orientation changes.
* The newly added listener will be called with current proposed rotation. Note that the context
- * of this window manager instance must be a {@link android.annotation.UiContext}.
+ * of this window manager instance must be a {@code UiContext}.
*
* @param executor The executor on which callback method will be invoked.
* @param listener Called when the proposed rotation for the context is being delivered.
@@ -1693,7 +1694,7 @@
* {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180} and
* {@link Surface#ROTATION_270}.
* @throws UnsupportedOperationException if this method is called on an instance that is not
- * associated with a {@link android.annotation.UiContext}.
+ * associated with a {@code UiContext}.
*/
default void addProposedRotationListener(@NonNull @CallbackExecutor Executor executor,
@NonNull IntConsumer listener) {
@@ -3115,7 +3116,7 @@
/**
* Never animate position changes of the window.
*
- * @see android.R.attr#Window_windowNoMoveAnimation
+ * @see android.R.styleable#Window_windowNoMoveAnimation
* {@hide}
*/
@UnsupportedAppUsage
@@ -3905,6 +3906,7 @@
* This value is ignored if {@link #preferredDisplayModeId} is set.
* @hide
*/
+ @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST)
@TestApi
public float preferredMinDisplayRefreshRate;
@@ -3914,6 +3916,7 @@
* This value is ignored if {@link #preferredDisplayModeId} is set.
* @hide
*/
+ @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST)
@TestApi
public float preferredMaxDisplayRefreshRate;
@@ -4531,7 +4534,7 @@
* Set whether animations can be played for position changes on this window. If disabled,
* the window will move to its new position instantly without animating.
*
- * @attr ref android.R.attr#Window_windowNoMoveAnimation
+ * @attr ref android.R.styleable#Window_windowNoMoveAnimation
*/
public void setCanPlayMoveAnimation(boolean enable) {
if (enable) {
@@ -4546,7 +4549,7 @@
* This does not guarantee that an animation will be played in all such situations. For
* example, drag-resizing may move the window but not play an animation.
*
- * @attr ref android.R.attr#Window_windowNoMoveAnimation
+ * @attr ref android.R.styleable#Window_windowNoMoveAnimation
*/
public boolean canPlayMoveAnimation() {
return (privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0;
diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java
index 7ad43c7..26298bc 100644
--- a/core/java/android/view/WindowMetrics.java
+++ b/core/java/android/view/WindowMetrics.java
@@ -47,7 +47,6 @@
* @see WindowInsets#getInsets(int)
* @see WindowManager#getCurrentWindowMetrics()
* @see WindowManager#getMaximumWindowMetrics()
- * @see android.annotation.UiContext
*/
public final class WindowMetrics {
@NonNull
@@ -99,8 +98,7 @@
}
/**
- * Returns the bounds of the area associated with this window or
- * {@link android.annotation.UiContext}.
+ * Returns the bounds of the area associated with this window or {@code UiContext}.
* <p>
* <b>Note that the size of the reported bounds can have different size than
* {@link Display#getSize(Point)}.</b> This method reports the window size including all system
@@ -133,7 +131,7 @@
/**
* Returns the {@link WindowInsets} of the area associated with this window or
- * {@link android.annotation.UiContext}.
+ * {@code UiContext}.
*
* @return the {@link WindowInsets} of the visual area.
*/
@@ -146,9 +144,8 @@
}
/**
- * Returns the density of the area associated with this window or
- * {@link android.annotation.UiContext}, which uses the same units as
- * {@link android.util.DisplayMetrics#density}.
+ * Returns the density of the area associated with this window or {@code UiContext},
+ * which uses the same units as {@link android.util.DisplayMetrics#density}.
*
* @see android.util.DisplayMetrics#DENSITY_DEFAULT
* @see android.util.DisplayMetrics#density
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 60f46e6..12ce0f4 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -1731,6 +1731,9 @@
@Override
public void sendAttachOverlayResult(
@AccessibilityService.AttachOverlayResult int result, int interactionId) {
+ if (!Flags.a11yOverlayCallbacks()) {
+ return;
+ }
synchronized (mInstanceLock) {
if (mAttachAccessibilityOverlayCallbacks.contains(interactionId)) {
final Pair<Executor, IntConsumer> pair =
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index e6a8b78..43bfe13 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -1562,9 +1562,8 @@
* describes the action.
* </p>
* <p>
- * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View,
- * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the
- * view.
+ * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View, CharSequence,
+ * AccessibilityViewCommand)} to register an action directly on the view.
* <p>
* <strong>Note:</strong> Cannot be called from an
* {@link android.accessibilityservice.AccessibilityService}.
@@ -5167,8 +5166,7 @@
* </p>
* <aside class="note">
* <b>Note:</b> Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View,
- * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the
- * view.
+ * CharSequence, AccessibilityViewCommand)} to register an action directly on the view.
* </p>
*/
public static final class AccessibilityAction implements Parcelable {
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index 09b2f9c..fa0052c 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -99,7 +99,6 @@
/** @hide */
public static final int UNDEFINED_CONNECTION_ID = -1;
/** @hide */
- @TestApi
public static final int UNDEFINED_WINDOW_ID = -1;
/** @hide */
public static final int ANY_WINDOW_ID = -2;
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 85dadd4..cc612ed 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -13,3 +13,10 @@
description: "Allows the a11y shortcut disambig dialog to appear on the lockscreen"
bug: "303871725"
}
+
+flag {
+ name: "a11y_overlay_callbacks"
+ namespace: "accessibility"
+ description: "Whether to allow the passing of result callbacks when attaching a11y overlays."
+ bug: "304478691"
+}
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index a76780d..32256b9 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -56,7 +56,7 @@
private static final int SEQUENTIALLY = 1;
/**
- * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above,
+ * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and above,
* this change ID enables to use expectedPresentationTime instead of the frameTime
* for the frame start time .
*
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index dc3d323..bb815c0 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -183,7 +183,8 @@
public static final int FLUSH_REASON_VIEW_TREE_APPEARED = 10;
/**
- * After {@link UPSIDE_DOWN_CAKE}, {@link #notifyViewsDisappeared(AutofillId, long[])} wraps
+ * After {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * {@link #notifyViewsDisappeared(AutofillId, long[])} wraps
* the virtual children with a pair of view tree appearing and view tree appeared events.
*/
@ChangeId
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 2241fd5..b44d6a4 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -317,16 +317,14 @@
}
}
- // Should not be possible for mComponentName to be null here but check anyway
- if (mManager.mOptions.contentProtectionOptions.enableReceiver
- && mManager.getContentProtectionEventBuffer() != null
- && mComponentName != null) {
+ if (isContentProtectionEnabled()) {
mContentProtectionEventProcessor =
new ContentProtectionEventProcessor(
mManager.getContentProtectionEventBuffer(),
mHandler,
mSystemServerInterface,
- mComponentName.getPackageName());
+ mComponentName.getPackageName(),
+ mManager.mOptions.contentProtectionOptions);
} else {
mContentProtectionEventProcessor = null;
}
@@ -956,4 +954,15 @@
private boolean isContentCaptureReceiverEnabled() {
return mManager.mOptions.enableReceiver;
}
+
+ @UiThread
+ private boolean isContentProtectionEnabled() {
+ // Should not be possible for mComponentName to be null here but check anyway
+ // Should not be possible for groups to be empty if receiver is enabled but check anyway
+ return mManager.mOptions.contentProtectionOptions.enableReceiver
+ && mManager.getContentProtectionEventBuffer() != null
+ && mComponentName != null
+ && (!mManager.mOptions.contentProtectionOptions.requiredGroups.isEmpty()
+ || !mManager.mOptions.contentProtectionOptions.optionalGroups.isEmpty());
+ }
}
diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
index b44abf3..aaf90bd 100644
--- a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
+++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
@@ -19,9 +19,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
+import android.content.ContentCaptureOptions;
import android.content.pm.ParceledListSlice;
import android.os.Handler;
-import android.text.InputType;
import android.util.Log;
import android.view.contentcapture.ContentCaptureEvent;
import android.view.contentcapture.IContentCaptureManager;
@@ -33,10 +33,10 @@
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
+import java.util.Collection;
import java.util.List;
import java.util.Set;
+import java.util.stream.Stream;
/**
* Main entry point for processing {@link ContentCaptureEvent} for the content protection flow.
@@ -47,33 +47,13 @@
private static final String TAG = "ContentProtectionEventProcessor";
- private static final List<Integer> PASSWORD_FIELD_INPUT_TYPES =
- Collections.unmodifiableList(
- Arrays.asList(
- InputType.TYPE_NUMBER_VARIATION_PASSWORD,
- InputType.TYPE_TEXT_VARIATION_PASSWORD,
- InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
- InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD));
-
- private static final List<String> PASSWORD_TEXTS =
- Collections.unmodifiableList(
- Arrays.asList("password", "pass word", "code", "pin", "credential"));
-
- private static final List<String> ADDITIONAL_SUSPICIOUS_TEXTS =
- Collections.unmodifiableList(
- Arrays.asList("user", "mail", "phone", "number", "login", "log in", "sign in"));
-
private static final Duration MIN_DURATION_BETWEEN_FLUSHING = Duration.ofSeconds(3);
- private static final String ANDROID_CLASS_NAME_PREFIX = "android.";
-
private static final Set<Integer> EVENT_TYPES_TO_STORE =
- Collections.unmodifiableSet(
- new HashSet<>(
- Arrays.asList(
- ContentCaptureEvent.TYPE_VIEW_APPEARED,
- ContentCaptureEvent.TYPE_VIEW_DISAPPEARED,
- ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED)));
+ Set.of(
+ ContentCaptureEvent.TYPE_VIEW_APPEARED,
+ ContentCaptureEvent.TYPE_VIEW_DISAPPEARED,
+ ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED);
private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150;
@@ -85,11 +65,7 @@
@NonNull private final String mPackageName;
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
- public boolean mPasswordFieldDetected = false;
-
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
- public boolean mSuspiciousTextDetected = false;
+ @NonNull private final ContentCaptureOptions.ContentProtectionOptions mOptions;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@Nullable
@@ -97,15 +73,32 @@
private int mResetLoginRemainingEventsToProcess;
+ private boolean mAnyGroupFound = false;
+
+ // Ordered by priority
+ private final List<SearchGroup> mGroupsRequired;
+
+ // Ordered by priority
+ private final List<SearchGroup> mGroupsOptional;
+
+ // Ordered by priority
+ private final List<SearchGroup> mGroupsAll;
+
public ContentProtectionEventProcessor(
@NonNull RingBuffer<ContentCaptureEvent> eventBuffer,
@NonNull Handler handler,
@NonNull IContentCaptureManager contentCaptureManager,
- @NonNull String packageName) {
+ @NonNull String packageName,
+ @NonNull ContentCaptureOptions.ContentProtectionOptions options) {
mEventBuffer = eventBuffer;
mHandler = handler;
mContentCaptureManager = contentCaptureManager;
mPackageName = packageName;
+ mOptions = options;
+ mGroupsRequired = options.requiredGroups.stream().map(SearchGroup::new).toList();
+ mGroupsOptional = options.optionalGroups.stream().map(SearchGroup::new).toList();
+ mGroupsAll =
+ Stream.of(mGroupsRequired, mGroupsOptional).flatMap(Collection::stream).toList();
}
/** Main entry point for {@link ContentCaptureEvent} processing. */
@@ -130,9 +123,31 @@
@UiThread
private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) {
- mPasswordFieldDetected |= isPasswordField(event);
- mSuspiciousTextDetected |= isSuspiciousText(event);
- if (mPasswordFieldDetected && mSuspiciousTextDetected) {
+ ViewNode viewNode = event.getViewNode();
+ String eventText = ContentProtectionUtils.getEventTextLower(event);
+ String viewNodeText = ContentProtectionUtils.getViewNodeTextLower(viewNode);
+ String hintText = ContentProtectionUtils.getHintTextLower(viewNode);
+
+ mGroupsAll.stream()
+ .filter(group -> !group.mFound)
+ .filter(
+ group ->
+ group.matches(eventText)
+ || group.matches(viewNodeText)
+ || group.matches(hintText))
+ .findFirst()
+ .ifPresent(
+ group -> {
+ group.mFound = true;
+ mAnyGroupFound = true;
+ });
+
+ boolean loginDetected =
+ mGroupsRequired.stream().allMatch(group -> group.mFound)
+ && mGroupsOptional.stream().filter(group -> group.mFound).count()
+ >= mOptions.optionalGroupsThreshold;
+
+ if (loginDetected) {
loginDetected();
} else {
maybeResetLoginFlags();
@@ -150,14 +165,13 @@
@UiThread
private void resetLoginFlags() {
- mPasswordFieldDetected = false;
- mSuspiciousTextDetected = false;
- mResetLoginRemainingEventsToProcess = 0;
+ mGroupsAll.forEach(group -> group.mFound = false);
+ mAnyGroupFound = false;
}
@UiThread
private void maybeResetLoginFlags() {
- if (mPasswordFieldDetected || mSuspiciousTextDetected) {
+ if (mAnyGroupFound) {
if (mResetLoginRemainingEventsToProcess <= 0) {
mResetLoginRemainingEventsToProcess = RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS;
} else {
@@ -194,61 +208,21 @@
}
}
- private boolean isPasswordField(@NonNull ContentCaptureEvent event) {
- return isPasswordField(event.getViewNode());
- }
+ private static final class SearchGroup {
- private boolean isPasswordField(@Nullable ViewNode viewNode) {
- if (viewNode == null) {
- return false;
+ @NonNull private final List<String> mSearchStrings;
+
+ public boolean mFound = false;
+
+ SearchGroup(@NonNull List<String> searchStrings) {
+ mSearchStrings = searchStrings;
}
- return isAndroidPasswordField(viewNode) || isWebViewPasswordField(viewNode);
- }
- private boolean isAndroidPasswordField(@NonNull ViewNode viewNode) {
- if (!isAndroidViewNode(viewNode)) {
- return false;
+ public boolean matches(@Nullable String text) {
+ if (text == null) {
+ return false;
+ }
+ return mSearchStrings.stream().anyMatch(text::contains);
}
- int inputType = viewNode.getInputType();
- return PASSWORD_FIELD_INPUT_TYPES.stream()
- .anyMatch(passwordInputType -> (inputType & passwordInputType) != 0);
- }
-
- private boolean isWebViewPasswordField(@NonNull ViewNode viewNode) {
- if (viewNode.getClassName() != null) {
- return false;
- }
- return isPasswordText(ContentProtectionUtils.getViewNodeText(viewNode));
- }
-
- private boolean isAndroidViewNode(@NonNull ViewNode viewNode) {
- String className = viewNode.getClassName();
- return className != null && className.startsWith(ANDROID_CLASS_NAME_PREFIX);
- }
-
- private boolean isSuspiciousText(@NonNull ContentCaptureEvent event) {
- return isSuspiciousText(ContentProtectionUtils.getEventText(event))
- || isSuspiciousText(ContentProtectionUtils.getViewNodeText(event));
- }
-
- private boolean isSuspiciousText(@Nullable String text) {
- if (text == null) {
- return false;
- }
- if (isPasswordText(text)) {
- return true;
- }
- String lowerCaseText = text.toLowerCase();
- return ADDITIONAL_SUSPICIOUS_TEXTS.stream()
- .anyMatch(suspiciousText -> lowerCaseText.contains(suspiciousText));
- }
-
- private boolean isPasswordText(@Nullable String text) {
- if (text == null) {
- return false;
- }
- String lowerCaseText = text.toLowerCase();
- return PASSWORD_TEXTS.stream()
- .anyMatch(passwordText -> lowerCaseText.contains(passwordText));
}
}
diff --git a/core/java/android/view/contentprotection/ContentProtectionUtils.java b/core/java/android/view/contentprotection/ContentProtectionUtils.java
index 9abf6f1..1ecac7f 100644
--- a/core/java/android/view/contentprotection/ContentProtectionUtils.java
+++ b/core/java/android/view/contentprotection/ContentProtectionUtils.java
@@ -28,33 +28,39 @@
*/
public final class ContentProtectionUtils {
- /** Returns the text extracted directly from the {@link ContentCaptureEvent}, if set. */
+ /** Returns the lowercase text extracted from the {@link ContentCaptureEvent}, if set. */
@Nullable
- public static String getEventText(@NonNull ContentCaptureEvent event) {
+ public static String getEventTextLower(@NonNull ContentCaptureEvent event) {
CharSequence text = event.getText();
if (text == null) {
return null;
}
- return text.toString();
+ return text.toString().toLowerCase();
}
- /** Returns the text extracted from the event's {@link ViewNode}, if set. */
+ /** Returns the lowercase text extracted from the {@link ViewNode}, if set. */
@Nullable
- public static String getViewNodeText(@NonNull ContentCaptureEvent event) {
- ViewNode viewNode = event.getViewNode();
+ public static String getViewNodeTextLower(@Nullable ViewNode viewNode) {
if (viewNode == null) {
return null;
}
- return getViewNodeText(viewNode);
- }
-
- /** Returns the text extracted directly from the {@link ViewNode}, if set. */
- @Nullable
- public static String getViewNodeText(@NonNull ViewNode viewNode) {
CharSequence text = viewNode.getText();
if (text == null) {
return null;
}
- return text.toString();
+ return text.toString().toLowerCase();
+ }
+
+ /** Returns the lowercase hint text extracted from the {@link ViewNode}, if set. */
+ @Nullable
+ public static String getHintTextLower(@Nullable ViewNode viewNode) {
+ if (viewNode == null) {
+ return null;
+ }
+ String text = viewNode.getHint();
+ if (text == null) {
+ return null;
+ }
+ return text.toLowerCase();
}
}
diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
index 7e06f87..f6ee061 100644
--- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
+++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
@@ -6,3 +6,17 @@
description: "If true, content protection blocklist is mutable and can be updated."
bug: "301658008"
}
+
+flag {
+ name: "parse_groups_config_enabled"
+ namespace: "content_protection"
+ description: "If true, content protection groups config will be parsed."
+ bug: "302187922"
+}
+
+flag {
+ name: "setting_ui_enabled"
+ namespace: "content_protection"
+ description: "If true, content protection setting ui is displayed in Settings > Privacy & Security > More security & privacy."
+ bug: "305792348"
+}
diff --git a/core/java/android/view/displayhash/DisplayHashResultCallback.java b/core/java/android/view/displayhash/DisplayHashResultCallback.java
index 6e3d9a8..927874f 100644
--- a/core/java/android/view/displayhash/DisplayHashResultCallback.java
+++ b/core/java/android/view/displayhash/DisplayHashResultCallback.java
@@ -106,7 +106,7 @@
* {@link android.view.View#generateDisplayHash(String, Rect, Executor,
* DisplayHashResultCallback)} results in an error and cannot generate a display hash.
*
- * @param errorCode One of the values in {@link DisplayHashErrorCode}
+ * @param errorCode the error code
*/
void onDisplayHashError(@DisplayHashErrorCode int errorCode);
}
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 56b5fac..fd96890 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -26,4 +26,11 @@
namespace: "core_graphics"
description: "Enable the `setFrameRate` callback"
bug: "299946220"
+}
+
+flag {
+ name: "wm_display_refresh_rate_test"
+ namespace: "core_graphics"
+ description: "Adds WindowManager display refresh rate fields to test API"
+ bug: "304475199"
}
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index a92420a..c5114b9 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -47,7 +47,6 @@
import android.view.MotionEvent.ToolType;
import android.view.View;
import android.view.autofill.AutofillId;
-import android.widget.Editor;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.InputMethodDebug;
@@ -722,9 +721,9 @@
private boolean mIsStylusHandwritingEnabled;
/**
- * Set {@code true} if the {@link Editor} has
+ * Set {@code true} if the {@code Editor} has
* {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled.
- * {@code false} by default, {@link Editor} must set it {@code true} to indicate that
+ * {@code false} by default, {@code Editor} must set it {@code true} to indicate that
* it supports stylus handwriting.
*
* @param enabled {@code true} if stylus handwriting is enabled.
@@ -736,7 +735,7 @@
}
/**
- * Returns {@code true} when an {@link Editor} has stylus handwriting enabled.
+ * Returns {@code true} when an {@code Editor} has stylus handwriting enabled.
* {@code false} by default.
* @see #setStylusHandwritingEnabled(boolean)
* @see InputMethodManager#isStylusHandwritingAvailable()
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 5bb1e93..eeab005 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -39,6 +39,7 @@
import android.annotation.DisplayContext;
import android.annotation.DrawableRes;
import android.annotation.DurationMillisLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -100,7 +101,6 @@
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
-import android.widget.Editor;
import android.window.ImeOnBackInvokedDispatcher;
import android.window.WindowOnBackInvokedDispatcher;
@@ -1513,6 +1513,7 @@
* Returns {@code true} if currently selected IME supports Stylus handwriting & is enabled.
* If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
* called and Stylus touch should continue as normal touch input.
+ *
* @see #startStylusHandwriting(View)
*/
public boolean isStylusHandwritingAvailable() {
@@ -1536,6 +1537,7 @@
@NonNull
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
@TestApi
+ @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS)
@SuppressLint("UserHandle")
public boolean isStylusHandwritingAvailableAsUser(@NonNull UserHandle user) {
final Context fallbackContext = ActivityThread.currentApplication();
@@ -1656,6 +1658,7 @@
@NonNull
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
@TestApi
+ @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS)
@SuppressLint("UserHandle")
public List<InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull UserHandle user) {
return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(user.getIdentifier());
@@ -1691,12 +1694,13 @@
* {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required if this is
* different from the calling process user ID.
* @return {@link List} of {@link InputMethodSubtype}.
- * @see #getEnabledInputMethodListAsUser(int)
+ * @see #getEnabledInputMethodListAsUser(UserHandle)
* @hide
*/
@NonNull
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
@TestApi
+ @FlaggedApi(Flags.FLAG_IMM_USERHANDLE_HOSTSIDETESTS)
@SuppressLint("UserHandle")
public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(
@NonNull String imeId, boolean allowsImplicitlyEnabledSubtypes,
@@ -2374,16 +2378,16 @@
* Prepares delegation of starting stylus handwriting session to a different editor in same
* or different window than the view on which initial handwriting stroke was detected.
*
- * Delegation can be used to start stylus handwriting session before the {@link Editor} view or
+ * Delegation can be used to start stylus handwriting session before the {@code Editor} view or
* its {@link InputConnection} is started. Calling this method starts buffering of stylus
* motion events until {@link #acceptStylusHandwritingDelegation(View)} is called, at which
* point the handwriting session can be started and the buffered stylus motion events will be
* delivered to the IME.
* e.g. Delegation can be used when initial handwriting stroke is
- * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual
- * {@link Editor} is on a different window.
+ * on a pseudo {@code Editor} like widget (with no {@link InputConnection}) but actual
+ * {@code Editor} is on a different window.
*
- * <p> Note: If an actual {@link Editor} capable of {@link InputConnection} is being scribbled
+ * <p> Note: If an actual {@code Editor} capable of {@link InputConnection} is being scribbled
* upon using stylus, use {@link #startStylusHandwriting(View)} instead.</p>
*
* @param delegatorView the view that receives initial stylus stroke and delegates it to the
@@ -2402,21 +2406,21 @@
* different window in a different package than the view on which initial handwriting stroke
* was detected.
*
- * Delegation can be used to start stylus handwriting session before the {@link Editor} view or
+ * Delegation can be used to start stylus handwriting session before the {@code Editor} view or
* its {@link InputConnection} is started. Calling this method starts buffering of stylus
* motion events until {@link #acceptStylusHandwritingDelegation(View, String)} is called, at
* which point the handwriting session can be started and the buffered stylus motion events will
* be delivered to the IME.
* e.g. Delegation can be used when initial handwriting stroke is
- * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual
- * {@link Editor} is on a different window in the given package.
+ * on a pseudo {@code Editor} like widget (with no {@link InputConnection}) but actual
+ * {@code Editor} is on a different window in the given package.
*
* <p>Note: If delegator and delegate are in same package use
* {@link #prepareStylusHandwritingDelegation(View)} instead.</p>
*
* @param delegatorView the view that receives initial stylus stroke and delegates it to the
* actual editor. Its window must {@link View#hasWindowFocus have focus}.
- * @param delegatePackageName package name that contains actual {@link Editor} which should
+ * @param delegatePackageName package name that contains actual {@code Editor} which should
* start stylus handwriting session by calling {@link #acceptStylusHandwritingDelegation}.
* @see #prepareStylusHandwritingDelegation(View)
* @see #acceptStylusHandwritingDelegation(View, String)
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index c14b510..1e8718c 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -14,4 +14,12 @@
description: "Feature flag for adding EditorInfo#mStylusHandwritingEnabled"
bug: "293898187"
is_fixed_read_only: true
+}
+
+flag {
+ name: "imm_userhandle_hostsidetests"
+ namespace: "input_method"
+ description: "Feature flag for replacing UserIdInt with UserHandle in some helper IMM functions"
+ bug: "301713309"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/android/view/inspector/PropertyReader.java b/core/java/android/view/inspector/PropertyReader.java
index 5be0e3f..78de7e1 100644
--- a/core/java/android/view/inspector/PropertyReader.java
+++ b/core/java/android/view/inspector/PropertyReader.java
@@ -124,7 +124,7 @@
void readObject(int id, @Nullable Object value);
/**
- * Read a color packed into a {@link ColorInt} as a property.
+ * Read a color packed into an int as a property.
*
* @param id Identifier of the property from a {@link PropertyMapper}
* @param value Value of the property
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 103725d..f19a2f9 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -685,8 +685,9 @@
return false;
}
+ /** See {@link RemoteViews#visitUris(Consumer)}. **/
public void visitUris(@NonNull Consumer<Uri> visitor) {
- // Nothing to visit by default
+ // Nothing to visit by default.
}
}
@@ -761,9 +762,11 @@
}
/**
- * Note all {@link Uri} that are referenced internally, with the expectation
- * that Uri permission grants will need to be issued to ensure the recipient
- * of this object is able to render its contents.
+ * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
+ * grants will need to be issued to ensure the recipient of this object is able to render its
+ * contents.
+ * See b/281044385 for more context and examples about what happens when this isn't done
+ * correctly.
*
* @hide
*/
@@ -1088,6 +1091,13 @@
public String getUniqueKey() {
return (SET_REMOTE_ADAPTER_TAG + "_" + mViewId);
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ for (RemoteViews remoteViews : mList) {
+ remoteViews.visitUris(visitor);
+ }
+ }
}
/**
@@ -1289,6 +1299,12 @@
public String getUniqueKey() {
return (SET_REMOTE_ADAPTER_TAG + "_" + mViewId);
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+ items.visitUris(visitor);
+ }
}
private class SetRemoteViewsAdapterIntent extends Action {
@@ -1359,6 +1375,13 @@
public int getActionTag() {
return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG;
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ // TODO(b/281044385): Maybe visit intent URIs. This may require adding a dedicated
+ // visitUris method in the Intent class, since it can contain other intents. Otherwise,
+ // the basic thing to do here would be just visitor.accept(intent.getData()).
+ }
}
/**
@@ -1434,6 +1457,11 @@
public int getActionTag() {
return SET_ON_CLICK_RESPONSE_TAG;
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse.
+ }
}
/**
@@ -1504,6 +1532,11 @@
public int getActionTag() {
return SET_ON_CHECKED_CHANGE_RESPONSE_TAG;
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse.
+ }
}
/** @hide **/
@@ -2063,6 +2096,7 @@
final Icon icon = (Icon) getParameterValue(null);
if (icon != null) visitIconUri(icon, visitor);
break;
+ // TODO(b/281044385): Should we do anything about type BUNDLE?
}
}
}
@@ -2812,7 +2846,7 @@
}
@Override
- public final void visitUris(@NonNull Consumer<Uri> visitor) {
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
mNestedViews.visitUris(visitor);
}
}
@@ -7262,6 +7296,15 @@
Math.max(mViewTypeCount, 1));
}
}
+
+ /**
+ * See {@link RemoteViews#visitUris(Consumer)}.
+ */
+ private void visitUris(@NonNull Consumer<Uri> visitor) {
+ for (RemoteViews view : mViews) {
+ view.visitUris(visitor);
+ }
+ }
}
/**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index e8281ea..17c82b6 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -15555,6 +15555,9 @@
private void ensureIterableTextForAccessibilitySelectable() {
if (!(mText instanceof Spannable)) {
setText(mText, BufferType.SPANNABLE);
+ if (getLayout() == null) {
+ assumeLayout();
+ }
}
}
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index f53737a..5562360 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -49,7 +49,7 @@
private final int mSwipeEdge;
/**
- * Creates a new {@link BackMotionEvent} instance.
+ * Creates a new {@link BackEvent} instance.
*
* @param touchX Absolute X location of the touch point of this event.
* @param touchY Absolute Y location of the touch point of this event.
diff --git a/core/java/android/window/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java
index 07ac292..2736b68 100644
--- a/core/java/android/window/SystemPerformanceHinter.java
+++ b/core/java/android/window/SystemPerformanceHinter.java
@@ -211,7 +211,11 @@
session.displayId);
mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
- mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH);
+ // smoothSwitchOnly is false to request a higher framerate, even if it means switching
+ // the display mode will cause would jank on non-VRR devices because keeping a lower
+ // refresh rate would mean a poorer user experience.
+ mTransaction.setFrameRateCategory(
+ displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH, /* smoothSwitchOnly= */ false);
transactionChanged = true;
Trace.beginAsyncSection("PerfHint-framerate-" + session.displayId + "-"
+ session.reason, session.traceCookie);
@@ -251,7 +255,11 @@
session.displayId);
mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
FRAME_RATE_SELECTION_STRATEGY_SELF);
- mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT);
+ // smoothSwitchOnly is false to request a higher framerate, even if it means switching
+ // the display mode will cause would jank on non-VRR devices because keeping a lower
+ // refresh rate would mean a poorer user experience.
+ mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT,
+ /* smoothSwitchOnly= */ false);
transactionChanged = true;
Trace.endAsyncSection("PerfHint-framerate-" + session.displayId + "-" + session.reason,
session.traceCookie);
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 43fa0be..4e0f9a5 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -88,6 +88,26 @@
*/
public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11;
+ /**
+ * Reorders the TaskFragment to be the bottom-most in the Task. Note that this op will bring the
+ * TaskFragment to the bottom of the Task below all the other Activities and TaskFragments.
+ *
+ * This is only allowed for system organizers. See
+ * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
+ * ITaskFragmentOrganizer, boolean)}
+ */
+ public static final int OP_TYPE_REORDER_TO_BOTTOM_OF_TASK = 12;
+
+ /**
+ * Reorders the TaskFragment to be the top-most in the Task. Note that this op will bring the
+ * TaskFragment to the top of the Task above all the other Activities and TaskFragments.
+ *
+ * This is only allowed for system organizers. See
+ * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
+ * ITaskFragmentOrganizer, boolean)}
+ */
+ public static final int OP_TYPE_REORDER_TO_TOP_OF_TASK = 13;
+
@IntDef(prefix = { "OP_TYPE_" }, value = {
OP_TYPE_UNKNOWN,
OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -101,7 +121,9 @@
OP_TYPE_SET_ANIMATION_PARAMS,
OP_TYPE_SET_RELATIVE_BOUNDS,
OP_TYPE_REORDER_TO_FRONT,
- OP_TYPE_SET_ISOLATED_NAVIGATION
+ OP_TYPE_SET_ISOLATED_NAVIGATION,
+ OP_TYPE_REORDER_TO_BOTTOM_OF_TASK,
+ OP_TYPE_REORDER_TO_TOP_OF_TASK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface OperationType {}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index d2a16a3..1a2d202 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -29,6 +29,7 @@
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -361,6 +362,15 @@
}
/**
+ * Whether this transition contains any changes to the window hierarchy,
+ * including keyguard visibility.
+ */
+ public boolean hasChangesOrSideEffects() {
+ return !mChanges.isEmpty() || isKeyguardGoingAway()
+ || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0;
+ }
+
+ /**
* Whether this transition includes keyguard going away.
*/
public boolean isKeyguardGoingAway() {
@@ -392,6 +402,18 @@
@Override
public String toString() {
+ return toString("");
+ }
+
+ /**
+ * Returns a string representation of this transition info.
+ * @hide
+ */
+ public String toString(@NonNull String prefix) {
+ final boolean shouldPrettyPrint = !prefix.isEmpty() && !mChanges.isEmpty();
+ final String innerPrefix = shouldPrettyPrint ? prefix + " " : "";
+ final String changesLineStart = shouldPrettyPrint ? "\n" + prefix : "";
+ final String perChangeLineStart = shouldPrettyPrint ? "\n" + innerPrefix : "";
StringBuilder sb = new StringBuilder();
sb.append("{id=").append(mDebugId).append(" t=").append(transitTypeToString(mType))
.append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack)
@@ -403,12 +425,15 @@
sb.append(mRoots.get(i).mDisplayId).append("@").append(mRoots.get(i).mOffset);
}
sb.append("] c=[");
+ sb.append(perChangeLineStart);
for (int i = 0; i < mChanges.size(); ++i) {
if (i > 0) {
sb.append(',');
+ sb.append(perChangeLineStart);
}
sb.append(mChanges.get(i));
}
+ sb.append(changesLineStart);
sb.append("]}");
return sb.toString();
}
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index 932608a3..bd54e14 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -62,13 +62,16 @@
/** The transition flags known at the time of the request. These may not be complete. */
private final int mFlags;
+ /** This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work! */
+ private final int mDebugId;
+
/** constructor override */
public TransitionRequestInfo(
@WindowManager.TransitionType int type,
@Nullable ActivityManager.RunningTaskInfo triggerTask,
@Nullable RemoteTransition remoteTransition) {
this(type, triggerTask, null /* pipTask */,
- remoteTransition, null /* displayChange */, 0 /* flags */);
+ remoteTransition, null /* displayChange */, 0 /* flags */, -1 /* debugId */);
}
/** constructor override */
@@ -78,16 +81,29 @@
@Nullable RemoteTransition remoteTransition,
int flags) {
this(type, triggerTask, null /* pipTask */,
- remoteTransition, null /* displayChange */, flags);
+ remoteTransition, null /* displayChange */, flags, -1 /* debugId */);
}
+ /** constructor override */
public TransitionRequestInfo(
@WindowManager.TransitionType int type,
@Nullable ActivityManager.RunningTaskInfo triggerTask,
@Nullable RemoteTransition remoteTransition,
@Nullable TransitionRequestInfo.DisplayChange displayChange,
int flags) {
- this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags);
+ this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags,
+ -1 /* debugId */);
+ }
+
+ /** constructor override */
+ public TransitionRequestInfo(
+ @WindowManager.TransitionType int type,
+ @Nullable ActivityManager.RunningTaskInfo triggerTask,
+ @Nullable ActivityManager.RunningTaskInfo pipTask,
+ @Nullable RemoteTransition remoteTransition,
+ @Nullable TransitionRequestInfo.DisplayChange displayChange,
+ int flags) {
+ this(type, triggerTask, pipTask, remoteTransition, displayChange, flags, -1 /* debugId */);
}
/** @hide */
@@ -270,7 +286,7 @@
};
@DataClass.Generated(
- time = 1695667226050L,
+ time = 1697564781403L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
inputSignatures = "private final int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate int mStartRotation\nprivate int mEndRotation\nprivate boolean mPhysicalDisplayChanged\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)")
@@ -318,6 +334,8 @@
* (if size is changing).
* @param flags
* The transition flags known at the time of the request. These may not be complete.
+ * @param debugId
+ * This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work!
*/
@DataClass.Generated.Member
public TransitionRequestInfo(
@@ -326,7 +344,8 @@
@Nullable ActivityManager.RunningTaskInfo pipTask,
@Nullable RemoteTransition remoteTransition,
@Nullable TransitionRequestInfo.DisplayChange displayChange,
- int flags) {
+ int flags,
+ int debugId) {
this.mType = type;
com.android.internal.util.AnnotationValidations.validate(
WindowManager.TransitionType.class, null, mType);
@@ -335,6 +354,7 @@
this.mRemoteTransition = remoteTransition;
this.mDisplayChange = displayChange;
this.mFlags = flags;
+ this.mDebugId = debugId;
// onConstructed(); // You can define this method to get a callback
}
@@ -392,6 +412,14 @@
}
/**
+ * This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work!
+ */
+ @DataClass.Generated.Member
+ public int getDebugId() {
+ return mDebugId;
+ }
+
+ /**
* If non-null, the task containing the activity whose lifecycle change (start or
* finish) has caused this transition to occur.
*/
@@ -443,7 +471,8 @@
"pipTask = " + mPipTask + ", " +
"remoteTransition = " + mRemoteTransition + ", " +
"displayChange = " + mDisplayChange + ", " +
- "flags = " + mFlags +
+ "flags = " + mFlags + ", " +
+ "debugId = " + mDebugId +
" }";
}
@@ -465,6 +494,7 @@
if (mRemoteTransition != null) dest.writeTypedObject(mRemoteTransition, flags);
if (mDisplayChange != null) dest.writeTypedObject(mDisplayChange, flags);
dest.writeInt(mFlags);
+ dest.writeInt(mDebugId);
}
@Override
@@ -485,6 +515,7 @@
RemoteTransition remoteTransition = (flg & 0x8) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
TransitionRequestInfo.DisplayChange displayChange = (flg & 0x10) == 0 ? null : (TransitionRequestInfo.DisplayChange) in.readTypedObject(TransitionRequestInfo.DisplayChange.CREATOR);
int flags = in.readInt();
+ int debugId = in.readInt();
this.mType = type;
com.android.internal.util.AnnotationValidations.validate(
@@ -494,6 +525,7 @@
this.mRemoteTransition = remoteTransition;
this.mDisplayChange = displayChange;
this.mFlags = flags;
+ this.mDebugId = debugId;
// onConstructed(); // You can define this method to get a callback
}
@@ -513,10 +545,10 @@
};
@DataClass.Generated(
- time = 1695667226088L,
+ time = 1697564781438L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
- inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\n java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
+ inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\nprivate final int mDebugId\n java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index 611da3c..0cbfcc5 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -155,7 +155,7 @@
}
/**
- * Override {@link Service}'s empty implementation and listen to {@link ActivityThread} for
+ * Override {@link Service}'s empty implementation and listen to {@code ActivityThread} for
* low memory and trim memory events.
*/
@Override
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 7c931cd..7cfd35b 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -13,3 +13,27 @@
description: "On close to square display, when necessary, configuration includes status bar"
bug: "291870756"
}
+
+flag {
+ name: "dimmer_refactor"
+ namespace: "windowing_frontend"
+ description: "Refactor dim to fix flickers"
+ bug: "295291019"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "transit_ready_tracking"
+ namespace: "windowing_frontend"
+ description: "Enable accurate transition readiness tracking"
+ bug: "294925498"
+}
+
+
+flag {
+ name: "wallpaper_offset_async"
+ namespace: "windowing_frontend"
+ description: "Do not synchronise the wallpaper offset"
+ bug: "293248754"
+ is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index ec5d4ff..24dc6db 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -22,3 +22,10 @@
description: "Whether the TaskFragment system organizer feature is enabled"
bug: "284050041"
}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "window_state_resize_item_flag"
+ description: "Whether to dispatch window resize through ClientTransaction is enabled"
+ bug: "301870955"
+}
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index 4e7bfe5..71bbccb 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -259,7 +259,7 @@
}
return true;
}
- return false;
+ return super.onKeyDown(keyCode,event);
}
@Override
@@ -268,7 +268,7 @@
stopWarp();
return true;
}
- return false;
+ return super.onKeyUp(keyCode,event);
}
private void startWarp() {
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 58376a7..0801dd8 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -62,16 +62,14 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
+ import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Deque;
-import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
+import java.util.Queue;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.function.Predicate;
-import java.util.regex.Pattern;
/**
* A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local
@@ -89,6 +87,8 @@
DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER,
DocumentsContract.QUERY_ARG_MIME_TYPES);
+ private static final int MAX_RESULTS_NUMBER = 23;
+
private static String joinNewline(String... args) {
return TextUtils.join("\n", args);
}
@@ -375,62 +375,53 @@
}
/**
- * This method is similar to
- * {@link DocumentsProvider#queryChildDocuments(String, String[], String)}. This method returns
- * all children documents including hidden directories/files.
- *
- * <p>
- * In a scoped storage world, access to "Android/data" style directories are hidden for privacy
- * reasons. This method may show privacy sensitive data, so its usage should only be in
- * restricted modes.
- *
- * @param parentDocumentId the directory to return children for.
- * @param projection list of {@link Document} columns to put into the
- * cursor. If {@code null} all supported columns should be
- * included.
- * @param sortOrder how to order the rows, formatted as an SQL
- * {@code ORDER BY} clause (excluding the ORDER BY itself).
- * Passing {@code null} will use the default sort order, which
- * may be unordered. This ordering is a hint that can be used to
- * prioritize how data is fetched from the network, but UI may
- * always enforce a specific ordering
- * @throws FileNotFoundException when parent document doesn't exist or query fails
+ * WARNING: this method should really be {@code final}, but for the backward compatibility it's
+ * not; new classes that extend {@link FileSystemProvider} should override
+ * {@link #queryChildDocuments(String, String[], String, boolean)}, not this method.
*/
- protected Cursor queryChildDocumentsShowAll(
- String parentDocumentId, String[] projection, String sortOrder)
- throws FileNotFoundException {
- return queryChildDocuments(parentDocumentId, projection, sortOrder, File -> true);
- }
-
@Override
- public Cursor queryChildDocuments(
- String parentDocumentId, String[] projection, String sortOrder)
+ public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder)
throws FileNotFoundException {
- // Access to some directories is hidden for privacy reasons.
- return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow);
+ return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ false);
}
- private Cursor queryChildDocuments(
- String parentDocumentId, String[] projection, String sortOrder,
- @NonNull Predicate<File> filter) throws FileNotFoundException {
- final File parent = getFileForDocId(parentDocumentId);
- final MatrixCursor result = new DirectoryCursor(
- resolveProjection(projection), parentDocumentId, parent);
+ /**
+ * This method is similar to {@link #queryChildDocuments(String, String[], String)}, however, it
+ * could return <b>all</b> content of the directory, <b>including restricted (hidden)
+ * directories and files</b>.
+ * <p>
+ * In the scoped storage world, some directories and files (e.g. {@code Android/data/} and
+ * {@code Android/obb/} on the external storage) are hidden for privacy reasons.
+ * Hence, this method may reveal privacy-sensitive data, thus should be used with extra care.
+ */
+ @Override
+ public final Cursor queryChildDocumentsForManage(String documentId, String[] projection,
+ String sortOrder) throws FileNotFoundException {
+ return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ true);
+ }
- if (!filter.test(parent)) {
- Log.w(TAG, "No permission to access parentDocumentId: " + parentDocumentId);
+ protected Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder,
+ boolean includeHidden) throws FileNotFoundException {
+ final File parent = getFileForDocId(documentId);
+ final MatrixCursor result = new DirectoryCursor(
+ resolveProjection(projection), documentId, parent);
+
+ if (!parent.isDirectory()) {
+ Log.w(TAG, '"' + documentId + "\" is not a directory");
return result;
}
- if (parent.isDirectory()) {
- for (File file : FileUtils.listFilesOrEmpty(parent)) {
- if (filter.test(file)) {
- includeFile(result, null, file);
- }
- }
- } else {
- Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory");
+ if (!includeHidden && shouldHideDocument(documentId)) {
+ Log.w(TAG, "Queried directory \"" + documentId + "\" is hidden");
+ return result;
}
+
+ for (File file : FileUtils.listFilesOrEmpty(parent)) {
+ if (!includeHidden && shouldHideDocument(file)) continue;
+
+ includeFile(result, null, file);
+ }
+
return result;
}
@@ -452,23 +443,29 @@
*
* @see ContentResolver#EXTRA_HONORED_ARGS
*/
- protected final Cursor querySearchDocuments(
- File folder, String[] projection, Set<String> exclusion, Bundle queryArgs)
- throws FileNotFoundException {
+ protected final Cursor querySearchDocuments(File folder, String[] projection,
+ Set<String> exclusion, Bundle queryArgs) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(resolveProjection(projection));
- final List<File> pending = new ArrayList<>();
- pending.add(folder);
- while (!pending.isEmpty() && result.getCount() < 24) {
- final File file = pending.remove(0);
- if (shouldHide(file)) continue;
+
+ // We'll be a running a BFS here.
+ final Queue<File> pending = new ArrayDeque<>();
+ pending.offer(folder);
+
+ while (!pending.isEmpty() && result.getCount() < MAX_RESULTS_NUMBER) {
+ final File file = pending.poll();
+
+ // Skip hidden documents (both files and directories)
+ if (shouldHideDocument(file)) continue;
if (file.isDirectory()) {
for (File child : FileUtils.listFilesOrEmpty(file)) {
- pending.add(child);
+ pending.offer(child);
}
}
- if (!exclusion.contains(file.getAbsolutePath()) && matchSearchQueryArguments(file,
- queryArgs)) {
+
+ if (exclusion.contains(file.getAbsolutePath())) continue;
+
+ if (matchSearchQueryArguments(file, queryArgs)) {
includeFile(result, null, file);
}
}
@@ -612,26 +609,23 @@
final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS);
if (flagIndex != -1) {
+ final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR);
int flags = 0;
if (file.canWrite()) {
- if (mimeType.equals(Document.MIME_TYPE_DIR)) {
+ flags |= Document.FLAG_SUPPORTS_DELETE;
+ flags |= Document.FLAG_SUPPORTS_RENAME;
+ flags |= Document.FLAG_SUPPORTS_MOVE;
+ if (isDir) {
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
- flags |= Document.FLAG_SUPPORTS_DELETE;
- flags |= Document.FLAG_SUPPORTS_RENAME;
- flags |= Document.FLAG_SUPPORTS_MOVE;
-
- if (shouldBlockFromTree(docId)) {
- flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
- }
-
} else {
flags |= Document.FLAG_SUPPORTS_WRITE;
- flags |= Document.FLAG_SUPPORTS_DELETE;
- flags |= Document.FLAG_SUPPORTS_RENAME;
- flags |= Document.FLAG_SUPPORTS_MOVE;
}
}
+ if (isDir && shouldBlockDirectoryFromTree(docId)) {
+ flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
+ }
+
if (mimeType.startsWith("image/")) {
flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
}
@@ -664,22 +658,36 @@
return row;
}
- private static final Pattern PATTERN_HIDDEN_PATH = Pattern.compile(
- "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$");
+ /**
+ * Some providers may want to restrict access to certain directories and files,
+ * e.g. <i>"Android/data"</i> and <i>"Android/obb"</i> on the shared storage for
+ * privacy reasons.
+ * Such providers should override this method.
+ */
+ protected boolean shouldHideDocument(@NonNull String documentId)
+ throws FileNotFoundException {
+ return false;
+ }
/**
- * In a scoped storage world, access to "Android/data" style directories are
- * hidden for privacy reasons.
+ * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of
+ * a {@link String} {@code documentId}.
+ *
+ * @see #shouldHideDocument(String)
*/
- protected boolean shouldHide(@NonNull File file) {
- return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches());
+ protected final boolean shouldHideDocument(@NonNull File document)
+ throws FileNotFoundException {
+ return shouldHideDocument(getDocIdForFile(document));
}
- private boolean shouldShow(@NonNull File file) {
- return !shouldHide(file);
- }
-
- protected boolean shouldBlockFromTree(@NonNull String docId) {
+ /**
+ * @return if the directory that should be blocked from being selected when the user launches
+ * an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} intent.
+ *
+ * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE
+ */
+ protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId)
+ throws FileNotFoundException {
return false;
}
diff --git a/core/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
index 72a1bac..ca6c54d 100644
--- a/core/java/com/android/internal/jank/DisplayResolutionTracker.java
+++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
@@ -24,6 +24,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
@@ -147,7 +148,8 @@
public void registerDisplayListener(DisplayManager.DisplayListener listener) {
manager.registerDisplayListener(listener, handler,
DisplayManager.EVENT_FLAG_DISPLAY_ADDED
- | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED);
+ | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED,
+ ActivityThread.currentPackageName());
}
@Override
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 6e836e0..7e9cef7 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -40,6 +40,7 @@
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
@@ -273,7 +274,9 @@
public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82;
- private static final int LAST_CUJ = CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+ public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83;
+
+ private static final int LAST_CUJ = CUJ_LAUNCHER_UNFOLD_ANIM;
private static final int NO_STATSD_LOGGING = -1;
// Used to convert CujType to InteractionType enum value for statsd logging.
@@ -366,6 +369,7 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
}
private static class InstanceHolder {
@@ -468,6 +472,7 @@
CUJ_IME_INSETS_SHOW_ANIMATION,
CUJ_IME_INSETS_HIDE_ANIMATION,
CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
+ CUJ_LAUNCHER_UNFOLD_ANIM,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -1101,6 +1106,8 @@
return "IME_INSETS_HIDE_ANIMATION";
case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER:
return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER";
+ case CUJ_LAUNCHER_UNFOLD_ANIM:
+ return "LAUNCHER_UNFOLD_ANIM";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index d3103f1..0399430 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -37,7 +37,6 @@
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -78,7 +77,7 @@
private static final String TAG = "BatteryStatsHistory";
// Current on-disk Parcel version. Must be updated when the format of the parcelable changes
- private static final int VERSION = 209;
+ private static final int VERSION = 210;
private static final String HISTORY_DIR = "battery-history";
private static final String FILE_SUFFIX = ".bh";
@@ -194,10 +193,11 @@
private int mNextHistoryTagIdx = 0;
private int mNumHistoryTagChars = 0;
private int mHistoryBufferLastPos = -1;
- private long mLastHistoryElapsedRealtimeMs = 0;
private long mTrackRunningHistoryElapsedRealtimeMs = 0;
private long mTrackRunningHistoryUptimeMs = 0;
- private long mHistoryBaseTimeMs;
+ private final MonotonicClock mMonotonicClock;
+ // Monotonic time when we started writing to the history buffer
+ private long mHistoryBufferStartTime;
private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>();
private byte mLastHistoryStepLevel = 0;
private boolean mMutable = true;
@@ -307,23 +307,26 @@
* @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps
*/
public BatteryStatsHistory(File systemDir, int maxHistoryFiles, int maxHistoryBufferSize,
- HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+ MonotonicClock monotonicClock) {
this(Parcel.obtain(), systemDir, maxHistoryFiles, maxHistoryBufferSize,
- stepDetailsCalculator, clock, new TraceDelegate());
+ stepDetailsCalculator, clock, monotonicClock, new TraceDelegate());
initHistoryBuffer();
}
@VisibleForTesting
public BatteryStatsHistory(Parcel historyBuffer, File systemDir,
int maxHistoryFiles, int maxHistoryBufferSize,
- HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, TraceDelegate tracer) {
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+ MonotonicClock monotonicClock, TraceDelegate tracer) {
this(historyBuffer, systemDir, maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator,
- clock, tracer, null);
+ clock, monotonicClock, tracer, null);
}
private BatteryStatsHistory(Parcel historyBuffer, File systemDir,
int maxHistoryFiles, int maxHistoryBufferSize,
- HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, TraceDelegate tracer,
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+ MonotonicClock monotonicClock, TraceDelegate tracer,
BatteryStatsHistory writableHistory) {
mHistoryBuffer = historyBuffer;
mSystemDir = systemDir;
@@ -332,6 +335,7 @@
mStepDetailsCalculator = stepDetailsCalculator;
mTracer = tracer;
mClock = clock;
+ mMonotonicClock = monotonicClock;
mWritableHistory = writableHistory;
if (mWritableHistory != null) {
mMutable = false;
@@ -381,16 +385,18 @@
}
private BatteryHistoryFile makeBatteryHistoryFile() {
- return new BatteryHistoryFile(mHistoryDir, mClock.elapsedRealtime() + mHistoryBaseTimeMs);
+ return new BatteryHistoryFile(mHistoryDir, mMonotonicClock.monotonicTime());
}
public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
- HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+ MonotonicClock monotonicClock) {
mMaxHistoryFiles = maxHistoryFiles;
mMaxHistoryBufferSize = maxHistoryBufferSize;
mStepDetailsCalculator = stepDetailsCalculator;
mTracer = new TraceDelegate();
mClock = clock;
+ mMonotonicClock = monotonicClock;
mHistoryBuffer = Parcel.obtain();
mSystemDir = null;
@@ -417,16 +423,16 @@
mHistoryBuffer = Parcel.obtain();
mHistoryBuffer.unmarshall(historyBlob, 0, historyBlob.length);
+ mMonotonicClock = null;
readFromParcel(parcel, true /* useBlobs */);
}
private void initHistoryBuffer() {
- mHistoryBaseTimeMs = 0;
- mLastHistoryElapsedRealtimeMs = 0;
mTrackRunningHistoryElapsedRealtimeMs = 0;
mTrackRunningHistoryUptimeMs = 0;
mWrittenPowerStatsDescriptors.clear();
+ mHistoryBufferStartTime = mMonotonicClock.monotonicTime();
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
@@ -466,7 +472,7 @@
historyBufferCopy.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
return new BatteryStatsHistory(historyBufferCopy, mSystemDir, 0, 0, null, null, null,
- this);
+ null, this);
}
}
@@ -491,7 +497,7 @@
* When {@link #mHistoryBuffer} reaches {@link BatteryStatsImpl.Constants#MAX_HISTORY_BUFFER},
* create next history file.
*/
- public void startNextFile() {
+ public void startNextFile(long elapsedRealtimeMs) {
if (mMaxHistoryFiles == 0) {
Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history");
return;
@@ -517,6 +523,7 @@
Slog.e(TAG, "Could not create history file: " + mActiveFile.getBaseFile());
}
+ mHistoryBufferStartTime = mMonotonicClock.monotonicTime(elapsedRealtimeMs);
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
@@ -699,9 +706,12 @@
if (mHistoryParcels != null) {
while (mParcelIndex < mHistoryParcels.size()) {
final Parcel p = mHistoryParcels.get(mParcelIndex++);
- if (!skipHead(p)) {
+ if (!verifyVersion(p)) {
continue;
}
+ // skip monotonic time field.
+ p.readLong();
+
final int bufSize = p.readInt();
final int curPos = p.dataPosition();
mCurrentParcelEnd = curPos + bufSize;
@@ -745,24 +755,36 @@
}
out.unmarshall(raw, 0, raw.length);
out.setDataPosition(0);
- return skipHead(out);
+ if (!verifyVersion(out)) {
+ return false;
+ }
+ // skip monotonic time field.
+ out.readLong();
+ return true;
}
/**
- * Skip the header part of history parcel.
+ * Verify header part of history parcel.
*
- * @param p history parcel to skip head.
* @return true if version match, false if not.
*/
- private boolean skipHead(Parcel p) {
+ private boolean verifyVersion(Parcel p) {
p.setDataPosition(0);
final int version = p.readInt();
- if (version != VERSION) {
- return false;
- }
- // skip historyBaseTime field.
- p.readLong();
- return true;
+ return version == VERSION;
+ }
+
+ /**
+ * Extracts the monotonic time, as per {@link MonotonicClock}, from the supplied battery history
+ * buffer.
+ */
+ public long getHistoryBufferStartTime(Parcel p) {
+ int pos = p.dataPosition();
+ p.setDataPosition(0);
+ p.readInt(); // Skip the version field
+ long monotonicTime = p.readLong();
+ p.setDataPosition(pos);
+ return monotonicTime;
}
/**
@@ -1438,7 +1460,8 @@
throw new ConcurrentModificationException("Battery history is not writable");
}
- final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
+ final long timeDiffMs = mMonotonicClock.monotonicTime(elapsedRealtimeMs)
+ - mHistoryLastWritten.time;
final int diffStates = mHistoryLastWritten.states ^ cur.states;
final int diffStates2 = mHistoryLastWritten.states2 ^ cur.states2;
final int lastDiffStates = mHistoryLastWritten.states ^ mHistoryLastLastWritten.states;
@@ -1476,7 +1499,9 @@
mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
mHistoryBufferLastPos = -1;
- elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs;
+
+ elapsedRealtimeMs -= timeDiffMs;
+
// If the last written history had a wakelock tag, we need to retain it.
// Note that the condition above made sure that we aren't in a case where
// both it and the current history item have a wakelock tag.
@@ -1513,7 +1538,7 @@
HistoryItem copy = new HistoryItem();
copy.setTo(cur);
- startNextFile();
+ startNextFile(elapsedRealtimeMs);
// startRecordingHistory will reset mHistoryCur.
startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
@@ -1548,7 +1573,7 @@
mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
mHistoryLastLastWritten.setTo(mHistoryLastWritten);
final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
- mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
+ mHistoryLastWritten.setTo(mMonotonicClock.monotonicTime(elapsedRealtimeMs), cmd, cur);
if (mHistoryLastWritten.time < mHistoryLastLastWritten.time - 60000) {
Slog.wtf(TAG, "Significantly earlier event written to battery history:"
+ " time=" + mHistoryLastWritten.time
@@ -1556,7 +1581,6 @@
}
mHistoryLastWritten.tagsFirstOccurrence = hasTags;
writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
- mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
cur.wakelockTag = null;
cur.wakeReasonTag = null;
cur.eventCode = HistoryItem.EVENT_NONE;
@@ -1926,6 +1950,10 @@
return;
}
+ // Save the monotonic time first, so that even if the history write below fails,
+ // we still wouldn't end up with overlapping history timelines.
+ mMonotonicClock.write();
+
Parcel p = Parcel.obtain();
try {
final long start = SystemClock.uptimeMillis();
@@ -1951,8 +1979,7 @@
return;
}
- final long historyBaseTime = in.readLong();
-
+ mHistoryBufferStartTime = in.readLong();
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
@@ -1972,39 +1999,11 @@
mHistoryBuffer.appendFrom(in, curPos, bufSize);
in.setDataPosition(curPos + bufSize);
}
-
- mHistoryBaseTimeMs = historyBaseTime;
- if (DEBUG) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** NEW mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
-
- if (mHistoryBaseTimeMs > 0) {
- long elapsedRealtimeMs = mClock.elapsedRealtime();
- mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
- mHistoryBaseTimeMs = mHistoryBaseTimeMs - elapsedRealtimeMs + 1;
- if (DEBUG) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** ADJUSTED mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
- }
}
private void writeHistoryBuffer(Parcel out) {
- if (DEBUG) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** WRITING mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- sb.append(" mLastHistoryElapsedRealtimeMs: ");
- TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
out.writeInt(BatteryStatsHistory.VERSION);
- out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs);
+ out.writeLong(mHistoryBufferStartTime);
out.writeInt(mHistoryBuffer.dataSize());
if (DEBUG) {
Slog.i(TAG, "***************** WRITING HISTORY: "
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index a5d2d0f..6bd5898 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -16,7 +16,6 @@
package com.android.internal.os;
-import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import android.os.BatteryManager;
import android.os.BatteryStats;
@@ -34,8 +33,8 @@
private static final boolean DEBUG = false;
private static final String TAG = "BatteryStatsHistoryItr";
private final BatteryStatsHistory mBatteryStatsHistory;
- private final @CurrentTimeMillisLong long mStartTimeMs;
- private final @CurrentTimeMillisLong long mEndTimeMs;
+ private final long mStartTimeMs;
+ private final long mEndTimeMs;
private final BatteryStats.HistoryStepDetails mReadHistoryStepDetails =
new BatteryStats.HistoryStepDetails();
private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>();
@@ -43,10 +42,10 @@
new PowerStats.DescriptorRegistry();
private BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
private boolean mNextItemReady;
+ private boolean mTimeInitialized;
- public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history,
- @CurrentTimeMillisLong long startTimeMs,
- @CurrentTimeMillisLong long endTimeMs) {
+ public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, long startTimeMs,
+ long endTimeMs) {
mBatteryStatsHistory = history;
mStartTimeMs = startTimeMs;
mEndTimeMs = (endTimeMs != 0) ? endTimeMs : Long.MAX_VALUE;
@@ -82,7 +81,12 @@
break;
}
- final long lastRealtimeMs = mHistoryItem.time;
+ if (!mTimeInitialized) {
+ mHistoryItem.time = mBatteryStatsHistory.getHistoryBufferStartTime(p);
+ mTimeInitialized = true;
+ }
+
+ final long lastMonotonicTimeMs = mHistoryItem.time;
final long lastWalltimeMs = mHistoryItem.currentTime;
try {
readHistoryDelta(p, mHistoryItem);
@@ -93,12 +97,13 @@
if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
&& mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET
&& lastWalltimeMs != 0) {
- mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs);
+ mHistoryItem.currentTime =
+ lastWalltimeMs + (mHistoryItem.time - lastMonotonicTimeMs);
}
- if (mEndTimeMs != 0 && mHistoryItem.currentTime >= mEndTimeMs) {
+ if (mEndTimeMs != 0 && mHistoryItem.time >= mEndTimeMs) {
break;
}
- if (mHistoryItem.currentTime >= mStartTimeMs) {
+ if (mHistoryItem.time >= mStartTimeMs) {
mNextItemReady = true;
return;
}
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index 5ea6ba8..1f44b33 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -191,6 +191,27 @@
}
/**
+ * Sets the new values for the given state.
+ */
+ public void setValues(int state, long[] values) {
+ if (state < 0 || state >= mStateCount) {
+ throw new IllegalArgumentException(
+ "State: " + state + ", outside the range: [0-" + (mStateCount - 1) + "]");
+ }
+ if (values.length != mLength) {
+ throw new IllegalArgumentException(
+ "Invalid array length: " + values.length + ", expected: " + mLength);
+ }
+ LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
+ if (container == null || container.mLength != values.length) {
+ container = new LongArrayContainer(values.length);
+ }
+ container.setValues(values);
+ native_setValues(mNativeObject, state, container.mNativeObject);
+ sTmpArrayContainer.set(container);
+ }
+
+ /**
* Sets the new values. The delta between the previously set values and these values
* is distributed among the state according to the time the object spent in those states
* since the previous call to updateValues.
@@ -317,6 +338,10 @@
private static native void native_setState(long nativeObject, int state, long timestampMs);
@CriticalNative
+ private static native void native_setValues(long nativeObject, int state,
+ long longArrayContainerNativeObject);
+
+ @CriticalNative
private static native void native_updateValues(long nativeObject,
long longArrayContainerNativeObject, long timestampMs);
diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java
new file mode 100644
index 0000000..6f114e3
--- /dev/null
+++ b/core/java/com/android/internal/os/MonotonicClock.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.annotation.NonNull;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * A clock that is similar to SystemClock#elapsedRealtime(), except that it is not reset
+ * on reboot, but keeps going.
+ */
+public class MonotonicClock {
+ private static final String TAG = "MonotonicClock";
+
+ private static final String XML_TAG_MONOTONIC_TIME = "monotonic_time";
+ private static final String XML_ATTR_TIMESHIFT = "timeshift";
+
+ private final AtomicFile mFile;
+ private final Clock mClock;
+ private long mTimeshift;
+
+ public MonotonicClock(File file) {
+ mFile = new AtomicFile(file);
+ mClock = Clock.SYSTEM_CLOCK;
+ read();
+ }
+
+ public MonotonicClock(long monotonicTime, @NonNull Clock clock) {
+ mClock = clock;
+ mFile = null;
+ mTimeshift = monotonicTime - mClock.elapsedRealtime();
+ }
+
+ /**
+ * Returns time in milliseconds, based on SystemClock.elapsedTime, adjusted so that
+ * after a device reboot the time keeps increasing.
+ */
+ public long monotonicTime() {
+ return monotonicTime(mClock.elapsedRealtime());
+ }
+
+ /**
+ * Like {@link #monotonicTime()}, except the elapsed time is supplied as an argument instead
+ * of being read from the Clock.
+ */
+ public long monotonicTime(long elapsedRealtimeMs) {
+ return mTimeshift + elapsedRealtimeMs;
+ }
+
+ private void read() {
+ if (!mFile.exists()) {
+ return;
+ }
+
+ try {
+ readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e);
+ }
+ }
+
+ /**
+ * Saves the timeshift into a file. Call this method just before system shutdown, after
+ * writing the last battery history event.
+ */
+ public void write() {
+ if (mFile == null) {
+ return;
+ }
+
+ mFile.write(out -> {
+ try {
+ writeXml(out, Xml.newBinarySerializer());
+ out.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e);
+ }
+ });
+ }
+
+ /**
+ * Parses an XML file containing the persistent state of the monotonic clock.
+ */
+ @VisibleForTesting
+ public void readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
+ long savedTimeshift = 0;
+ try {
+ parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG
+ && parser.getName().equals(XML_TAG_MONOTONIC_TIME)) {
+ savedTimeshift = parser.getAttributeLong(null, XML_ATTR_TIMESHIFT);
+ }
+ eventType = parser.next();
+ }
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ mTimeshift = savedTimeshift - mClock.elapsedRealtime();
+ }
+
+ /**
+ * Creates an XML file containing the persistent state of the monotonic clock.
+ */
+ @VisibleForTesting
+ public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
+ serializer.setOutput(out, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ serializer.startTag(null, XML_TAG_MONOTONIC_TIME);
+ serializer.attributeLong(null, XML_ATTR_TIMESHIFT, monotonicTime());
+ serializer.endTag(null, XML_TAG_MONOTONIC_TIME);
+ serializer.endDocument();
+ }
+}
diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/core/java/com/android/internal/os/MultiStateStats.java
index f971849..dc5055a 100644
--- a/core/java/com/android/internal/os/MultiStateStats.java
+++ b/core/java/com/android/internal/os/MultiStateStats.java
@@ -16,9 +16,17 @@
package com.android.internal.os;
+import android.util.Slog;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -29,15 +37,21 @@
* values;
*/
public class MultiStateStats {
+ private static final String TAG = "MultiStateStats";
+
+ private static final String XML_TAG_STATS = "stats";
+
/**
* A set of states, e.g. on-battery, screen-on, procstate. The state values are integers
* from 0 to States.mLabels.length
*/
public static class States {
+ final String mName;
final boolean mTracked;
final String[] mLabels;
- public States(boolean tracked, String... labels) {
+ public States(String name, boolean tracked, String... labels) {
+ mName = name;
this.mTracked = tracked;
this.mLabels = labels;
}
@@ -155,11 +169,28 @@
>>> mStateBitFieldShifts[stateIndex];
}
- private int setStateInComposite(int baseCompositeState, int stateIndex, int value) {
+ int setStateInComposite(int baseCompositeState, int stateIndex, int value) {
return (baseCompositeState & ~mStateBitFieldMasks[stateIndex])
| (value << mStateBitFieldShifts[stateIndex]);
}
+ int setStateInComposite(int compositeState, String stateName, String stateLabel) {
+ for (int stateIndex = 0; stateIndex < mStates.length; stateIndex++) {
+ States stateConfig = mStates[stateIndex];
+ if (stateConfig.mName.equals(stateName)) {
+ for (int state = 0; state < stateConfig.mLabels.length; state++) {
+ if (stateConfig.mLabels[state].equals(stateLabel)) {
+ return setStateInComposite(compositeState, stateIndex, state);
+ }
+ }
+ Slog.e(TAG, "Unexpected label '" + stateLabel + "' for state: " + stateName);
+ return -1;
+ }
+ }
+ Slog.e(TAG, "Unsupported state: " + stateName);
+ return -1;
+ }
+
/**
* Allocates a new stats container using this Factory's configuration.
*/
@@ -195,6 +226,10 @@
}
return serialState;
}
+
+ int getSerialState(int compositeState) {
+ return mCompositeToSerialState[compositeState];
+ }
}
private final Factory mFactory;
@@ -254,6 +289,106 @@
}
/**
+ * Stores contents in an XML doc.
+ */
+ public void writeXml(TypedXmlSerializer serializer) throws IOException {
+ long[] tmpArray = new long[mCounter.getArrayLength()];
+ writeXmlAllStates(serializer, new int[mFactory.mStates.length], 0, tmpArray);
+ }
+
+ private void writeXmlAllStates(TypedXmlSerializer serializer, int[] states, int stateIndex,
+ long[] values) throws IOException {
+ if (stateIndex < states.length) {
+ if (!mFactory.mStates[stateIndex].mTracked) {
+ writeXmlAllStates(serializer, states, stateIndex + 1, values);
+ return;
+ }
+
+ for (int i = 0; i < mFactory.mStates[stateIndex].mLabels.length; i++) {
+ states[stateIndex] = i;
+ writeXmlAllStates(serializer, states, stateIndex + 1, values);
+ }
+ return;
+ }
+
+ mCounter.getCounts(values, mFactory.getSerialState(states));
+ boolean nonZero = false;
+ for (long value : values) {
+ if (value != 0) {
+ nonZero = true;
+ break;
+ }
+ }
+ if (!nonZero) {
+ return;
+ }
+
+ serializer.startTag(null, XML_TAG_STATS);
+
+ for (int i = 0; i < states.length; i++) {
+ if (mFactory.mStates[i].mTracked && states[i] != 0) {
+ serializer.attribute(null, mFactory.mStates[i].mName,
+ mFactory.mStates[i].mLabels[states[i]]);
+ }
+ }
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] != 0) {
+ serializer.attributeLong(null, "_" + i, values[i]);
+ }
+ }
+ serializer.endTag(null, XML_TAG_STATS);
+ }
+
+ /**
+ * Populates the object with contents in an XML doc. The parser is expected to be
+ * positioned on the opening tag of the corresponding element.
+ */
+ public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException,
+ IOException {
+ String outerTag = parser.getName();
+ long[] tmpArray = new long[mCounter.getArrayLength()];
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG && parser.getName().equals(outerTag))) {
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals(XML_TAG_STATS)) {
+ Arrays.fill(tmpArray, 0);
+ int compositeState = 0;
+ int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; i++) {
+ String attributeName = parser.getAttributeName(i);
+ if (attributeName.startsWith("_")) {
+ int index;
+ try {
+ index = Integer.parseInt(attributeName.substring(1));
+ } catch (NumberFormatException e) {
+ throw new XmlPullParserException(
+ "Unexpected index syntax: " + attributeName, parser, e);
+ }
+ if (index < 0 || index >= tmpArray.length) {
+ Slog.e(TAG, "State index out of bounds: " + index
+ + " length: " + tmpArray.length);
+ return false;
+ }
+ tmpArray[index] = parser.getAttributeLong(i);
+ } else {
+ String attributeValue = parser.getAttributeValue(i);
+ compositeState = mFactory.setStateInComposite(compositeState,
+ attributeName, attributeValue);
+ if (compositeState == -1) {
+ return false;
+ }
+ }
+ }
+ mCounter.setValues(mFactory.getSerialState(compositeState), tmpArray);
+ }
+ }
+ eventType = parser.next();
+ }
+ return true;
+ }
+
+ /**
* Prints the accumulated stats, one line of every combination of states that has data.
*/
public void dump(PrintWriter pw) {
diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS
index e35b7f1..996e424 100644
--- a/core/java/com/android/internal/os/OWNERS
+++ b/core/java/com/android/internal/os/OWNERS
@@ -5,14 +5,10 @@
per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
# BatteryStats
-per-file BatterySipper.java = file:/BATTERY_STATS_OWNERS
per-file BatteryStats* = file:/BATTERY_STATS_OWNERS
-per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS
-per-file *ChargeCalculator* = file:/BATTERY_STATS_OWNERS
-per-file *PowerCalculator* = file:/BATTERY_STATS_OWNERS
-per-file *PowerEstimator* = file:/BATTERY_STATS_OWNERS
-per-file *PowerStats* = file:/BATTERY_STATS_OWNERS
per-file *Kernel* = file:/BATTERY_STATS_OWNERS
+per-file *Clock* = file:/BATTERY_STATS_OWNERS
per-file *MultiState* = file:/BATTERY_STATS_OWNERS
per-file *PowerProfile* = file:/BATTERY_STATS_OWNERS
+per-file *PowerStats* = file:/BATTERY_STATS_OWNERS
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 8f66d1f..1130a45 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -24,9 +24,16 @@
import android.os.PersistableBundle;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
-import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
@@ -61,6 +68,13 @@
* to adjust the algorithm in accordance with the stats available on the device.
*/
public static class Descriptor {
+ public static final String XML_TAG_DESCRIPTOR = "descriptor";
+ private static final String XML_ATTR_ID = "id";
+ private static final String XML_ATTR_NAME = "name";
+ private static final String XML_ATTR_STATS_ARRAY_LENGTH = "stats-array-length";
+ private static final String XML_ATTR_UID_STATS_ARRAY_LENGTH = "uid-stats-array-length";
+ private static final String XML_TAG_EXTRAS = "extras";
+
/**
* {@link BatteryConsumer.PowerComponent} (e.g. CPU, WIFI etc) that this snapshot relates
* to; or a custom power component ID (if the value
@@ -126,7 +140,7 @@
int firstWord = parcel.readInt();
int version = (firstWord & PARCEL_FORMAT_VERSION_MASK) >>> PARCEL_FORMAT_VERSION_SHIFT;
if (version != PARCEL_FORMAT_VERSION) {
- Log.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
+ Slog.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
+ "changed from " + version + " to " + PARCEL_FORMAT_VERSION);
return null;
}
@@ -155,6 +169,71 @@
that.extras); // Since the Parcel is now unparceled, do a deep comparison
}
+ /**
+ * Stores contents in an XML doc.
+ */
+ public void writeXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(null, XML_TAG_DESCRIPTOR);
+ serializer.attributeInt(null, XML_ATTR_ID, powerComponentId);
+ serializer.attribute(null, XML_ATTR_NAME, name);
+ serializer.attributeInt(null, XML_ATTR_STATS_ARRAY_LENGTH, statsArrayLength);
+ serializer.attributeInt(null, XML_ATTR_UID_STATS_ARRAY_LENGTH, uidStatsArrayLength);
+ try {
+ serializer.startTag(null, XML_TAG_EXTRAS);
+ extras.saveToXml(serializer);
+ serializer.endTag(null, XML_TAG_EXTRAS);
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ serializer.endTag(null, XML_TAG_DESCRIPTOR);
+ }
+
+ /**
+ * Creates a Descriptor by parsing an XML doc. The parser is expected to be positioned
+ * on or before the opening "descriptor" tag.
+ */
+ public static Descriptor createFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int powerComponentId = -1;
+ String name = null;
+ int statsArrayLength = 0;
+ int uidStatsArrayLength = 0;
+ PersistableBundle extras = null;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG
+ && parser.getName().equals(XML_TAG_DESCRIPTOR))) {
+ if (eventType == XmlPullParser.START_TAG) {
+ switch (parser.getName()) {
+ case XML_TAG_DESCRIPTOR:
+ powerComponentId = parser.getAttributeInt(null, XML_ATTR_ID);
+ name = parser.getAttributeValue(null, XML_ATTR_NAME);
+ statsArrayLength = parser.getAttributeInt(null,
+ XML_ATTR_STATS_ARRAY_LENGTH);
+ uidStatsArrayLength = parser.getAttributeInt(null,
+ XML_ATTR_UID_STATS_ARRAY_LENGTH);
+ break;
+ case XML_TAG_EXTRAS:
+ extras = PersistableBundle.restoreFromXml(parser);
+ break;
+ }
+ }
+ eventType = parser.next();
+ }
+ if (powerComponentId == -1) {
+ return null;
+ } else if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
+ return new Descriptor(powerComponentId, name, statsArrayLength, uidStatsArrayLength,
+ extras);
+ } else if (powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT) {
+ return new Descriptor(powerComponentId, statsArrayLength, uidStatsArrayLength,
+ extras);
+ } else {
+ Slog.e(TAG, "Unrecognized power component: " + powerComponentId);
+ return null;
+ }
+ }
+
@Override
public int hashCode() {
return Objects.hash(powerComponentId);
@@ -259,7 +338,7 @@
Descriptor descriptor = registry.get(powerComponentId);
if (descriptor == null) {
- Log.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);
+ Slog.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);
return null;
}
PowerStats stats = new PowerStats(descriptor);
@@ -274,7 +353,7 @@
stats.uidStats.put(uid, uidStats);
}
if (parcel.dataPosition() != endPos) {
- Log.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
+ Slog.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
+ ", actual length: " + (parcel.dataPosition() - startPos));
return null;
}
diff --git a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
index 6fd5018..504928c 100644
--- a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
+++ b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
@@ -74,16 +74,15 @@
return windowSwipeToDismiss;
}
- private boolean isPointerIndexValid(MotionEvent ev) {
+ private int getIndexForValidPointer(MotionEvent ev) {
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
if (DEBUG) {
Log.e(TAG, "Invalid pointer index: ignoring.");
}
mDiscardIntercept = true;
- return false;
}
- return true;
+ return pointerIndex;
}
private void updateSwiping(MotionEvent ev) {
@@ -98,7 +97,7 @@
}
}
- private void updateDiscardIntercept(MotionEvent ev) {
+ private void updateDiscardIntercept(MotionEvent ev, int pointerIndex) {
if (!mSwiping) {
// Don't look at canScroll until we have passed the touch slop
return;
@@ -107,8 +106,8 @@
return;
}
final boolean checkLeft = mDownX < ev.getRawX();
- final float x = ev.getX(mActivePointerId);
- final float y = ev.getY(mActivePointerId);
+ final float x = ev.getX(pointerIndex);
+ final float y = ev.getY(pointerIndex);
if (canScroll(mInstalledDecorView, false, checkLeft, x, y)) {
mDiscardIntercept = true;
}
@@ -152,11 +151,12 @@
if (mDiscardIntercept) {
break;
}
- if (!isPointerIndexValid(ev)) {
+ final int pointerIndex = getIndexForValidPointer(ev);
+ if (pointerIndex == -1) {
break;
}
updateSwiping(ev);
- updateDiscardIntercept(ev);
+ updateDiscardIntercept(ev, pointerIndex);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index ec525f0..4bb7c33 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -91,6 +91,8 @@
WM_DEBUG_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
"CoreBackPreview"),
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),
TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
private final boolean mEnabled;
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 4065055..8236783 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -108,4 +108,5 @@
boolean removeWeakEscrowToken(long handle, int userId);
boolean isWeakEscrowTokenActive(long handle, int userId);
boolean isWeakEscrowTokenValid(long handle, in byte[] token, int userId);
+ void unlockUserKeyIfUnsecured(int userId);
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index d5b8f62..a3e2706 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1933,8 +1933,23 @@
}
}
+ /**
+ * Unlocks the credential-encrypted storage for the given user if the user is not secured, i.e.
+ * doesn't have an LSKF.
+ * <p>
+ * Whether the storage has been unlocked can be determined by
+ * {@link StorageManager#isUserKeyUnlocked()}.
+ *
+ * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+ *
+ * @param userId the ID of the user whose storage to unlock
+ */
public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
- getLockSettingsInternal().unlockUserKeyIfUnsecured(userId);
+ try {
+ getLockSettings().unlockUserKeyIfUnsecured(userId);
+ } catch (RemoteException re) {
+ re.rethrowFromSystemServer();
+ }
}
public void createNewUser(@UserIdInt int userId, int userSerialNumber) {
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 6063c90..8114e1f 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -60,17 +60,6 @@
public abstract void onThirdPartyAppsStarted();
/**
- * Unlocks the credential-encrypted storage for the given user if the user is not secured, i.e.
- * doesn't have an LSKF.
- * <p>
- * This doesn't throw an exception on failure; whether the storage has been unlocked can be
- * determined by {@link StorageManager#isUserKeyUnlocked()}.
- *
- * @param userId the ID of the user whose storage to unlock
- */
- public abstract void unlockUserKeyIfUnsecured(@UserIdInt int userId);
-
- /**
* Creates the locksettings state for a new user.
* <p>
* This includes creating a synthetic password and protecting it with an empty LSKF.
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b5d70d3..50253cf 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -1315,7 +1315,16 @@
ALOGI("VM exiting with result code %d.", code);
onExit(code);
}
+
+#ifdef __ANDROID_CLANG_COVERAGE__
+ // When compiled with coverage, a function is registered with atexit to call
+ // `__llvm_profile_write_file` when the process exit.
+ // For Clang code coverage to work, call exit instead of _exit to run hooks
+ // registered with atexit.
+ ::exit(code);
+#else
::_exit(code);
+#endif
}
void AndroidRuntime::onVmCreated(JNIEnv* env)
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index deb138f..9c883d1 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -265,6 +265,18 @@
return mgr->isDataInjectionEnabled();
}
+static jboolean nativeIsReplayDataInjectionEnabled(JNIEnv *_env, jclass _this,
+ jlong sensorManager) {
+ SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+ return mgr->isReplayDataInjectionEnabled();
+}
+
+static jboolean nativeIsHalBypassReplayDataInjectionEnabled(JNIEnv *_env, jclass _this,
+ jlong sensorManager) {
+ SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+ return mgr->isHalBypassReplayDataInjectionEnabled();
+}
+
static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
jint deviceId, jlong size, jint channelType, jint fd,
jobject hardwareBufferObj) {
@@ -533,6 +545,11 @@
{"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled},
+ {"nativeIsReplayDataInjectionEnabled", "(J)Z", (void *)nativeIsReplayDataInjectionEnabled},
+
+ {"nativeIsHalBypassReplayDataInjectionEnabled", "(J)Z",
+ (void *)nativeIsHalBypassReplayDataInjectionEnabled},
+
{"nativeCreateDirectChannel", "(JIJIILandroid/hardware/HardwareBuffer;)I",
(void *)nativeCreateDirectChannel},
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index ba644eb..178c0d0 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -963,10 +963,11 @@
}
static void nativeSetFrameRateCategory(JNIEnv* env, jclass clazz, jlong transactionObj,
- jlong nativeObject, jint category) {
+ jlong nativeObject, jint category,
+ jboolean smoothSwitchOnly) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
const auto ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
- transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category));
+ transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category), smoothSwitchOnly);
}
static void nativeSetFrameRateSelectionStrategy(JNIEnv* env, jclass clazz, jlong transactionObj,
@@ -2181,7 +2182,7 @@
(void*)nativeSetFrameRate },
{"nativeSetDefaultFrameRateCompatibility", "(JJI)V",
(void*)nativeSetDefaultFrameRateCompatibility},
- {"nativeSetFrameRateCategory", "(JJI)V",
+ {"nativeSetFrameRateCategory", "(JJIZ)V",
(void*)nativeSetFrameRateCategory},
{"nativeSetFrameRateSelectionStrategy", "(JJI)V",
(void*)nativeSetFrameRateSelectionStrategy},
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index 6920211..1f29735 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -55,6 +55,15 @@
counter->setState(state, timestamp);
}
+static void native_setValues(jlong nativePtr, jint state, jlong longArrayContainerNativePtr) {
+ battery::LongArrayMultiStateCounter *counter =
+ reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ std::vector<uint64_t> *vector =
+ reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
+
+ counter->setValue(state, *vector);
+}
+
static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativePtr,
jlong timestamp) {
battery::LongArrayMultiStateCounter *counter =
@@ -210,6 +219,8 @@
// @CriticalNative
{"native_setState", "(JIJ)V", (void *)native_setState},
// @CriticalNative
+ {"native_setValues", "(JIJ)V", (void *)native_setValues},
+ // @CriticalNative
{"native_updateValues", "(JJJ)V", (void *)native_updateValues},
// @CriticalNative
{"native_incrementValues", "(JJJ)V", (void *)native_incrementValues},
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index eb14db0..068f4dd 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -115,6 +115,9 @@
// Only set if the app defined a monochrome icon.
optional string monochrome_icon_bitmap_path = 3;
+
+ // The component name of the original activity (pre-archival).
+ optional string original_component_name = 4;
}
/** Information about main activities. */
diff --git a/core/res/Android.bp b/core/res/Android.bp
index b71995f..4e686b7 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -104,6 +104,7 @@
android_app {
name: "framework-res",
+ use_resource_processor: false,
sdk_version: "core_platform",
certificate: "platform",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ca768ad..6daa5b9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5668,7 +5668,8 @@
android:description="@string/permdesc_deliverCompanionMessages"
android:protectionLevel="normal" />
- <!-- @hide @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)
+ <!-- @hide @FlaggedApi("android.companion.flags.companion_transport_apis")
+ @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)
Allows an application to send and receive messages via CDM transports.
-->
<permission android:name="android.permission.USE_COMPANION_TRANSPORTS"
@@ -6122,7 +6123,9 @@
android:protectionLevel="signature|privileged|development|appop|retailDemo" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
- <!-- @SystemApi @hide Allows trusted system components to report events to UsageStatsManager -->
+ <!-- @SystemApi @hide
+ @FlaggedApi("android.app.usage.report_usage_stats_permission")
+ Allows trusted system components to report events to UsageStatsManager -->
<permission android:name="android.permission.REPORT_USAGE_STATS"
android:protectionLevel="signature|module" />
@@ -7141,7 +7144,7 @@
{@link ActivityOptions#makeRemoteAnimation}
@hide <p>Not for use by third-party applications. -->
<permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|recents" />
<!-- Allows an application to watch changes and/or active state of app ops.
@hide <p>Not for use by third-party applications. -->
@@ -7235,13 +7238,23 @@
<!-- @SystemApi Required for the privileged assistant apps targeting
{@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
- that receive voice trigger from the trusted hotword detection service.
+ that receive voice trigger from a sandboxed {@link HotwordDetectionService}.
<p>Protection level: signature|privileged|appop
@FlaggedApi("android.permission.flags.voice_activation_permission_apis")
@hide -->
<permission android:name="android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO"
android:protectionLevel="signature|privileged|appop" />
+ <!-- @SystemApi Required for the privileged assistant apps targeting
+ {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
+ that receive training data from a sandboxed {@link HotwordDetectionService} or
+ {@link VisualQueryDetectionService}.
+ <p>Protection level: internal|appop
+ @FlaggedApi("android.permission.flags.voice_activation_permission_apis")
+ @hide -->
+ <permission android:name="android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA"
+ android:protectionLevel="internal|appop" />
+
<!-- @SystemApi Allows requesting the framework broadcast the
{@link Intent#ACTION_DEVICE_CUSTOMIZATION_READY} intent.
@hide -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 04fd70a..3496994 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -10090,6 +10090,15 @@
<!-- Perceptual luminance of a color, in accessibility friendly color space. From 0 to 100. -->
<attr name="lStar" format="float"/>
+ <!-- The attributes of the {@code <locale-config>} tag. -->
+ <!-- @FlaggedApi("android.content.res.default_locale") -->
+ <declare-styleable name="LocaleConfig">
+ <!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a>
+ the strings in values/strings.xml (the default strings in the directory with no locale
+ qualifier) are in. -->
+ <attr name="defaultLocale" format="string"/>
+ </declare-styleable>
+
<!-- The attributes of the {@code <locale>} tag within {@code <locale-config>}. -->
<declare-styleable name="LocaleConfig_Locale">
<!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b211ac2..5a1f2d1a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3988,6 +3988,13 @@
limit is unknown. -->
<item name="config_hapticChannelMaxVibrationAmplitude" format="float" type="dimen">0</item>
+ <!-- The fixed keyboard vibration strength in [0,1], or -1 to indicate the strength not fixed
+ and should depend on the touch feedback intensity user setting -->
+ <item name="config_keyboardHapticFeedbackFixedAmplitude" format="float" type="dimen">-1</item>
+
+ <!-- The default value for keyboard vibration toggle in settings. -->
+ <bool name="config_defaultKeyboardVibrationEnabled">true</bool>
+
<!-- If the device should still vibrate even in low power mode, for certain priority vibrations
(e.g. accessibility, alarms). This is mainly for Wear devices that don't have speakers. -->
<bool name="config_allowPriorityVibrationsInLowPowerMode">false</bool>
@@ -5541,6 +5548,14 @@
<!-- Add packages here -->
</string-array>
+ <!-- Enable pause wallpaper rendering upon state change such as app launch -->
+ <bool name="config_pauseWallpaperRenderWhenStateChangeEnabled">false</bool>
+
+ <!-- The list of packages to pause wallpaper rendering upon state change such as app launch -->
+ <string-array name="pause_wallpaper_render_when_state_change" translatable="false">
+ <!-- Add packages here -->
+ </string-array>
+
<!-- Whether or not to hide the navigation bar when the soft keyboard is visible in order to
create additional screen real estate outside beyond the keyboard. Note that the user needs
to have a confirmed way to dismiss the keyboard when desired. -->
diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml
index 8fb48bc..e42962c 100644
--- a/core/res/res/values/config_battery_stats.xml
+++ b/core/res/res/values/config_battery_stats.xml
@@ -31,4 +31,13 @@
is a relatively expensive operation, this throttle period may need to be adjusted for low-power
devices-->
<integer name="config_defaultPowerStatsThrottlePeriodCpu">60000</integer>
+
+ <!-- PowerStats aggregation period in milliseconds. This is the interval at which the power
+ stats aggregation procedure is performed and the results stored in PowerStatsStore. -->
+ <integer name="config_powerStatsAggregationPeriod">14400000</integer>
+
+ <!-- PowerStats aggregation span duration in milliseconds. This is the length of battery
+ history time for every aggregated power stats span that is stored stored in PowerStatsStore.
+ It should not be larger than config_powerStatsAggregationPeriod (but it can be the same) -->
+ <integer name="config_aggregatedPowerStatsSpanDuration">3600000</integer>
</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index adc7fe0..4cd4f63 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -110,6 +110,8 @@
<eat-comment/>
<staging-public-group type="attr" first-id="0x01bd0000">
+ <!-- @FlaggedApi("android.content.res.default_locale") -->
+ <public name="defaultLocale"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c1ecb05..8e1c09e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2053,6 +2053,8 @@
<java-symbol type="integer" name="config_previousVibrationsDumpAggregationTimeMillisLimit" />
<java-symbol type="integer" name="config_defaultVibrationAmplitude" />
<java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" />
+ <java-symbol type="dimen" name="config_keyboardHapticFeedbackFixedAmplitude" />
+ <java-symbol type="bool" name="config_defaultKeyboardVibrationEnabled" />
<java-symbol type="integer" name="config_vibrationWaveformRampStepDuration" />
<java-symbol type="bool" name="config_ignoreVibrationsOnWirelessCharger" />
<java-symbol type="integer" name="config_vibrationWaveformRampDownDuration" />
@@ -4306,6 +4308,8 @@
<java-symbol type="string" name="config_factoryResetPackage" />
<java-symbol type="array" name="config_highRefreshRateBlacklist" />
<java-symbol type="array" name="config_forceSlowJpegModeList" />
+ <java-symbol type="array" name="pause_wallpaper_render_when_state_change" />
+ <java-symbol type="bool" name="config_pauseWallpaperRenderWhenStateChangeEnabled" />
<java-symbol type="array" name="config_smallAreaDetectionAllowlist" />
@@ -5124,6 +5128,8 @@
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
<java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodCpu" />
+ <java-symbol type="integer" name="config_powerStatsAggregationPeriod" />
+ <java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" />
<java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/>
<java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/>
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index af8c69e..3a2e50a 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -54,6 +54,9 @@
<!-- Azerbaijan: 4-5 digits, known premium codes listed -->
<shortcode country="az" pattern="\\d{4,5}" premium="330[12]|87744|901[234]|93(?:94|101)|9426|9525" />
+ <!-- Bangladesh: 1-5 digits (standard system default, not country specific) -->
+ <shortcode country="bd" pattern="\\d{1,5}" free="16672" />
+
<!-- Belgium: 4 digits, plus EU: http://www.mobileweb.be/en/mobileweb/sms-numberplan.asp -->
<shortcode country="be" premium="\\d{4}" free="8\\d{3}|116\\d{3}" />
@@ -145,7 +148,7 @@
<shortcode country="in" pattern="\\d{1,5}" free="59336|53969" />
<!-- Indonesia: 1-5 digits (standard system default, not country specific) -->
- <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363" />
+ <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363|93457" />
<!-- Ireland: 5 digits, 5xxxx (50xxx=free, 5[12]xxx=standard), plus EU:
http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf -->
@@ -190,7 +193,7 @@
<shortcode country="mk" pattern="\\d{1,6}" free="129005|122" />
<!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
- <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101" />
+ <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453" />
<!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
<shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" />
@@ -205,7 +208,7 @@
<shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" free="2171" />
<!-- New Zealand: 3-4 digits, known premium codes listed -->
- <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" />
+ <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|3876|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" />
<!-- Peru: 4-5 digits (not confirmed), known premium codes listed -->
<shortcode country="pe" pattern="\\d{4,5}" free="9963|40778" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 2993a0e..445ddf5 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -65,6 +65,7 @@
"device-time-shell-utils",
"testables",
"com.android.text.flags-aconfig-java",
+ "flag-junit",
],
libs: [
@@ -75,6 +76,7 @@
"framework",
"ext",
"framework-res",
+ "android.view.flags-aconfig-java",
],
jni_libs: [
"libpowermanagertest_jni",
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
new file mode 100644
index 0000000..c00eb91
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.app.servertransaction;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.ClientTransactionHandler;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.util.MergedConfiguration;
+import android.view.IWindow;
+import android.view.InsetsState;
+import android.window.ClientWindowFrames;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link WindowStateResizeItem}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:WindowStateResizeItemTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class WindowStateResizeItemTest {
+
+ @Mock
+ private ClientTransactionHandler mHandler;
+ @Mock
+ private PendingTransactionActions mPendingActions;
+ @Mock
+ private IWindow mWindow;
+ @Mock
+ private ClientWindowFrames mFrames;
+ @Mock
+ private MergedConfiguration mConfiguration;
+ @Mock
+ private InsetsState mInsetsState;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testExecute() throws RemoteException {
+ final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames,
+ true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */,
+ true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
+ true /* dragResizing */);
+ item.execute(mHandler, mPendingActions);
+
+ verify(mWindow).resized(mFrames,
+ true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */,
+ true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
+ true /* dragResizing */);
+ }
+}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index 0941a2b..6c14ee3 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -137,7 +137,7 @@
);
});
- PollingCheck.waitFor(/* timeout= */ 7000, () -> {
+ PollingCheck.waitFor(/* timeout= */ 10000, () -> {
AtomicBoolean isActivityAtCorrectScale = new AtomicBoolean(false);
rule.getScenario().onActivity(activity ->
isActivityAtCorrectScale.set(
@@ -163,7 +163,7 @@
});
PollingCheck.waitFor(
- /* timeout= */ 5000,
+ /* timeout= */ 10000,
() -> InstrumentationRegistry.getInstrumentation()
.getContext()
.getResources()
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 149f58f..c2e6b60c 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -42,8 +42,8 @@
public class DisplayManagerGlobalTest {
private static final long ALL_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
- | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+ | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
@Mock
private IDisplayManager mDisplayManager;
@@ -69,7 +69,8 @@
@Test
public void testDisplayListenerIsCalled_WhenDisplayEventOccurs() throws RemoteException {
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS);
+ mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS,
+ null);
Mockito.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
IDisplayManagerCallback callback = mCallbackCaptor.getValue();
@@ -97,26 +98,27 @@
public void testDisplayListenerIsNotCalled_WhenClientIsNotSubscribed() throws RemoteException {
// First we subscribe to all events in order to test that the subsequent calls to
// registerDisplayListener will update the event mask.
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS);
+ mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS,
+ null);
Mockito.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
IDisplayManagerCallback callback = mCallbackCaptor.getValue();
int displayId = 1;
mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
- ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED);
+ ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED, null);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
waitForHandler();
Mockito.verifyZeroInteractions(mListener);
mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
- ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED);
+ ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
waitForHandler();
Mockito.verifyZeroInteractions(mListener);
mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
- ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
+ ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, null);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
waitForHandler();
Mockito.verifyZeroInteractions(mListener);
@@ -139,7 +141,7 @@
public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()
throws RemoteException {
mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
- DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
+ DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS, null);
InOrder inOrder = Mockito.inOrder(mDisplayManager);
inOrder.verify(mDisplayManager)
@@ -163,6 +165,7 @@
}
private void waitForHandler() {
- mHandler.runWithScissors(() -> { }, 0);
+ mHandler.runWithScissors(() -> {
+ }, 0);
}
}
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 07dec5d..b843ad7 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -79,6 +79,8 @@
private FaceManager.AuthenticationCallback mAuthCallback;
@Mock
private FaceManager.EnrollmentCallback mEnrollmentCallback;
+ @Mock
+ private FaceManager.FaceDetectionCallback mFaceDetectionCallback;
@Captor
private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mCaptor;
@@ -191,6 +193,23 @@
any(), anyString(), any(), any(), anyBoolean());
}
+ @Test
+ public void detectClient_onError() throws RemoteException {
+ ArgumentCaptor<IFaceServiceReceiver> argumentCaptor =
+ ArgumentCaptor.forClass(IFaceServiceReceiver.class);
+
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ mFaceManager.detectFace(cancellationSignal, mFaceDetectionCallback,
+ new FaceAuthenticateOptions.Builder().build());
+
+ verify(mService).detectFace(any(), argumentCaptor.capture(), any());
+
+ argumentCaptor.getValue().onError(5 /* error */, 0 /* vendorCode */);
+ mLooper.dispatchAll();
+
+ verify(mFaceDetectionCallback).onDetectionError(anyInt());
+ }
+
private void initializeProperties() throws RemoteException {
verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture());
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
index 625e2e3..70313b8 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
@@ -74,6 +74,8 @@
private FingerprintManager.AuthenticationCallback mAuthCallback;
@Mock
private FingerprintManager.EnrollmentCallback mEnrollCallback;
+ @Mock
+ private FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback;
@Captor
private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mCaptor;
@@ -166,4 +168,21 @@
anyString());
verify(mService, never()).enroll(any(), any(), anyInt(), any(), anyString(), anyInt());
}
+
+ @Test
+ public void detectClient_onError() throws RemoteException {
+ ArgumentCaptor<IFingerprintServiceReceiver> argumentCaptor =
+ ArgumentCaptor.forClass(IFingerprintServiceReceiver.class);
+
+ mFingerprintManager.detectFingerprint(new CancellationSignal(),
+ mFingerprintDetectionCallback,
+ new FingerprintAuthenticateOptions.Builder().build());
+
+ verify(mService).detectFingerprint(any(), argumentCaptor.capture(), any());
+
+ argumentCaptor.getValue().onError(5 /* error */, 0 /* vendorCode */);
+ mLooper.dispatchAll();
+
+ verify(mFingerprintDetectionCallback).onDetectionError(anyInt());
+ }
}
diff --git a/core/tests/coretests/src/android/os/LocaleListTest.java b/core/tests/coretests/src/android/os/LocaleListTest.java
index 1f00a7a..88fc826 100644
--- a/core/tests/coretests/src/android/os/LocaleListTest.java
+++ b/core/tests/coretests/src/android/os/LocaleListTest.java
@@ -81,4 +81,49 @@
// restore the original values
LocaleList.setDefault(originalLocaleList, originalLocaleIndex);
}
+
+ @SmallTest
+ public void testIntersection() {
+ LocaleList localesWithN = new LocaleList(
+ Locale.ENGLISH,
+ Locale.FRENCH,
+ Locale.GERMAN,
+ Locale.ITALIAN,
+ Locale.JAPANESE,
+ Locale.KOREAN,
+ Locale.CHINESE,
+ Locale.SIMPLIFIED_CHINESE,
+ Locale.TRADITIONAL_CHINESE,
+ Locale.FRANCE,
+ Locale.GERMANY,
+ Locale.JAPAN,
+ Locale.CANADA,
+ Locale.CANADA_FRENCH);
+ LocaleList localesWithE = new LocaleList(
+ Locale.ENGLISH,
+ Locale.FRENCH,
+ Locale.GERMAN,
+ Locale.JAPANESE,
+ Locale.KOREAN,
+ Locale.CHINESE,
+ Locale.SIMPLIFIED_CHINESE,
+ Locale.TRADITIONAL_CHINESE,
+ Locale.FRANCE,
+ Locale.GERMANY,
+ Locale.CANADA_FRENCH);
+ LocaleList localesWithNAndE = new LocaleList(
+ Locale.ENGLISH,
+ Locale.FRENCH,
+ Locale.GERMAN,
+ Locale.JAPANESE,
+ Locale.KOREAN,
+ Locale.CHINESE,
+ Locale.SIMPLIFIED_CHINESE,
+ Locale.TRADITIONAL_CHINESE,
+ Locale.FRANCE,
+ Locale.GERMANY,
+ Locale.CANADA_FRENCH);
+
+ assertEquals(localesWithNAndE, new LocaleList(localesWithE.getIntersection(localesWithN)));
+ }
}
diff --git a/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java b/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java
deleted file mode 100644
index e1f9523..0000000
--- a/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java
+++ /dev/null
@@ -1,126 +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 android.os.health;
-
-import static androidx.test.InstrumentationRegistry.getContext;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.ConditionVariable;
-import android.os.PowerMonitor;
-import android.os.PowerMonitorReadings;
-
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class SystemHealthManagerTest {
- private List<PowerMonitor> mPowerMonitorInfo;
- private PowerMonitorReadings mReadings;
- private RuntimeException mException;
-
- @Test
- public void getPowerMonitors() {
- SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class);
- List<PowerMonitor> powerMonitorInfo = shm.getSupportedPowerMonitors();
- assertThat(powerMonitorInfo).isNotNull();
- if (powerMonitorInfo.isEmpty()) {
- // This device does not support PowerStats HAL
- return;
- }
-
- PowerMonitor consumerMonitor = null;
- PowerMonitor measurementMonitor = null;
- for (PowerMonitor pmi : powerMonitorInfo) {
- if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
- measurementMonitor = pmi;
- } else {
- consumerMonitor = pmi;
- }
- }
-
- List<PowerMonitor> selectedMonitors = new ArrayList<>();
- if (consumerMonitor != null) {
- selectedMonitors.add(consumerMonitor);
- }
- if (measurementMonitor != null) {
- selectedMonitors.add(measurementMonitor);
- }
-
- PowerMonitorReadings readings = shm.getPowerMonitorReadings(selectedMonitors);
-
- for (PowerMonitor monitor : selectedMonitors) {
- assertThat(readings.getConsumedEnergyUws(monitor)).isAtLeast(0);
- assertThat(readings.getTimestamp(monitor)).isGreaterThan(0);
- }
- }
-
- @Test
- public void getPowerMonitorsAsync() {
- SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class);
- ConditionVariable done = new ConditionVariable();
- shm.getSupportedPowerMonitors(null, pms -> {
- mPowerMonitorInfo = pms;
- done.open();
- });
- done.block();
- assertThat(mPowerMonitorInfo).isNotNull();
- if (mPowerMonitorInfo.isEmpty()) {
- // This device does not support PowerStats HAL
- return;
- }
-
- PowerMonitor consumerMonitor = null;
- PowerMonitor measurementMonitor = null;
- for (PowerMonitor pmi : mPowerMonitorInfo) {
- if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
- measurementMonitor = pmi;
- } else {
- consumerMonitor = pmi;
- }
- }
-
- List<PowerMonitor> selectedMonitors = new ArrayList<>();
- if (consumerMonitor != null) {
- selectedMonitors.add(consumerMonitor);
- }
- if (measurementMonitor != null) {
- selectedMonitors.add(measurementMonitor);
- }
-
- done.close();
- shm.getPowerMonitorReadings(selectedMonitors, null,
- readings -> {
- mReadings = readings;
- done.open();
- },
- exception -> {
- mException = exception;
- done.open();
- }
- );
- done.block();
-
- assertThat(mException).isNull();
-
- for (PowerMonitor monitor : selectedMonitors) {
- assertThat(mReadings.getConsumedEnergyUws(monitor)).isAtLeast(0);
- assertThat(mReadings.getTimestamp(monitor)).isGreaterThan(0);
- }
- }
-}
diff --git a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
index 517aeae..0855268 100644
--- a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
+++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
@@ -20,8 +20,6 @@
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM;
-
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -42,16 +40,12 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.SharedMemory;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.TestableContext;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Flag;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver;
-
-import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
@@ -71,6 +65,9 @@
private NotificationChannel mNotificationChannel;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
// TODO(b/284297289): remove this flag set once resolved.
@Parameterized.Parameters(name = "rankingUpdateAshmem={0}")
public static Boolean[] getRankingUpdateAshmem() {
@@ -424,30 +421,11 @@
mNotificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "test channel",
NotificationManager.IMPORTANCE_DEFAULT);
- SystemUiSystemPropertiesFlags.TEST_RESOLVER = new FlagResolver() {
- @Override
- public boolean isEnabled(Flag flag) {
- if (flag.mSysPropKey.equals(RANKING_UPDATE_ASHMEM.mSysPropKey)) {
- return mRankingUpdateAshmem;
- }
- return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag);
- }
-
- @Override
- public int getIntValue(Flag flag) {
- return 0;
- }
-
- @Override
- public String getStringValue(Flag flag) {
- return null;
- }
- };
- }
-
- @After
- public void tearDown() {
- SystemUiSystemPropertiesFlags.TEST_RESOLVER = null;
+ if (mRankingUpdateAshmem) {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RANKING_UPDATE_ASHMEM);
+ } else {
+ mSetFlagsRule.disableFlags(Flags.FLAG_RANKING_UPDATE_ASHMEM);
+ }
}
/**
@@ -497,8 +475,7 @@
parcel.setDataPosition(0);
NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel);
// The rankingUpdate file descriptor is only non-null in the new path.
- if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+ if (Flags.rankingUpdateAshmem()) {
assertTrue(nru1.isFdNotNullAndClosed());
}
detailedAssertEquals(nru, nru1);
@@ -636,7 +613,7 @@
@Test
public void testRankingUpdate_writesSmartActionToParcel() {
- if (!mRankingUpdateAshmem) {
+ if (!Flags.rankingUpdateAshmem()) {
return;
}
ArrayList<Notification.Action> actions = new ArrayList<>();
@@ -674,7 +651,7 @@
@Test
public void testRankingUpdate_handlesEmptySmartActionList() {
- if (!mRankingUpdateAshmem) {
+ if (!Flags.rankingUpdateAshmem()) {
return;
}
ArrayList<Notification.Action> actions = new ArrayList<>();
@@ -697,7 +674,7 @@
@Test
public void testRankingUpdate_handlesNullSmartActionList() {
- if (!mRankingUpdateAshmem) {
+ if (!Flags.rankingUpdateAshmem()) {
return;
}
NotificationListenerService.Ranking ranking =
diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS
index 2ca9994..23668a4 100644
--- a/core/tests/coretests/src/android/view/OWNERS
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -20,4 +20,7 @@
per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
# Stylus
-per-file stylus/* = file:/core/java/android/text/OWNERS
\ No newline at end of file
+per-file stylus/* = file:/core/java/android/text/OWNERS
+
+# View
+file:/core/java/android/view/OWNERS
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 6a9fc04..1a38dec 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -17,6 +17,11 @@
package android.view;
import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
+import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
+import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
+import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
@@ -48,8 +53,12 @@
import android.os.Binder;
import android.os.SystemProperties;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowInsets.Side;
import android.view.WindowInsets.Type;
@@ -97,6 +106,10 @@
// state after the test completes.
private static boolean sOriginalTouchMode;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@BeforeClass
public static void setUpClass() {
sContext = sInstrumentation.getTargetContext();
@@ -427,6 +440,129 @@
assertThat(result).isFalse();
}
+ /**
+ * Test the default values are properly set
+ */
+ @UiThreadTest
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
+ public void votePreferredFrameRate_getDefaultValues() {
+ ViewRootImpl viewRootImpl = new ViewRootImpl(sContext,
+ sContext.getDisplayNoVerify());
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
+ }
+
+ /**
+ * Test the value of the frame rate cateogry based on the visibility of a view
+ * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE
+ * Visible: FRAME_RATE_CATEGORY_NORMAL
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
+ public void votePreferredFrameRate_voteFrameRateCategory_visibility() {
+ View view = new View(sContext);
+ attachViewToWindow(view);
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.setVisibility(View.INVISIBLE);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ });
+
+ sInstrumentation.runOnMainSync(() -> {
+ view.setVisibility(View.VISIBLE);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_NORMAL);
+ });
+ }
+
+ /**
+ * Test the value of the frame rate cateogry based on the size of a view.
+ * The current threshold value is 7% of the screen size
+ * <7%: FRAME_RATE_CATEGORY_LOW
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
+ public void votePreferredFrameRate_voteFrameRateCategory_smallSize() {
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+ wmlp.width = 1;
+ wmlp.height = 1;
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
+ });
+ }
+
+ /**
+ * Test the value of the frame rate cateogry based on the size of a view.
+ * The current threshold value is 7% of the screen size
+ * >=7% : FRAME_RATE_CATEGORY_NORMAL
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
+ public void votePreferredFrameRate_voteFrameRateCategory_normalSize() {
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ Display display = wm.getDefaultDisplay();
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getMetrics(metrics);
+ wmlp.width = (int) (metrics.widthPixels * 0.9);
+ wmlp.height = (int) (metrics.heightPixels * 0.9);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ sInstrumentation.runOnMainSync(() -> {
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ });
+ }
+
+ /**
+ * Test how values of the frame rate cateogry are aggregated.
+ * It should take the max value among all of the voted categories per frame.
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
+ public void votePreferredFrameRate_voteFrameRateCategory_aggregate() {
+ View view = new View(sContext);
+ attachViewToWindow(view);
+ sInstrumentation.runOnMainSync(() -> {
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ });
+ }
+
@Test
public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
index e76d266..d47d789 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -115,6 +115,26 @@
new ContentCaptureOptions.ContentProtectionOptions(
/* enableReceiver= */ true,
-BUFFER_SIZE,
+ /* requiredGroups= */ List.of(List.of("a")),
+ /* optionalGroups= */ Collections.emptyList(),
+ /* optionalGroupsThreshold= */ 0));
+ MainContentCaptureSession session = createSession(options);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ }
+
+ @Test
+ public void onSessionStarted_contentProtectionNoGroups_processorNotCreated() {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ true,
+ BUFFER_SIZE,
/* requiredGroups= */ Collections.emptyList(),
/* optionalGroups= */ Collections.emptyList(),
/* optionalGroupsThreshold= */ 0));
@@ -320,7 +340,7 @@
new ContentCaptureOptions.ContentProtectionOptions(
enableContentProtectionReceiver,
BUFFER_SIZE,
- /* requiredGroups= */ Collections.emptyList(),
+ /* requiredGroups= */ List.of(List.of("a")),
/* optionalGroups= */ Collections.emptyList(),
/* optionalGroupsThreshold= */ 0));
}
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
index 39a2e0e..ba0dbf4 100644
--- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
@@ -29,12 +29,13 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ContentCaptureOptions;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.Handler;
-import android.os.Looper;
-import android.text.InputType;
+import android.os.test.TestLooper;
import android.view.View;
import android.view.contentcapture.ContentCaptureEvent;
import android.view.contentcapture.IContentCaptureManager;
@@ -57,6 +58,7 @@
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import org.mockito.verification.VerificationMode;
import java.time.Instant;
import java.util.ArrayList;
@@ -75,13 +77,25 @@
private static final String PACKAGE_NAME = "com.test.package.name";
- private static final String ANDROID_CLASS_NAME = "android.test.some.class.name";
+ private static final String TEXT_REQUIRED1 = "TEXT REQUIRED1 TEXT";
- private static final String PASSWORD_TEXT = "ENTER PASSWORD HERE";
+ private static final String TEXT_REQUIRED2 = "TEXT REQUIRED2 TEXT";
- private static final String SUSPICIOUS_TEXT = "PLEASE SIGN IN";
+ private static final String TEXT_OPTIONAL1 = "TEXT OPTIONAL1 TEXT";
- private static final String SAFE_TEXT = "SAFE TEXT";
+ private static final String TEXT_OPTIONAL2 = "TEXT OPTIONAL2 TEXT";
+
+ private static final String TEXT_CONTAINS_OPTIONAL3 = "TEXTOPTIONAL3TEXT";
+
+ private static final String TEXT_SHARED = "TEXT SHARED TEXT";
+
+ private static final String TEXT_SAFE = "TEXT SAFE TEXT";
+
+ private static final List<List<String>> REQUIRED_GROUPS =
+ List.of(List.of("required1", "missing"), List.of("required2", "shared"));
+
+ private static final List<List<String>> OPTIONAL_GROUPS =
+ List.of(List.of("optional1"), List.of("optional2", "optional3"), List.of("shared"));
private static final ContentCaptureEvent PROCESS_EVENT = createProcessEvent();
@@ -91,7 +105,17 @@
private static final Set<Integer> EVENT_TYPES_TO_STORE =
ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED);
- private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150;
+ private static final int BUFFER_SIZE = 150;
+
+ private static final int OPTIONAL_GROUPS_THRESHOLD = 1;
+
+ private static final ContentCaptureOptions.ContentProtectionOptions OPTIONS =
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ true,
+ BUFFER_SIZE,
+ REQUIRED_GROUPS,
+ OPTIONAL_GROUPS,
+ OPTIONAL_GROUPS_THRESHOLD);
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -101,16 +125,19 @@
private final Context mContext = ApplicationProvider.getApplicationContext();
- private ContentProtectionEventProcessor mContentProtectionEventProcessor;
+ private final TestLooper mTestLooper = new TestLooper();
+
+ @NonNull private ContentProtectionEventProcessor mContentProtectionEventProcessor;
@Before
public void setup() {
mContentProtectionEventProcessor =
new ContentProtectionEventProcessor(
mMockEventBuffer,
- new Handler(Looper.getMainLooper()),
+ new Handler(mTestLooper.getLooper()),
mMockContentCaptureManager,
- PACKAGE_NAME);
+ PACKAGE_NAME,
+ OPTIONS);
}
@Test
@@ -156,347 +183,224 @@
}
@Test
- public void processEvent_loginDetected_inspectsOnlyTypeViewAppeared() {
- mContentProtectionEventProcessor.mPasswordFieldDetected = true;
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ public void processEvent_loginDetected_true_eventText() throws Exception {
+ when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ TEXT_REQUIRED1,
+ /* viewNodeText= */ null,
+ /* hintText= */ null));
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ TEXT_REQUIRED2,
+ /* viewNodeText= */ null,
+ /* hintText= */ null));
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ TEXT_OPTIONAL1,
+ /* viewNodeText= */ null,
+ /* hintText= */ null));
+
+ assertLoginDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_true_viewNodeText() throws Exception {
+ when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ null,
+ /* viewNodeText= */ TEXT_REQUIRED1,
+ /* hintText= */ null));
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ null,
+ /* viewNodeText= */ TEXT_REQUIRED2,
+ /* hintText= */ null));
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ null,
+ /* viewNodeText= */ TEXT_OPTIONAL1,
+ /* hintText= */ null));
+
+ assertLoginDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_true_hintText() throws Exception {
+ when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ null,
+ /* viewNodeText= */ null,
+ /* hintText= */ TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ null,
+ /* viewNodeText= */ null,
+ /* hintText= */ TEXT_REQUIRED2));
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ null,
+ /* viewNodeText= */ null,
+ /* hintText= */ TEXT_OPTIONAL1));
+
+ assertLoginDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_true_differentOptionalGroup() throws Exception {
+ when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL2));
+
+ assertLoginDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_true_usesContains() throws Exception {
+ when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_CONTAINS_OPTIONAL3));
+
+ assertLoginDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_false_missingRequiredGroups() {
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
+
+ assertLoginNotDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_false_missingOptionalGroups() {
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+
+ assertLoginNotDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_false_safeText() {
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE));
+
+ assertLoginNotDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_false_sharedTextOnce() {
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED));
+
+ assertLoginNotDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_true_sharedTextMultiple() throws Exception {
+ when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED));
+
+ assertLoginDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_false_inspectsOnlyTypeViewAppeared() {
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
for (int type = -100; type <= 100; type++) {
if (type == TYPE_VIEW_APPEARED) {
continue;
}
-
- mContentProtectionEventProcessor.processEvent(createEvent(type));
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ ContentCaptureEvent event = createEvent(type);
+ event.setText(TEXT_OPTIONAL1);
+ mContentProtectionEventProcessor.processEvent(event);
}
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
+ assertLoginNotDetected();
}
@Test
- public void processEvent_loginDetected() throws Exception {
+ public void processEvent_loginDetected_true_belowResetLimit() throws Exception {
when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
- mContentProtectionEventProcessor.mPasswordFieldDetected = true;
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
- mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer).clear();
- verify(mMockEventBuffer).toArray();
- assertOnLoginDetected();
- }
-
- @Test
- public void processEvent_loginDetected_passwordFieldNotDetected() {
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-
- mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void processEvent_loginDetected_suspiciousTextNotDetected() {
- mContentProtectionEventProcessor.mPasswordFieldDetected = true;
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-
- mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void processEvent_loginDetected_withoutViewNode() {
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-
- mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void processEvent_loginDetected_belowResetLimit() throws Exception {
- when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
- ContentCaptureEvent event =
- createAndroidPasswordFieldEvent(
- ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
-
- for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; i++) {
+ for (int i = 0; i < BUFFER_SIZE - 2; i++) {
mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
}
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
+ assertLoginNotDetected();
- mContentProtectionEventProcessor.processEvent(event);
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer).clear();
- verify(mMockEventBuffer).toArray();
- assertOnLoginDetected();
+ assertLoginDetected();
}
@Test
- public void processEvent_loginDetected_aboveResetLimit() throws Exception {
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
- ContentCaptureEvent event =
- createAndroidPasswordFieldEvent(
- ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ public void processEvent_loginDetected_false_aboveResetLimit() {
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
- for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS + 1; i++) {
+ for (int i = 0; i < BUFFER_SIZE - 1; i++) {
mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
}
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
+ assertLoginNotDetected();
- mContentProtectionEventProcessor.processEvent(event);
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
+ assertLoginNotDetected();
}
@Test
public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception {
when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
- mContentProtectionEventProcessor.mPasswordFieldDetected = true;
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
- mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+ for (int i = 0; i < 2; i++) {
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+ }
- mContentProtectionEventProcessor.mPasswordFieldDetected = true;
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
- mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer).clear();
- verify(mMockEventBuffer).toArray();
- assertOnLoginDetected();
+ assertLoginDetected();
}
@Test
public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception {
when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
- mContentProtectionEventProcessor.mPasswordFieldDetected = true;
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5);
- mContentProtectionEventProcessor.mPasswordFieldDetected = true;
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer, times(2)).clear();
- verify(mMockEventBuffer, times(2)).toArray();
- assertOnLoginDetected(PROCESS_EVENT, /* times= */ 2);
- }
-
- @Test
- public void isPasswordField_android() {
- ContentCaptureEvent event =
- createAndroidPasswordFieldEvent(
- ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isPasswordField_android_withoutClassName() {
- ContentCaptureEvent event =
- createAndroidPasswordFieldEvent(
- /* className= */ null, InputType.TYPE_TEXT_VARIATION_PASSWORD);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isPasswordField_android_wrongClassName() {
- ContentCaptureEvent event =
- createAndroidPasswordFieldEvent(
- "wrong.prefix" + ANDROID_CLASS_NAME,
- InputType.TYPE_TEXT_VARIATION_PASSWORD);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isPasswordField_android_wrongInputType() {
- ContentCaptureEvent event =
- createAndroidPasswordFieldEvent(
- ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_NORMAL);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isPasswordField_webView() throws Exception {
- ContentCaptureEvent event =
- createWebViewPasswordFieldEvent(
- /* className= */ null, /* eventText= */ null, PASSWORD_TEXT);
- when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[] {event});
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- verify(mMockEventBuffer).clear();
- verify(mMockEventBuffer).toArray();
- assertOnLoginDetected(event, /* times= */ 1);
- }
-
- @Test
- public void isPasswordField_webView_withClassName() {
- ContentCaptureEvent event =
- createWebViewPasswordFieldEvent(
- /* className= */ "any.class.name", /* eventText= */ null, PASSWORD_TEXT);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isPasswordField_webView_withSafeViewNodeText() {
- ContentCaptureEvent event =
- createWebViewPasswordFieldEvent(
- /* className= */ null, /* eventText= */ null, SAFE_TEXT);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isPasswordField_webView_withEventText() {
- ContentCaptureEvent event =
- createWebViewPasswordFieldEvent(/* className= */ null, PASSWORD_TEXT, SAFE_TEXT);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isSuspiciousText_withSafeText() {
- ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SAFE_TEXT);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isSuspiciousText_eventText_suspiciousText() {
- ContentCaptureEvent event = createSuspiciousTextEvent(SUSPICIOUS_TEXT, SAFE_TEXT);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isSuspiciousText_viewNodeText_suspiciousText() {
- ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SUSPICIOUS_TEXT);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isSuspiciousText_eventText_passwordText() {
- ContentCaptureEvent event = createSuspiciousTextEvent(PASSWORD_TEXT, SAFE_TEXT);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isSuspiciousText_viewNodeText_passwordText() {
- // Specify the class to differ from {@link isPasswordField_webView} test in this version
- ContentCaptureEvent event =
- createProcessEvent(
- "test.class.not.a.web.view", /* inputType= */ 0, SAFE_TEXT, PASSWORD_TEXT);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
+ assertLoginDetected(times(2));
}
private static ContentCaptureEvent createEvent(int type) {
@@ -511,20 +415,20 @@
return createEvent(TYPE_VIEW_APPEARED);
}
+ private ContentCaptureEvent createProcessEvent(@Nullable String eventText) {
+ return createProcessEvent(eventText, /* viewNodeText= */ null, /* hintText= */ null);
+ }
+
private ContentCaptureEvent createProcessEvent(
- @Nullable String className,
- int inputType,
- @Nullable String eventText,
- @Nullable String viewNodeText) {
+ @Nullable String eventText, @Nullable String viewNodeText, @Nullable String hintText) {
View view = new View(mContext);
ViewStructureImpl viewStructure = new ViewStructureImpl(view);
- if (className != null) {
- viewStructure.setClassName(className);
- }
if (viewNodeText != null) {
viewStructure.setText(viewNodeText);
}
- viewStructure.setInputType(inputType);
+ if (hintText != null) {
+ viewStructure.setHint(hintText);
+ }
ContentCaptureEvent event = createProcessEvent();
event.setViewNode(viewStructure.getNode());
@@ -535,34 +439,28 @@
return event;
}
- private ContentCaptureEvent createAndroidPasswordFieldEvent(
- @Nullable String className, int inputType) {
- return createProcessEvent(
- className, inputType, /* eventText= */ null, /* viewNodeText= */ null);
+ private void assertLoginNotDetected() {
+ mTestLooper.dispatchAll();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
}
- private ContentCaptureEvent createWebViewPasswordFieldEvent(
- @Nullable String className, @Nullable String eventText, @Nullable String viewNodeText) {
- return createProcessEvent(className, /* inputType= */ 0, eventText, viewNodeText);
+ private void assertLoginDetected() throws Exception {
+ assertLoginDetected(times(1));
}
- private ContentCaptureEvent createSuspiciousTextEvent(
- @Nullable String eventText, @Nullable String viewNodeText) {
- return createProcessEvent(
- /* className= */ null, /* inputType= */ 0, eventText, viewNodeText);
- }
+ private void assertLoginDetected(@NonNull VerificationMode verificationMode) throws Exception {
+ mTestLooper.dispatchAll();
+ verify(mMockEventBuffer, verificationMode).clear();
+ verify(mMockEventBuffer, verificationMode).toArray();
- private void assertOnLoginDetected() throws Exception {
- assertOnLoginDetected(PROCESS_EVENT, /* times= */ 1);
- }
-
- private void assertOnLoginDetected(ContentCaptureEvent event, int times) throws Exception {
ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class);
- verify(mMockContentCaptureManager, times(times)).onLoginDetected(captor.capture());
+ verify(mMockContentCaptureManager, verificationMode).onLoginDetected(captor.capture());
assertThat(captor.getValue()).isNotNull();
List<ContentCaptureEvent> actual = captor.getValue().getList();
assertThat(actual).isNotNull();
- assertThat(actual).containsExactly(event);
+ assertThat(actual).containsExactly(PROCESS_EVENT);
}
}
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
index 1459799..fbe478e 100644
--- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -44,68 +44,74 @@
private static final String TEXT = "TEST_TEXT";
- private static final ContentCaptureEvent EVENT = createEvent();
-
- private static final ViewNode VIEW_NODE = new ViewNode();
-
- private static final ViewNode VIEW_NODE_WITH_TEXT = createViewNodeWithText();
+ private static final String TEXT_LOWER = TEXT.toLowerCase();
@Test
- public void event_getEventText_null() {
- String actual = ContentProtectionUtils.getEventText(EVENT);
+ public void getEventTextLower_null() {
+ String actual = ContentProtectionUtils.getEventTextLower(createEvent());
assertThat(actual).isNull();
}
@Test
- public void event_getEventText_notNull() {
- ContentCaptureEvent event = createEvent();
- event.setText(TEXT);
+ public void getEventTextLower_notNull() {
+ String actual = ContentProtectionUtils.getEventTextLower(createEventWithText());
- String actual = ContentProtectionUtils.getEventText(event);
-
- assertThat(actual).isEqualTo(TEXT);
+ assertThat(actual).isEqualTo(TEXT_LOWER);
}
@Test
- public void event_getViewNodeText_null() {
- String actual = ContentProtectionUtils.getViewNodeText(EVENT);
+ public void getViewNodeTextLower_null() {
+ String actual = ContentProtectionUtils.getViewNodeTextLower(new ViewNode());
assertThat(actual).isNull();
}
@Test
- public void event_getViewNodeText_notNull() {
- ContentCaptureEvent event = createEvent();
- event.setViewNode(VIEW_NODE_WITH_TEXT);
+ public void getViewNodeTextLower_notNull() {
+ String actual = ContentProtectionUtils.getViewNodeTextLower(createViewNodeWithText());
- String actual = ContentProtectionUtils.getViewNodeText(event);
-
- assertThat(actual).isEqualTo(TEXT);
+ assertThat(actual).isEqualTo(TEXT_LOWER);
}
@Test
- public void viewNode_getViewNodeText_null() {
- String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE);
+ public void getHintTextLower_null() {
+ String actual = ContentProtectionUtils.getHintTextLower(new ViewNode());
assertThat(actual).isNull();
}
@Test
- public void viewNode_getViewNodeText_notNull() {
- String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE_WITH_TEXT);
+ public void getHintTextLower_notNull() {
+ String actual = ContentProtectionUtils.getHintTextLower(createViewNodeWithHint());
- assertThat(actual).isEqualTo(TEXT);
+ assertThat(actual).isEqualTo(TEXT_LOWER);
}
private static ContentCaptureEvent createEvent() {
return new ContentCaptureEvent(/* sessionId= */ 123, TYPE_SESSION_STARTED);
}
- private static ViewNode createViewNodeWithText() {
+ private static ContentCaptureEvent createEventWithText() {
+ ContentCaptureEvent event = createEvent();
+ event.setText(TEXT);
+ return event;
+ }
+
+ private static ViewStructureImpl createViewStructureImpl() {
View view = new View(ApplicationProvider.getApplicationContext());
- ViewStructureImpl viewStructure = new ViewStructureImpl(view);
- viewStructure.setText(TEXT);
- return viewStructure.getNode();
+ return new ViewStructureImpl(view);
+ }
+
+ private static ViewNode createViewNodeWithText() {
+ ViewStructureImpl viewStructureImpl = createViewStructureImpl();
+ viewStructureImpl.setText(TEXT);
+ return viewStructureImpl.getNode();
+ }
+
+ private static ViewNode createViewNodeWithHint() {
+ ViewStructureImpl viewStructureImpl = createViewStructureImpl();
+ viewStructureImpl.setHint(TEXT);
+ return viewStructureImpl.getNode();
}
}
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
index 86f26e5..df212eb 100644
--- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
@@ -17,7 +17,9 @@
package android.widget;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import android.content.Context;
import android.platform.test.annotations.Presubmit;
import android.util.PollingCheck;
@@ -32,6 +34,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
@RunWith(AndroidJUnit4.class)
@MediumTest
@Presubmit
@@ -49,23 +54,43 @@
}
@Test
- public void testScrollAfterFlingTop() {
- mHorizontalScrollView.scrollTo(100, 0);
- mHorizontalScrollView.fling(-10000);
- PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() > 0);
- PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() == 0f);
+ public void testScrollAfterFlingLeft() throws Throwable {
+ WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity);
+ mHorizontalScrollView.mEdgeGlowLeft = edgeEffect;
+ mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(100, 0));
+ mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(-10000));
+ assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS));
+ mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- least one frame
+ PollingCheck.waitFor(() -> edgeEffect.getDistance() == 0f);
assertEquals(0, mHorizontalScrollView.getScrollX());
}
@Test
- public void testScrollAfterFlingBottom() {
+ public void testScrollAfterFlingRight() throws Throwable {
+ WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity);
+ mHorizontalScrollView.mEdgeGlowRight = edgeEffect;
int childWidth = mHorizontalScrollView.getChildAt(0).getWidth();
int maxScroll = childWidth - mHorizontalScrollView.getWidth();
- mHorizontalScrollView.scrollTo(maxScroll - 100, 0);
- mHorizontalScrollView.fling(10000);
- PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() > 0);
+ mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(maxScroll - 100, 0));
+ mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(10000));
+ assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS));
+ mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- at least one frame
PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() == 0f);
assertEquals(maxScroll, mHorizontalScrollView.getScrollX());
}
+
+ static class WatchedEdgeEffect extends EdgeEffect {
+ public CountDownLatch onAbsorbLatch = new CountDownLatch(1);
+
+ WatchedEdgeEffect(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onAbsorb(int velocity) {
+ super.onAbsorb(velocity);
+ onAbsorbLatch.countDown();
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
index 263e563..6229530 100644
--- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
+++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
@@ -31,6 +31,7 @@
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -154,7 +155,8 @@
eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_HIGH));
+ eq(FRAME_RATE_CATEGORY_HIGH),
+ eq(false));
verify(mTransaction).applyAsyncUnsafe();
}
@@ -171,7 +173,8 @@
eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_DEFAULT));
+ eq(FRAME_RATE_CATEGORY_DEFAULT),
+ eq(false));
verify(mTransaction).applyAsyncUnsafe();
}
@@ -241,7 +244,8 @@
eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_HIGH));
+ eq(FRAME_RATE_CATEGORY_HIGH),
+ eq(false));
verify(mTransaction).setEarlyWakeupStart();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -261,7 +265,8 @@
eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_DEFAULT));
+ eq(FRAME_RATE_CATEGORY_DEFAULT),
+ eq(false));
verify(mTransaction).setEarlyWakeupEnd();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -281,7 +286,8 @@
eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_DEFAULT));
+ eq(FRAME_RATE_CATEGORY_DEFAULT),
+ eq(false));
verify(mTransaction).setEarlyWakeupEnd();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -299,7 +305,8 @@
eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_HIGH));
+ eq(FRAME_RATE_CATEGORY_HIGH),
+ eq(false));
verify(mTransaction).setEarlyWakeupStart();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -310,7 +317,7 @@
mHinter.startSession(HINT_ALL, DEFAULT_DISPLAY_ID, TEST_OTHER_REASON);
// Verify we never call SF and perf manager since session1 is already running
verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt());
- verify(mTransaction, never()).setFrameRateCategory(any(), anyInt());
+ verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean());
verify(mTransaction, never()).setEarlyWakeupEnd();
verify(mTransaction, never()).applyAsyncUnsafe();
verify(mAdpfSession, never()).sendHint(anyInt());
@@ -318,7 +325,7 @@
session2.close();
// Verify we have not cleaned up because session1 is still running
verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt());
- verify(mTransaction, never()).setFrameRateCategory(any(), anyInt());
+ verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean());
verify(mTransaction, never()).setEarlyWakeupEnd();
verify(mTransaction, never()).applyAsyncUnsafe();
verify(mAdpfSession, never()).sendHint(anyInt());
@@ -330,7 +337,8 @@
eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_DEFAULT));
+ eq(FRAME_RATE_CATEGORY_DEFAULT),
+ eq(false));
verify(mTransaction).setEarlyWakeupEnd();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -348,7 +356,8 @@
eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_HIGH));
+ eq(FRAME_RATE_CATEGORY_HIGH),
+ eq(false));
verify(mTransaction).setEarlyWakeupStart();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -363,7 +372,8 @@
eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
verify(mTransaction).setFrameRateCategory(
eq(mSecondaryDisplayRoot),
- eq(FRAME_RATE_CATEGORY_HIGH));
+ eq(FRAME_RATE_CATEGORY_HIGH),
+ eq(false));
verify(mTransaction, never()).setEarlyWakeupStart();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession, never()).sendHint(anyInt());
@@ -378,13 +388,15 @@
eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_DEFAULT));
+ eq(FRAME_RATE_CATEGORY_DEFAULT),
+ eq(false));
verify(mTransaction, never()).setFrameRateSelectionStrategy(
eq(mSecondaryDisplayRoot),
anyInt());
verify(mTransaction, never()).setFrameRateCategory(
eq(mSecondaryDisplayRoot),
- anyInt());
+ anyInt(),
+ eq(false));
verify(mTransaction, never()).setEarlyWakeupEnd();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession, never()).sendHint(anyInt());
@@ -401,7 +413,8 @@
eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
verify(mTransaction).setFrameRateCategory(
eq(mSecondaryDisplayRoot),
- eq(FRAME_RATE_CATEGORY_DEFAULT));
+ eq(FRAME_RATE_CATEGORY_DEFAULT),
+ eq(false));
verify(mTransaction).setEarlyWakeupEnd();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 2cbeaa1..f846ac5 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -34,6 +34,7 @@
KernelSingleUidTimeReaderTest.class,
LongArrayMultiStateCounterTest.class,
LongMultiStateCounterTest.class,
+ MonotonicClockTest.class,
PowerProfileTest.class,
PowerStatsTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
new file mode 100644
index 0000000..7951270
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.util.Xml;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MonotonicClockTest {
+ private final MockClock mClock = new MockClock();
+
+ @Test
+ public void persistence() throws IOException {
+ MonotonicClock monotonicClock = new MonotonicClock(1000, mClock);
+ mClock.realtime = 234;
+
+ assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ monotonicClock.writeXml(out, Xml.newFastSerializer());
+ String xml = out.toString();
+ assertThat(xml).contains("timeshift=\"1234\"");
+
+ mClock.realtime = 42;
+ MonotonicClock newMonotonicClock = new MonotonicClock(0, mClock);
+ newMonotonicClock.readXml(new ByteArrayInputStream(out.toByteArray()),
+ Xml.newFastPullParser());
+
+ mClock.realtime = 2000;
+ assertThat(newMonotonicClock.monotonicTime()).isEqualTo(1234 - 42 + 2000);
+ }
+}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 9c65287..b71aaf3 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1801,6 +1801,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-504637678": {
+ "message": "Starting animation on dim layer %s, requested by %s, alpha: %f -> %f, blur: %d -> %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_DIMMER",
+ "at": "com\/android\/server\/wm\/SmoothDimmer.java"
+ },
"-503656156": {
"message": "Update process config of %s to new config %s",
"level": "VERBOSE",
@@ -3739,6 +3745,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityClientController.java"
},
+ "1309365288": {
+ "message": "Removing dim surface %s on transaction %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_DIMMER",
+ "at": "com\/android\/server\/wm\/SmoothDimmer.java"
+ },
"1316533291": {
"message": "State movement: %s from:%s to:%s reason:%s",
"level": "VERBOSE",
@@ -4003,6 +4015,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1620751818": {
+ "message": "Dim %s skipping animation and directly setting alpha=%f, blur=%d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_DIMMER",
+ "at": "com\/android\/server\/wm\/SmoothDimmer.java"
+ },
"1621562070": {
"message": " startWCT=%s",
"level": "VERBOSE",
@@ -4560,6 +4578,9 @@
"WM_DEBUG_CONTENT_RECORDING": {
"tag": "WindowManager"
},
+ "WM_DEBUG_DIMMER": {
+ "tag": "WindowManager"
+ },
"WM_DEBUG_DRAW": {
"tag": "WindowManager"
},
diff --git a/graphics/java/Android.bp b/graphics/java/Android.bp
index 63d1f6d..db37a387 100644
--- a/graphics/java/Android.bp
+++ b/graphics/java/Android.bp
@@ -7,6 +7,12 @@
default_applicable_licenses: ["frameworks_base_license"],
}
+aconfig_declarations {
+ name: "framework_graphics_flags",
+ package: "com.android.graphics.flags",
+ srcs: ["android/framework_graphics.aconfig"],
+}
+
filegroup {
name: "framework-graphics-nonupdatable-sources",
srcs: [
diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig
new file mode 100644
index 0000000..9a0a22a
--- /dev/null
+++ b/graphics/java/android/framework_graphics.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.graphics.flags"
+
+flag {
+ name: "exact_compute_bounds"
+ namespace: "core_graphics"
+ description: "Add a function without unused exact param for computeBounds."
+ bug: "304478551"
+}
\ No newline at end of file
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index b5fb13d..0a6fb84 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -16,11 +16,14 @@
package android.graphics;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.graphics.hwui.flags.Flags;
+
import libcore.util.NativeAllocationRegistry;
/**
@@ -125,6 +128,7 @@
* Creates a new gainmap using the provided gainmap as the metadata source and the provided
* bitmap as the replacement for the gainmapContents
*/
+ @FlaggedApi(Flags.FLAG_GAINMAP_CONSTRUCTOR_WITH_METADATA)
public Gainmap(@NonNull Gainmap gainmap, @NonNull Bitmap gainmapContents) {
this(gainmapContents, nCreateCopy(gainmap.mNativePtr));
}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index db1cc44..9fde0fd 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1609,7 +1609,7 @@
/**
* Returns the color of the shadow layer.
*
- * @return the shadow layer's color encoded as a {@link ColorLong}.
+ * @return the shadow layer's color encoded as a {@code ColorLong}.
* @see #setShadowLayer(float,float,float,int)
* @see #setShadowLayer(float,float,float,long)
* @see Color
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index c9c1b23..deb89fa 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -16,11 +16,14 @@
package android.graphics;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
+import com.android.graphics.flags.Flags;
+
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -309,6 +312,7 @@
*
* @param bounds Returns the computed bounds of the path's control points.
*/
+ @FlaggedApi(Flags.FLAG_EXACT_COMPUTE_BOUNDS)
public void computeBounds(@NonNull RectF bounds) {
nComputeBounds(mNativePath, bounds);
}
diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java
index 4461f39..c2a7a84 100644
--- a/graphics/java/android/graphics/drawable/RippleShader.java
+++ b/graphics/java/android/graphics/drawable/RippleShader.java
@@ -109,9 +109,8 @@
+ " float alpha = min(fadeIn, 1. - fadeOutNoise);\n"
+ " vec2 uv = p * in_resolutionScale;\n"
+ " vec2 densityUv = uv - mod(uv, in_noiseScale);\n"
- + " float turbulence = turbulence(uv, in_turbulencePhase);\n"
- + " float sparkleAlpha = sparkles(densityUv, in_noisePhase) * ring * alpha "
- + "* turbulence;\n"
+ + " float turb = turbulence(uv, in_turbulencePhase);\n"
+ + " float sparkleAlpha = sparkles(densityUv, in_noisePhase) * ring * alpha * turb;\n"
+ " float fade = min(fadeIn, 1. - fadeOutRipple);\n"
+ " float waveAlpha = softCircle(p, center, in_maxRadius * scaleIn, 1.) * fade "
+ "* in_color.a;\n"
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index f5e5803..dc1773b 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -39,12 +39,14 @@
/**
* No hyphenation preference is specified.
*
+ * <p>
* This is a special value of hyphenation preference indicating no hyphenation preference is
* specified. When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig}
* with {@link Builder#merge(LineBreakConfig)} function, the hyphenation preference of
* overridden config will be kept if the hyphenation preference of overriding config is
* {@link #HYPHENATION_UNSPECIFIED}.
*
+ * <p>
* <pre>
* val override = LineBreakConfig.Builder()
* .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
@@ -57,6 +59,7 @@
* // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
* </pre>
*
+ * <p>
* This value is resolved to {@link #HYPHENATION_ENABLED} if this value is used for text
* layout/rendering.
*/
@@ -89,6 +92,7 @@
/**
* No line break style is specified.
*
+ * <p>
* This is a special value of line break style indicating no style value is specified.
* When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} with
* {@link Builder#merge(LineBreakConfig)} function, the line break style of overridden config
@@ -107,6 +111,7 @@
* // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
* </pre>
*
+ * <p>
* This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if this value is used for text
* layout/rendering.
*/
@@ -270,6 +275,8 @@
/**
* Resets this builder to the given config state.
*
+ * @param config a config value used for resetting. {@code null} is allowed. If {@code null}
+ * is passed, all configs are reset to unspecified.
* @return This {@code Builder}.
* @hide
*/
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index dc2e794..0e3fb16 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -249,7 +249,7 @@
* @param useBoundsForWidth True for using bounding box, false for advances.
* @return this builder instance
* @see Layout#getUseBoundsForWidth()
- * @see StaticLayout.Builder#setUseBoundsForWidth(boolean)
+ * @see android.text.StaticLayout.Builder#setUseBoundsForWidth(boolean)
*/
@FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public @NonNull Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 0e198d5..e6de597 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -322,7 +322,7 @@
}
/**
- * Returns the {@link CopyResultStatus} of the copy request.
+ * Returns the status of the copy request.
*/
public @CopyResultStatus int getStatus() {
return mStatus;
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 31c2eb2..b7ea04f 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -128,25 +128,6 @@
}
/**
- * Queries user state from Keystore 2.0.
- *
- * @param userId - Android user id of the user.
- * @return UserState enum variant as integer if successful or an error
- */
- public static int getState(int userId) {
- StrictMode.noteDiskRead();
- try {
- return getService().getState(userId);
- } catch (ServiceSpecificException e) {
- Log.e(TAG, "getState failed", e);
- return e.errorCode;
- } catch (Exception e) {
- Log.e(TAG, "Can not connect to keystore", e);
- return SYSTEM_ERROR;
- }
- }
-
- /**
* Informs Keystore 2.0 that an off body event was detected.
*/
public static void onDeviceOffBody() {
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 8045f55..11b8271 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -19,8 +19,6 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.StrictMode;
-import android.os.UserHandle;
-import android.security.maintenance.UserState;
/**
* @hide This should not be made public in its present form because it
@@ -37,15 +35,6 @@
// Used for UID field to indicate the calling UID.
public static final int UID_SELF = -1;
- // States
- public enum State {
- @UnsupportedAppUsage
- UNLOCKED,
- @UnsupportedAppUsage
- LOCKED,
- UNINITIALIZED
- };
-
private static final KeyStore KEY_STORE = new KeyStore();
@UnsupportedAppUsage
@@ -55,28 +44,6 @@
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public State state(int userId) {
- int userState = AndroidKeyStoreMaintenance.getState(userId);
- switch (userState) {
- case UserState.UNINITIALIZED:
- return KeyStore.State.UNINITIALIZED;
- case UserState.LSKF_UNLOCKED:
- return KeyStore.State.UNLOCKED;
- case UserState.LSKF_LOCKED:
- return KeyStore.State.LOCKED;
- default:
- throw new AssertionError(userState);
- }
- }
-
- /** @hide */
- @UnsupportedAppUsage
- public State state() {
- return state(UserHandle.myUserId());
- }
-
- /** @hide */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public byte[] get(String key) {
return null;
}
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index 253d704..5825fac 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -319,7 +319,7 @@
/**
* Returns one of the error codes exported by the class.
*
- * @return a public error code, one of the values in {@link PublicErrorCode}.
+ * @return a public error code
*/
@PublicErrorCode
public int getNumericErrorCode() {
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 96c257b..1ba41b1 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -75,16 +75,18 @@
* {@link java.security.interfaces.ECPublicKey} or {@link java.security.interfaces.RSAPublicKey}
* interfaces.
*
- * <p>For asymmetric key pairs, a self-signed X.509 certificate will be also generated and stored in
- * the Android Keystore. This is because the {@link java.security.KeyStore} abstraction does not
- * support storing key pairs without a certificate. The subject, serial number, and validity dates
- * of the certificate can be customized in this spec. The self-signed certificate may be replaced at
- * a later time by a certificate signed by a Certificate Authority (CA).
+ * <p>For asymmetric key pairs, a X.509 certificate will be also generated and stored in the Android
+ * Keystore. This is because the {@link java.security.KeyStore} abstraction does not support storing
+ * key pairs without a certificate. The subject, serial number, and validity dates of the
+ * certificate can be customized in this spec. The certificate may be replaced at a later time by a
+ * certificate signed by a Certificate Authority (CA).
*
- * <p>NOTE: If a private key is not authorized to sign the self-signed certificate, then the
- * certificate will be created with an invalid signature which will not verify. Such a certificate
- * is still useful because it provides access to the public key. To generate a valid signature for
- * the certificate the key needs to be authorized for all of the following:
+ * <p>NOTE: If attestation is not requested using {@link Builder#setAttestationChallenge(byte[])},
+ * generated certificate may be self-signed. If a private key is not authorized to sign the
+ * certificate, then the certificate will be created with an invalid signature which will not
+ * verify. Such a certificate is still useful because it provides access to the public key. To
+ * generate a valid signature for the certificate the key needs to be authorized for all of the
+ * following:
* <ul>
* <li>{@link KeyProperties#PURPOSE_SIGN},</li>
* <li>operation without requiring the user to be authenticated (see
@@ -989,12 +991,6 @@
* @param purposes set of purposes (e.g., encrypt, decrypt, sign) for which the key can be
* used. Attempts to use the key for any other purpose will be rejected.
*
- * <p>If the set of purposes for which the key can be used does not contain
- * {@link KeyProperties#PURPOSE_SIGN}, the self-signed certificate generated by
- * {@link KeyPairGenerator} of {@code AndroidKeyStore} provider will contain an
- * invalid signature. This is OK if the certificate is only used for obtaining the
- * public key from Android KeyStore.
- *
* <p>See {@link KeyProperties}.{@code PURPOSE} flags.
*/
public Builder(@NonNull String keystoreAlias, @KeyProperties.PurposeEnum int purposes) {
@@ -1140,7 +1136,7 @@
}
/**
- * Sets the subject used for the self-signed certificate of the generated key pair.
+ * Sets the subject used for the certificate of the generated key pair.
*
* <p>By default, the subject is {@code CN=fake}.
*/
@@ -1154,7 +1150,7 @@
}
/**
- * Sets the serial number used for the self-signed certificate of the generated key pair.
+ * Sets the serial number used for the certificate of the generated key pair.
*
* <p>By default, the serial number is {@code 1}.
*/
@@ -1168,8 +1164,7 @@
}
/**
- * Sets the start of the validity period for the self-signed certificate of the generated
- * key pair.
+ * Sets the start of the validity period for the certificate of the generated key pair.
*
* <p>By default, this date is {@code Jan 1 1970}.
*/
@@ -1183,8 +1178,7 @@
}
/**
- * Sets the end of the validity period for the self-signed certificate of the generated key
- * pair.
+ * Sets the end of the validity period for the certificate of the generated key pair.
*
* <p>By default, this date is {@code Jan 1 2048}.
*/
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index c72a42c..18796494 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -151,6 +151,7 @@
static_libs: [
"androidx.appcompat_appcompat",
"androidx.core_core-animation",
+ "androidx.core_core-ktx",
"androidx.arch.core_core-runtime",
"androidx-constraintlayout_constraintlayout",
"androidx.dynamicanimation_dynamicanimation",
diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml
index da8abde..8d2e28b 100644
--- a/libs/WindowManager/Shell/res/values-television/config.xml
+++ b/libs/WindowManager/Shell/res/values-television/config.xml
@@ -45,13 +45,13 @@
<integer name="config_pipForceCloseDelay">5000</integer>
<!-- Animation duration when exit starting window: fade out icon -->
- <integer name="starting_window_app_reveal_icon_fade_out_duration">500</integer>
+ <integer name="starting_window_app_reveal_icon_fade_out_duration">200</integer>
<!-- Animation delay when exit starting window: reveal app -->
- <integer name="starting_window_app_reveal_anim_delay">0</integer>
+ <integer name="starting_window_app_reveal_anim_delay">200</integer>
<!-- Animation duration when exit starting window: reveal app -->
- <integer name="starting_window_app_reveal_anim_duration">500</integer>
+ <integer name="starting_window_app_reveal_anim_duration">300</integer>
<!-- Default animation type when hiding the starting window. The possible values are:
- 0 for radial vanish + slide up
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 06ce371..8cf869b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -87,33 +87,28 @@
mTransitions.addHandler(this);
}
- @Override
- public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- boolean containsEmbeddingSplit = false;
- boolean containsNonEmbeddedChange = false;
- final List<TransitionInfo.Change> changes = info.getChanges();
- for (int i = changes.size() - 1; i >= 0; i--) {
- final TransitionInfo.Change change = changes.get(i);
- if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
- containsNonEmbeddedChange = true;
- } else if (!change.hasFlags(FLAG_FILLS_TASK)) {
+ /** Whether ActivityEmbeddingController should animate this transition. */
+ public boolean shouldAnimate(@NonNull TransitionInfo info) {
+ boolean containsEmbeddingChange = false;
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (!change.hasFlags(FLAG_FILLS_TASK) && change.hasFlags(
+ FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
// Whether the Task contains any ActivityEmbedding split before or after the
// transition.
- containsEmbeddingSplit = true;
+ containsEmbeddingChange = true;
}
}
- if (!containsEmbeddingSplit) {
+ if (!containsEmbeddingChange) {
// Let the system to play the default animation if there is no ActivityEmbedding split
// window. This allows to play the app customized animation when there is no embedding,
// such as the device is in a folded state.
return false;
}
- if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) {
+
+ if (containsNonEmbeddedChange(info) && !handleNonEmbeddedChanges(info.getChanges())) {
return false;
}
+
final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
if (options != null
// Scene-transition will be handled by app side.
@@ -123,6 +118,17 @@
return false;
}
+ return true;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+
+ if (!shouldAnimate(info)) return false;
+
// Start ActivityEmbedding animation.
mTransitionCallbacks.put(transition, finishCallback);
mAnimationRunner.startAnimation(transition, info, startTransaction, finishTransaction);
@@ -136,6 +142,16 @@
mAnimationRunner.cancelAnimationFromMerge();
}
+ /** Whether TransitionInfo contains non-ActivityEmbedding embedded window. */
+ private boolean containsNonEmbeddedChange(@NonNull TransitionInfo info) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) {
final Rect nonClosingEmbeddedArea = new Rect();
for (int i = changes.size() - 1; i >= 0; i--) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 2241c34..ac5ba51e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1784,13 +1784,14 @@
mStackOnLeftOrWillBe = mPositioner.isStackOnLeft(startPosition);
mStackAnimationController.setStackPosition(startPosition);
mExpandedAnimationController.setCollapsePoint(startPosition);
- // Set the translation x so that this bubble will animate in from the same side they
- // expand / collapse on.
- bubble.getIconView().setTranslationX(startPosition.x);
} else if (firstBubble) {
mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
}
+ // Set the view translation x so that this bubble will animate in from the same side they
+ // expand / collapse on.
+ bubble.getIconView().setTranslationX(mStackAnimationController.getStackPosition().x);
+
mBubbleContainer.addView(bubble.getIconView(), 0,
new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
mPositioner.getBubbleSize()));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index c51af46..ea7b2e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -605,6 +605,7 @@
@Provides
static Transitions provideTransitions(Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
ShellTaskOrganizer organizer,
TransactionPool pool,
@@ -612,14 +613,13 @@
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor,
- ShellCommandHandler shellCommandHandler,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) {
// TODO(b/238217847): Force override shell init if registration is disabled
shellInit = new ShellInit(mainExecutor);
}
- return new Transitions(context, shellInit, shellController, organizer, pool,
- displayController, mainExecutor, mainHandler, animExecutor, shellCommandHandler,
+ return new Transitions(context, shellInit, shellCommandHandler, shellController, organizer,
+ pool, displayController, mainExecutor, mainHandler, animExecutor,
rootTaskDisplayAreaOrganizer);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 14a040a..a533ca5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -32,6 +32,7 @@
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
@@ -366,11 +367,12 @@
KeyguardTransitionHandler keyguardTransitionHandler,
Optional<DesktopTasksController> desktopTasksController,
Optional<UnfoldTransitionHandler> unfoldHandler,
+ Optional<ActivityEmbeddingController> activityEmbeddingController,
Transitions transitions) {
return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
pipTransitionController, recentsTransitionHandler,
keyguardTransitionHandler, desktopTasksController,
- unfoldHandler);
+ unfoldHandler, activityEmbeddingController);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS
new file mode 100644
index 0000000..74a29dd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS
@@ -0,0 +1 @@
+hwwang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 8a64037..1898ea7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -17,19 +17,28 @@
package com.android.wm.shell.dagger.pip;
import android.annotation.NonNull;
+import android.content.Context;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipTransition;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import dagger.Module;
import dagger.Provides;
+import java.util.Optional;
+
/**
* Provides dependencies from {@link com.android.wm.shell.pip2}, this implementation is meant to be
* the successor of its sibling {@link Pip1Module}.
@@ -42,8 +51,26 @@
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
- PipBoundsAlgorithm pipBoundsAlgorithm) {
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ Optional<PipController> pipController) {
return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
pipBoundsAlgorithm);
}
+
+ @WMSingleton
+ @Provides
+ static Optional<PipController> providePipController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ if (!PipUtils.isPip2ExperimentEnabled()) {
+ return Optional.empty();
+ } else {
+ return Optional.ofNullable(PipController.create(
+ context, shellInit, shellController, displayController, displayInsetsController,
+ pipDisplayLayoutState));
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
new file mode 100644
index 0000000..186cb61
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -0,0 +1,133 @@
+/*
+ * 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.wm.shell.pip2.phone;
+
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.view.InsetsState;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+
+/**
+ * Manages the picture-in-picture (PIP) UI and states for Phones.
+ */
+public class PipController implements ConfigurationChangeListener,
+ DisplayController.OnDisplaysChangedListener {
+ private static final String TAG = PipController.class.getSimpleName();
+
+ private Context mContext;
+ private ShellController mShellController;
+ private DisplayController mDisplayController;
+ private DisplayInsetsController mDisplayInsetsController;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
+
+ private PipController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ mContext = context;
+ mShellController = shellController;
+ mDisplayController = displayController;
+ mDisplayInsetsController = displayInsetsController;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ private void onInit() {
+ // Ensure that we have the display info in case we get calls to update the bounds before the
+ // listener calls back
+ mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
+ DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
+ mPipDisplayLayoutState.setDisplayLayout(layout);
+
+ mShellController.addConfigurationChangeListener(this);
+ mDisplayController.addDisplayWindowListener(this);
+ mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
+ new DisplayInsetsController.OnInsetsChangedListener() {
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ onDisplayChanged(mDisplayController
+ .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
+ }
+ });
+ }
+
+ /**
+ * Instantiates {@link PipController}, returns {@code null} if the feature not supported.
+ */
+ public static PipController create(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Device doesn't support Pip feature", TAG);
+ return null;
+ }
+ return new PipController(context, shellInit, shellController, displayController,
+ displayInsetsController, pipDisplayLayoutState);
+ }
+
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ mPipDisplayLayoutState.onConfigurationChanged();
+ }
+
+ @Override
+ public void onThemeChanged() {
+ onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()));
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+ return;
+ }
+ onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+ return;
+ }
+ onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+ }
+
+ private void onDisplayChanged(DisplayLayout layout) {
+ mPipDisplayLayoutState.setDisplayLayout(layout);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index d31476c..d277eef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -925,19 +925,8 @@
if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
else wct.restoreTransientOrder(mRecentsTask);
}
- if (!toHome
- // If a recents gesture starts on the 3p launcher, then the 3p launcher is the
- // live tile (pausing app). If the gesture is "cancelled" we need to return to
- // 3p launcher instead of "task-switching" away from it.
- && (!mWillFinishToHome || mPausingSeparateHome)
- && mPausingTasks != null && mState == STATE_NORMAL) {
- if (mPausingSeparateHome) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " returning to 3p home");
- } else {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " returning to app");
- }
+ if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app");
// The gesture is returning to the pausing-task(s) rather than continuing with
// recents, so end the transition by moving the app back to the top (and also
// re-showing it's task).
@@ -969,6 +958,15 @@
wct.restoreTransientOrder(mRecentsTask);
}
} else {
+ if (mPausingSeparateHome) {
+ if (mOpeningTasks.isEmpty()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " recents occluded 3p home");
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " switch task by recents on 3p home");
+ }
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " normal finish");
// The general case: committing to recents, going home, or switching tasks.
for (int i = 0; i < mOpeningTasks.size(); ++i) {
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 68ca231..7a4834c 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
@@ -23,6 +23,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -395,6 +396,13 @@
return mMainStage.isActive();
}
+ /** @return whether this transition-request has the launch-adjacent flag. */
+ public boolean requestHasLaunchAdjacentFlag(TransitionRequestInfo request) {
+ final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
+ return triggerTask != null && triggerTask.baseIntent != null
+ && (triggerTask.baseIntent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0;
+ }
+
/** @return whether the transition-request implies entering pip from split. */
public boolean requestImpliesSplitToPip(TransitionRequestInfo request) {
if (!isSplitActive() || !mMixedHandler.requestHasPipEnter(request)) {
@@ -2459,10 +2467,20 @@
EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
}
- // When split in the background, it should be only opening/dismissing transition and
- // would keep out not empty. Prevent intercepting all transitions for split screen when
- // it is in the background and not identify to handle it.
- return (!out.isEmpty() || isSplitScreenVisible()) ? out : null;
+ if (!out.isEmpty()) {
+ // One of the cases above handled it
+ return out;
+ } else if (isSplitScreenVisible()) {
+ // If split is visible, only defer handling this transition if it's launching
+ // adjacent while there is already a split pair -- this may trigger PIP and
+ // that should be handled by the mixed handler.
+ final boolean deferTransition = requestHasLaunchAdjacentFlag(request)
+ && mMainStage.getChildCount() != 0 && mSideStage.getChildCount() != 0;
+ return !deferTransition ? out : null;
+ }
+ // Don't intercept the transition if we are not handling it as a part of one of the
+ // cases above and it is not already visible
+ return null;
} else {
if (isOpening && getStageOfTask(triggerTask) != null) {
// One task is appearing into split, prepare to enter split screen.
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 451e618..918a5a4 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
@@ -21,7 +21,9 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
@@ -43,6 +45,7 @@
import android.window.WindowContainerTransaction;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -74,6 +77,7 @@
private final KeyguardTransitionHandler mKeyguardHandler;
private DesktopTasksController mDesktopTasksController;
private UnfoldTransitionHandler mUnfoldHandler;
+ private ActivityEmbeddingController mActivityEmbeddingController;
private static class MixedTransition {
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
@@ -93,9 +97,12 @@
/** Recents Transition while in desktop mode. */
static final int TYPE_RECENTS_DURING_DESKTOP = 6;
- /** Fuld/Unfold transition. */
+ /** Fold/Unfold transition. */
static final int TYPE_UNFOLD = 7;
+ /** Enter pip from one of the Activity Embedding windows. */
+ static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 8;
+
/** The default animation for this mixed transition. */
static final int ANIM_TYPE_DEFAULT = 0;
@@ -150,7 +157,8 @@
Optional<RecentsTransitionHandler> recentsHandlerOptional,
KeyguardTransitionHandler keyguardHandler,
Optional<DesktopTasksController> desktopTasksControllerOptional,
- Optional<UnfoldTransitionHandler> unfoldHandler) {
+ Optional<UnfoldTransitionHandler> unfoldHandler,
+ Optional<ActivityEmbeddingController> activityEmbeddingController) {
mPlayer = player;
mKeyguardHandler = keyguardHandler;
if (Transitions.ENABLE_SHELL_TRANSITIONS
@@ -170,6 +178,7 @@
}
mDesktopTasksController = desktopTasksControllerOptional.orElse(null);
mUnfoldHandler = unfoldHandler.orElse(null);
+ mActivityEmbeddingController = activityEmbeddingController.orElse(null);
}, this);
}
}
@@ -192,6 +201,16 @@
mPipHandler.augmentRequest(transition, request, out);
mSplitHandler.addEnterOrExitIfNeeded(request, out);
return out;
+ } else if (request.getType() == TRANSIT_PIP
+ && (request.getFlags() & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0 && (
+ mActivityEmbeddingController != null)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " Got a PiP-enter request from an Activity Embedding split");
+ mActiveTransitions.add(new MixedTransition(
+ MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition));
+ // Postpone transition splitting to later.
+ WindowContainerTransaction out = new WindowContainerTransaction();
+ return out;
} else if (request.getRemoteTransition() != null
&& TransitionUtil.isOpeningType(request.getType())
&& (request.getTriggerTask() == null
@@ -355,6 +374,9 @@
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ return animateEnterPipFromActivityEmbedding(mixed, info, startTransaction,
+ finishTransaction, finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
return false;
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
@@ -400,6 +422,58 @@
}
}
+ private boolean animateEnterPipFromActivityEmbedding(@NonNull MixedTransition mixed,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+ + "entering PIP from an Activity Embedding window");
+ // Split into two transitions (wct)
+ TransitionInfo.Change pipChange = null;
+ final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (mPipHandler.isEnteringPip(change, info.getType())) {
+ if (pipChange != null) {
+ throw new IllegalStateException("More than 1 pip-entering changes in one"
+ + " transition? " + info);
+ }
+ pipChange = change;
+ // going backwards, so remove-by-index is fine.
+ everythingElse.getChanges().remove(i);
+ }
+ }
+
+ final Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ --mixed.mInFlightSubAnimations;
+ mixed.joinFinishArgs(wct);
+ if (mixed.mInFlightSubAnimations > 0) return;
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(mixed.mFinishWCT);
+ };
+
+ if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) {
+ // Fallback to dispatching to other handlers.
+ return false;
+ }
+
+ // PIP window should always be on the highest Z order.
+ if (pipChange != null) {
+ mixed.mInFlightSubAnimations = 2;
+ mPipHandler.startEnterAnimation(
+ pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
+ finishTransaction,
+ finishCB);
+ } else {
+ mixed.mInFlightSubAnimations = 1;
+ }
+
+ mActivityEmbeddingController.startAnimation(mixed.mTransition, everythingElse,
+ startTransaction, finishTransaction, finishCB);
+ return true;
+ }
+
private boolean animateOpenIntentWithRemoteAndPip(@NonNull MixedTransition mixed,
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@@ -811,6 +885,10 @@
} else {
mPipHandler.end();
}
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ mPipHandler.end();
+ mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
mPipHandler.end();
if (mixed.mLeftoversHandler != null) {
@@ -851,6 +929,9 @@
if (mixed == null) return;
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
} else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 8b050e5..b1fc16d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -63,7 +63,7 @@
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (mTransition != transition) return false;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote"
- + " transition %s for #%d.", mRemote, info.getDebugId());
+ + " transition %s for (#%d).", mRemote, info.getDebugId());
final IBinder.DeathRecipient remoteDied = () -> {
Log.e(Transitions.TAG, "Remote transition died, finishing");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 592b22a..ca2c3b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -126,7 +126,7 @@
}
}
}
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for #%d to %s",
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for (#%d) to %s",
info.getDebugId(), pendingRemote);
if (pendingRemote == null) return false;
@@ -241,7 +241,7 @@
if (remote == null) return null;
mRequestedRemotes.put(transition, remote);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested"
- + " for %s: %s", transition, remote);
+ + " for (#%d) %s: %s", request.getDebugId(), transition, remote);
return new WindowContainerTransaction();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
index 87c438a..ba0ef20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
@@ -19,13 +19,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
+import android.util.Slog;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
-import java.util.ArrayList;
-
/**
* A Simple handler that tracks SLEEP transitions. We track them specially since we (ab)use these
* as sentinels for fast-forwarding through animations when the screen is off.
@@ -34,30 +33,25 @@
* don't register it like a normal handler.
*/
class SleepHandler implements Transitions.TransitionHandler {
- final ArrayList<IBinder> mSleepTransitions = new ArrayList<>();
-
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- mSleepTransitions.remove(transition);
- startTransaction.apply();
- finishCallback.onTransitionFinished(null);
- return true;
+ if (info.hasChangesOrSideEffects()) {
+ Slog.e(Transitions.TAG, "Real changes included in a SLEEP transition");
+ return false;
+ } else {
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
}
@Override
@Nullable
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
- mSleepTransitions.add(transition);
return new WindowContainerTransaction();
}
-
- @Override
- public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
- @Nullable SurfaceControl.Transaction finishTransaction) {
- mSleepTransitions.remove(transition);
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 0d9a9e9..576bba96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -179,6 +179,7 @@
private final DefaultTransitionHandler mDefaultTransitionHandler;
private final RemoteTransitionHandler mRemoteTransitionHandler;
private final DisplayController mDisplayController;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
private final SleepHandler mSleepHandler = new SleepHandler();
@@ -188,9 +189,6 @@
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
- @Nullable
- private final ShellCommandHandler mShellCommandHandler;
-
private final ArrayList<TransitionObserver> mObservers = new ArrayList<>();
/** List of {@link Runnable} instances to run when the last active transition has finished. */
@@ -237,7 +235,7 @@
@Override
public String toString() {
if (mInfo != null && mInfo.getDebugId() >= 0) {
- return "(#" + mInfo.getDebugId() + ")" + mToken + "@" + getTrack();
+ return "(#" + mInfo.getDebugId() + ") " + mToken + "@" + getTrack();
}
return mToken.toString() + "@" + getTrack();
}
@@ -275,13 +273,14 @@
@NonNull ShellExecutor mainExecutor,
@NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor) {
- this(context, shellInit, shellController, organizer, pool, displayController, mainExecutor,
- mainHandler, animExecutor, null,
+ this(context, shellInit, new ShellCommandHandler(), shellController, organizer, pool,
+ displayController, mainExecutor, mainHandler, animExecutor,
new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit));
}
public Transitions(@NonNull Context context,
@NonNull ShellInit shellInit,
+ @Nullable ShellCommandHandler shellCommandHandler,
@NonNull ShellController shellController,
@NonNull WindowOrganizer organizer,
@NonNull TransactionPool pool,
@@ -289,7 +288,6 @@
@NonNull ShellExecutor mainExecutor,
@NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor,
- @Nullable ShellCommandHandler shellCommandHandler,
@NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
mOrganizer = organizer;
mContext = context;
@@ -300,13 +298,13 @@
mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
displayController, pool, mainExecutor, mainHandler, animExecutor, rootTDAOrganizer);
mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
+ mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
// The very last handler (0 in the list) should be the default one.
mHandlers.add(mDefaultTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
// Next lowest priority is remote transitions.
mHandlers.add(mRemoteTransitionHandler);
- mShellCommandHandler = shellCommandHandler;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
shellInit.addInitCallback(this::onInit, this);
}
@@ -339,9 +337,8 @@
TransitionMetrics.getInstance();
}
- if (mShellCommandHandler != null) {
- mShellCommandHandler.addCommandCallback("transitions", this, this);
- }
+ mShellCommandHandler.addCommandCallback("transitions", this, this);
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
public boolean isRegistered() {
@@ -655,8 +652,8 @@
void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
- transitionToken, info);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
+ info.getDebugId(), transitionToken, info);
final int activeIdx = findByToken(mPendingTransitions, transitionToken);
if (activeIdx < 0) {
throw new IllegalStateException("Got transitionReady for non-pending transition "
@@ -1073,8 +1070,8 @@
void requestStartTransition(@NonNull IBinder transitionToken,
@Nullable TransitionRequestInfo request) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
- transitionToken, request);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s",
+ request.getDebugId(), transitionToken, request);
if (isTransitionKnown(transitionToken)) {
throw new RuntimeException("Transition already started " + transitionToken);
}
@@ -1475,4 +1472,68 @@
pw.println(prefix + "tracing");
mTracer.printShellCommandHelp(pw, prefix + " ");
}
+
+ private void dump(@NonNull PrintWriter pw, String prefix) {
+ pw.println(prefix + TAG);
+
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + "Handlers:");
+ for (TransitionHandler handler : mHandlers) {
+ pw.print(innerPrefix);
+ pw.print(handler.getClass().getSimpleName());
+ pw.println(" (" + Integer.toHexString(System.identityHashCode(handler)) + ")");
+ }
+
+ pw.println(prefix + "Observers:");
+ for (TransitionObserver observer : mObservers) {
+ pw.print(innerPrefix);
+ pw.println(observer.getClass().getSimpleName());
+ }
+
+ pw.println(prefix + "Pending Transitions:");
+ for (ActiveTransition transition : mPendingTransitions) {
+ pw.print(innerPrefix + "token=");
+ pw.println(transition.mToken);
+ pw.print(innerPrefix + "id=");
+ pw.println(transition.mInfo != null
+ ? transition.mInfo.getDebugId()
+ : -1);
+ pw.print(innerPrefix + "handler=");
+ pw.println(transition.mHandler != null
+ ? transition.mHandler.getClass().getSimpleName()
+ : null);
+ }
+ if (mPendingTransitions.isEmpty()) {
+ pw.println(innerPrefix + "none");
+ }
+
+ pw.println(prefix + "Ready-during-sync Transitions:");
+ for (ActiveTransition transition : mReadyDuringSync) {
+ pw.print(innerPrefix + "token=");
+ pw.println(transition.mToken);
+ pw.print(innerPrefix + "id=");
+ pw.println(transition.mInfo != null
+ ? transition.mInfo.getDebugId()
+ : -1);
+ pw.print(innerPrefix + "handler=");
+ pw.println(transition.mHandler != null
+ ? transition.mHandler.getClass().getSimpleName()
+ : null);
+ }
+ if (mReadyDuringSync.isEmpty()) {
+ pw.println(innerPrefix + "none");
+ }
+
+ pw.println(prefix + "Tracks:");
+ for (int i = 0; i < mTracks.size(); i++) {
+ final ActiveTransition transition = mTracks.get(i).mActiveTransition;
+ pw.println(innerPrefix + "Track #" + i);
+ pw.print(innerPrefix + "active=");
+ pw.println(transition);
+ if (transition != null) {
+ pw.print(innerPrefix + "hander=");
+ pw.println(transition.mHandler);
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 389db62..dadd264 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -18,6 +18,7 @@
import android.graphics.PointF;
import android.graphics.Rect;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.window.WindowContainerTransaction;
@@ -45,6 +46,7 @@
private final int mDisallowedAreaForEndBoundsHeight;
private boolean mHasDragResized;
private int mCtrlType;
+ @Surface.Rotation private int mRotation;
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
DisplayController displayController, int disallowedAreaForEndBoundsHeight) {
@@ -78,7 +80,10 @@
mTaskOrganizer.applyTransaction(wct);
}
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
- if (mStableBounds.isEmpty()) {
+ int rotation = mWindowDecoration
+ .mTaskInfo.configuration.windowConfiguration.getDisplayRotation();
+ if (mStableBounds.isEmpty() || mRotation != rotation) {
+ mRotation = rotation;
mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
.getStableBounds(mStableBounds);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 303954a..852c037 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -21,6 +21,7 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -58,6 +59,7 @@
private final int mDisallowedAreaForEndBoundsHeight;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private int mCtrlType;
+ @Surface.Rotation private int mRotation;
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
@@ -98,7 +100,10 @@
}
mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
- if (mStableBounds.isEmpty()) {
+ int rotation = mDesktopWindowDecoration
+ .mTaskInfo.configuration.windowConfiguration.getDisplayRotation();
+ if (mStableBounds.isEmpty() || mRotation != rotation) {
+ mRotation = rotation;
mDisplayController.getDisplayLayout(mDesktopWindowDecoration.mDisplay.getDisplayId())
.getStableBounds(mStableBounds);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 0548a8e..d0e647b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -297,7 +297,7 @@
}
// Task surface itself
- float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
+ float shadowRadius;
final Point taskPosition = mTaskInfo.positionInParent;
if (isFullscreen) {
// Setting the task crop to the width/height stops input events from being sent to
@@ -308,9 +308,12 @@
// drag-resized by the window decoration.
startT.setWindowCrop(mTaskSurface, null);
finishT.setWindowCrop(mTaskSurface, null);
+ // Shadow is not needed for fullscreen tasks
+ shadowRadius = 0;
} else {
startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+ shadowRadius = loadDimension(resources, params.mShadowRadiusId);
}
startT.setShadowRadius(mTaskSurface, shadowRadius)
.show(mTaskSurface);
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 7f02072..e111edc 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -39,7 +39,25 @@
}
filegroup {
- name: "WMShellFlickerTestsPip-src",
+ name: "WMShellFlickerTestsPip1-src",
+ srcs: [
+ "src/com/android/wm/shell/flicker/pip/A*.kt",
+ "src/com/android/wm/shell/flicker/pip/B*.kt",
+ "src/com/android/wm/shell/flicker/pip/C*.kt",
+ "src/com/android/wm/shell/flicker/pip/D*.kt",
+ "src/com/android/wm/shell/flicker/pip/S*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPip2-src",
+ srcs: [
+ "src/com/android/wm/shell/flicker/pip/E*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPip3-src",
srcs: ["src/com/android/wm/shell/flicker/pip/*.kt"],
}
@@ -176,7 +194,9 @@
],
exclude_srcs: [
":WMShellFlickerTestsBubbles-src",
- ":WMShellFlickerTestsPip-src",
+ ":WMShellFlickerTestsPip1-src",
+ ":WMShellFlickerTestsPip2-src",
+ ":WMShellFlickerTestsPip3-src",
":WMShellFlickerTestsPipCommon-src",
":WMShellFlickerTestsPipApps-src",
":WMShellFlickerTestsSplitScreenGroup1-src",
@@ -207,12 +227,55 @@
instrumentation_target_package: "com.android.wm.shell.flicker.pip",
srcs: [
":WMShellFlickerTestsBase-src",
- ":WMShellFlickerTestsPip-src",
+ ":WMShellFlickerTestsPip3-src",
":WMShellFlickerTestsPipCommon-src",
],
}
android_test {
+ name: "WMShellFlickerTestsPip1",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestPip.xml"],
+ package_name: "com.android.wm.shell.flicker.pip",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerTestsPip1-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsPip2",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestPip.xml"],
+ package_name: "com.android.wm.shell.flicker.pip",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerTestsPip2-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsPip3",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestPip.xml"],
+ package_name: "com.android.wm.shell.flicker.pip",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerTestsPip3-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+ exclude_srcs: [
+ ":WMShellFlickerTestsPip1-src",
+ ":WMShellFlickerTestsPip2-src",
+ ],
+}
+
+android_test {
name: "WMShellFlickerTestsPipApps",
defaults: ["WMShellFlickerTestsDefault"],
additional_manifests: ["manifests/AndroidManifestPip.xml"],
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
index b13e9a2..1df1136 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -81,9 +81,7 @@
<option name="shell-timeout" value="6600s"/>
<option name="test-timeout" value="6000s"/>
<option name="hidden-api-checks" value="false"/>
- <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed
<option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
- -->
<!-- PerfettoListener related arguments -->
<option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
<option name="instrumentation-arg"
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 19c8435..94e3959 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -22,6 +22,7 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -55,7 +56,7 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
- EnterPipViaAppUiButtonTest(flicker) {
+ EnterPipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index b9c9049..da83d4c0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -1169,7 +1169,7 @@
}
@Test
- public void testEmptyTransitionStillReportsKeyguardGoingAway() {
+ public void testEmptyTransition_withKeyguardGoingAway_plays() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -1188,6 +1188,65 @@
}
@Test
+ public void testSleepTransition_withKeyguardGoingAway_plays(){
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ IBinder transitToken = new Binder();
+ transitions.requestStartTransition(transitToken,
+ new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+
+ // Make a no-op transition
+ TransitionInfo info = new TransitionInfoBuilder(
+ TRANSIT_SLEEP, TRANSIT_FLAG_KEYGUARD_GOING_AWAY, true /* noOp */).build();
+ transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+ new StubTransaction());
+
+ // If keyguard-going-away flag set, then it shouldn't be aborted.
+ assertEquals(1, mDefaultHandler.activeCount());
+ }
+
+ @Test
+ public void testSleepTransition_withChanges_plays(){
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ IBinder transitToken = new Binder();
+ transitions.requestStartTransition(transitToken,
+ new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+
+ // Make a transition with some changes
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_SLEEP)
+ .addChange(TRANSIT_OPEN).build();
+ info.setTrack(0);
+ transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+ new StubTransaction());
+
+ // If there is an actual change, then it shouldn't be aborted.
+ assertEquals(1, mDefaultHandler.activeCount());
+ }
+
+
+ @Test
+ public void testSleepTransition_empty_SyncBySleepHandler() {
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ IBinder transitToken = new Binder();
+ transitions.requestStartTransition(transitToken,
+ new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+
+ // Make a no-op transition
+ TransitionInfo info = new TransitionInfoBuilder(
+ TRANSIT_SLEEP, 0x0, true /* noOp */).build();
+ transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+ new StubTransaction());
+
+ // If there is nothing to actually play, it should not be offered to handlers.
+ assertEquals(0, mDefaultHandler.activeCount());
+ }
+
+ @Test
public void testMultipleTracks() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index c0c4498..add78b2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -6,6 +6,9 @@
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.view.Display
+import android.view.Surface
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
import android.view.SurfaceControl
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
@@ -24,6 +27,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
+import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.argThat
import org.mockito.Mockito.never
@@ -76,7 +80,15 @@
whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
- (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ if (mockWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_90 ||
+ mockWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_270
+ ) {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
+ } else {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+ }
}
`when`(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS)
`when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
@@ -89,6 +101,7 @@
defaultMinSize = DEFAULT_MIN
displayId = DISPLAY_ID
configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+ configuration.windowConfiguration.displayRotation = ROTATION_90
}
mockWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -623,7 +636,7 @@
)
val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS.top.toFloat() - 5
+ val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
taskPositioner.onDragPositioningMove(
newX,
newY
@@ -641,11 +654,83 @@
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS.top
+ STABLE_BOUNDS_LANDSCAPE.top
}
})
}
+ @Test
+ fun testDragResize_drag_updatesStableBoundsOnRotate() {
+ // Test landscape stable bounds
+ performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+ val rectAfterDrag = Rect(STARTING_BOUNDS)
+ rectAfterDrag.right += 2000
+ // First drag; we should fetch stable bounds.
+ verify(mockDisplayLayout, Mockito.times(1)).getStableBounds(any())
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag
+ }
+ })
+ // Drag back to starting bounds.
+ performDrag(
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+
+ // Display did not rotate; we should use previous stable bounds
+ verify(mockDisplayLayout, Mockito.times(1)).getStableBounds(any())
+
+ // Rotate the screen to portrait
+ mockWindowDecoration.mTaskInfo.apply {
+ configuration.windowConfiguration.displayRotation = Surface.ROTATION_0
+ }
+ // Test portrait stable bounds
+ performDrag(
+ STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+ rectAfterDrag.right -= 2000
+ rectAfterDrag.bottom += 2000
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag
+ }
+ })
+ // Display has rotated; we expect a new stable bounds.
+ verify(mockDisplayLayout, Mockito.times(2)).getStableBounds(any())
+ }
+
+ private fun performDrag(
+ startX: Float,
+ startY: Float,
+ endX: Float,
+ endY: Float,
+ ctrlType: Int
+ ) {
+ taskPositioner.onDragPositioningStart(
+ ctrlType,
+ startX,
+ startY
+ )
+ taskPositioner.onDragPositioningMove(
+ endX,
+ endY
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ endX,
+ endY
+ )
+ }
+
companion object {
private const val TASK_ID = 5
private const val MIN_WIDTH = 10
@@ -664,11 +749,17 @@
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
DISPLAY_BOUNDS.right,
DISPLAY_BOUNDS.bottom)
- private val STABLE_BOUNDS = Rect(
+ private val STABLE_BOUNDS_LANDSCAPE = Rect(
DISPLAY_BOUNDS.left,
DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
DISPLAY_BOUNDS.right,
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
)
+ private val STABLE_BOUNDS_PORTRAIT = Rect(
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.left + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.bottom,
+ DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 8913453..a70ebf1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -21,6 +21,9 @@
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.view.Display
+import android.view.Surface.ROTATION_0
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.window.WindowContainerToken
@@ -30,6 +33,7 @@
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
@@ -93,10 +97,17 @@
whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
- (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ if (mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_90 ||
+ mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_270
+ ) {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
+ } else {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+ }
}
`when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
-
mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
taskId = TASK_ID
token = taskToken
@@ -105,6 +116,7 @@
defaultMinSize = DEFAULT_MIN
displayId = DISPLAY_ID
configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+ configuration.windowConfiguration.displayRotation = ROTATION_90
}
mockDesktopWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -343,7 +355,7 @@
)
val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS.top.toFloat() - 5
+ val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
taskPositioner.onDragPositioningMove(
newX,
newY
@@ -361,11 +373,79 @@
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS.top
+ STABLE_BOUNDS_LANDSCAPE.top
}
})
}
+ @Test
+ fun testDragResize_drag_updatesStableBoundsOnRotate() {
+ // Test landscape stable bounds
+ performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+ val rectAfterDrag = Rect(STARTING_BOUNDS)
+ rectAfterDrag.right += 2000
+ // First drag; we should fetch stable bounds.
+ verify(mockDisplayLayout, times(1)).getStableBounds(any())
+ verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag}},
+ eq(taskPositioner))
+ // Drag back to starting bounds.
+ performDrag(STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+
+ // Display did not rotate; we should use previous stable bounds
+ verify(mockDisplayLayout, times(1)).getStableBounds(any())
+
+ // Rotate the screen to portrait
+ mockDesktopWindowDecoration.mTaskInfo.apply {
+ configuration.windowConfiguration.displayRotation = ROTATION_0
+ }
+ // Test portrait stable bounds
+ performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+ rectAfterDrag.right -= 2000
+ rectAfterDrag.bottom += 2000
+
+ verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag}},
+ eq(taskPositioner))
+ // Display has rotated; we expect a new stable bounds.
+ verify(mockDisplayLayout, times(2)).getStableBounds(any())
+ }
+
+ private fun performDrag(
+ startX: Float,
+ startY: Float,
+ endX: Float,
+ endY: Float,
+ ctrlType: Int
+ ) {
+ taskPositioner.onDragPositioningStart(
+ ctrlType,
+ startX,
+ startY
+ )
+ taskPositioner.onDragPositioningMove(
+ endX,
+ endY
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ endX,
+ endY
+ )
+ }
+
companion object {
private const val TASK_ID = 5
private const val MIN_WIDTH = 10
@@ -378,11 +458,17 @@
private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
- private val STABLE_BOUNDS = Rect(
+ private val STABLE_BOUNDS_LANDSCAPE = Rect(
DISPLAY_BOUNDS.left,
DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
DISPLAY_BOUNDS.right,
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
)
+ private val STABLE_BOUNDS_PORTRAIT = Rect(
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.left + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.bottom,
+ DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
+ )
}
}
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 7f22693..d056248 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -785,7 +785,7 @@
has_locale = true;
}
- // if we don't have a result yet
+ // if we don't have a result yet
if (!final_result ||
// or this config is better before the locale than the existing result
result->config.isBetterThanBeforeLocale(final_result->config, desired_config) ||
@@ -863,9 +863,12 @@
// We can skip calling ResTable_config::match() if the caller does not care for the
// configuration to match or if we're using the list of types that have already had their
- // configuration matched.
+ // configuration matched. The exception to this is when the user has multiple locales set
+ // because the filtered list will then have values from multiple locales and we will need to
+ // call match() to make sure the current entry matches the config we are currently checking.
const ResTable_config& this_config = type_entry->config;
- if (!(use_filtered || ignore_configuration || this_config.match(desired_config))) {
+ if (!((use_filtered && (configurations_.size() == 1))
+ || ignore_configuration || this_config.match(desired_config))) {
continue;
}
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index d0d3c5e..e672b98 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -20,3 +20,10 @@
description: "APIs to help enable animations involving gainmaps"
bug: "296482289"
}
+
+flag {
+ name: "gainmap_constructor_with_metadata"
+ namespace: "core_graphics"
+ description: "APIs to create a new gainmap with a bitmap for metadata."
+ bug: "304478551"
+}
diff --git a/libs/hwui/api/current.txt b/libs/hwui/api/current.txt
index 7940821..c396a20 100644
--- a/libs/hwui/api/current.txt
+++ b/libs/hwui/api/current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.graphics {
public class ColorMatrix {
diff --git a/libs/hwui/api/module-lib-current.txt b/libs/hwui/api/module-lib-current.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/module-lib-current.txt
+++ b/libs/hwui/api/module-lib-current.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/module-lib-removed.txt b/libs/hwui/api/module-lib-removed.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/module-lib-removed.txt
+++ b/libs/hwui/api/module-lib-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/removed.txt b/libs/hwui/api/removed.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/removed.txt
+++ b/libs/hwui/api/removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/system-current.txt b/libs/hwui/api/system-current.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/system-current.txt
+++ b/libs/hwui/api/system-current.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/system-removed.txt b/libs/hwui/api/system-removed.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/system-removed.txt
+++ b/libs/hwui/api/system-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 90850d3..22c5862 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -26,6 +26,7 @@
#include <gui/TraceUtils.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
+#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
#include <ui/FatVector.h>
#include <vk/GrVkExtensions.h>
#include <vk/GrVkTypes.h>
@@ -435,7 +436,7 @@
options.fContextDeleteContext = this;
options.fContextDeleteProc = onGrContextReleased;
- return GrDirectContext::MakeVulkan(backendContext, options);
+ return GrDirectContexts::MakeVulkan(backendContext, options);
}
VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const {
diff --git a/location/java/android/location/GnssSignalType.java b/location/java/android/location/GnssSignalType.java
index 16c3f2e..2dd67f0 100644
--- a/location/java/android/location/GnssSignalType.java
+++ b/location/java/android/location/GnssSignalType.java
@@ -34,8 +34,7 @@
/**
* Creates a {@link GnssSignalType} with a full list of parameters.
*
- * @param constellationType the constellation type as defined in
- * {@link GnssStatus.ConstellationType}
+ * @param constellationType the constellation type
* @param carrierFrequencyHz the carrier frequency in Hz
* @param codeType the code type as defined in {@link GnssMeasurement#getCodeType()}
*/
@@ -66,7 +65,7 @@
this.mCodeType = codeType;
}
- /** Returns the {@link GnssStatus.ConstellationType}. */
+ /** Returns the constellation type. */
@GnssStatus.ConstellationType
public int getConstellationType() {
return mConstellationType;
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index ceb3858..b002bbf 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -1282,10 +1282,8 @@
* {@link AudioFormat#CHANNEL_OUT_BACK_CENTER},
* {@link AudioFormat#CHANNEL_OUT_SIDE_LEFT},
* {@link AudioFormat#CHANNEL_OUT_SIDE_RIGHT}.
- * <p> For a valid {@link AudioTrack} channel position mask,
- * the following conditions apply:
- * <br> (1) at most eight channel positions may be used;
- * <br> (2) right/left pairs should be matched.
+ * <p> For output or {@link AudioTrack}, channel position masks which do not contain
+ * matched left/right pairs are invalid.
* <p> For input or {@link AudioRecord}, the mask should be
* {@link AudioFormat#CHANNEL_IN_MONO} or
* {@link AudioFormat#CHANNEL_IN_STEREO}. {@link AudioFormat#CHANNEL_IN_MONO} is
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 7eb0c76..4a5b4f2 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -74,8 +74,7 @@
// Methods for MediaRouter2Manager
List<RoutingSessionInfo> getRemoteSessions(IMediaRouter2Manager manager);
- RoutingSessionInfo getSystemSessionInfoForPackage(
- IMediaRouter2Manager manager, String packageName);
+ RoutingSessionInfo getSystemSessionInfoForPackage(String packageName);
void registerManager(IMediaRouter2Manager manager, String packageName);
void unregisterManager(IMediaRouter2Manager manager);
void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId,
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 8c63580..159427b 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -463,7 +463,7 @@
/**
* Returns the current {@link RouteListingPreference} of the target router.
*
- * <p>If this instance was created using {@link #getInstance(Context, String)}, then it returns
+ * <p>If this instance was created using {@code #getInstance(Context, String)}, then it returns
* the last {@link RouteListingPreference} set by the process this router was created for.
*
* @see #setRouteListingPreference(RouteListingPreference)
@@ -2059,9 +2059,7 @@
public RoutingSessionInfo getSystemSessionInfo() {
RoutingSessionInfo result;
try {
- result =
- mMediaRouterService.getSystemSessionInfoForPackage(
- mClient, mClientPackageName);
+ result = mMediaRouterService.getSystemSessionInfoForPackage(mClientPackageName);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 3abfc629..830708c 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -377,7 +377,7 @@
@Nullable
public RoutingSessionInfo getSystemRoutingSession(@Nullable String packageName) {
try {
- return mMediaRouterService.getSystemSessionInfoForPackage(mClient, packageName);
+ return mMediaRouterService.getSystemSessionInfoForPackage(packageName);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index e9a6ed4..9ced2a4 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -16,6 +16,7 @@
package android.media.audiopolicy;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -48,6 +49,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.media.audio.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -417,6 +419,7 @@
* @return {@link AudioManager#SUCCESS} if the update was successful,
* {@link AudioManager#ERROR} otherwise.
*/
+ @FlaggedApi(Flags.FLAG_AUDIO_POLICY_UPDATE_MIXING_RULES_API)
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public int updateMixingRules(
@NonNull List<Pair<AudioMix, AudioMixingRule>> mixingRuleUpdates) {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index a354f91..c9cfa67 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -27,3 +27,10 @@
description: "Disables the broadcast receiver that prevents scanning when the screen is off."
bug: "304234628"
}
+
+flag {
+ namespace: "media_solutions"
+ name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling"
+ description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller."
+ bug: "293743975"
+}
diff --git a/media/java/android/media/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java
index 6e2aaab..c54bfce 100644
--- a/media/java/android/media/midi/MidiUmpDeviceService.java
+++ b/media/java/android/media/midi/MidiUmpDeviceService.java
@@ -16,6 +16,9 @@
package android.media.midi;
+import static com.android.media.midi.flags.Flags.FLAG_VIRTUAL_UMP;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Service;
@@ -38,7 +41,7 @@
* of {@link MidiReceiver}s for sending data out the output ports.
*
* Unlike traditional MIDI byte streams, only complete UMPs should be sent.
- * Unlike with {@link #MidiDeviceService}, the number of input and output ports must be equal.
+ * Unlike with {@link MidiDeviceService}, the number of input and output ports must be equal.
*
* <p>To extend this class, you must declare the service in your manifest file with
* an intent filter with the {@link #SERVICE_INTERFACE} action
@@ -54,9 +57,11 @@
* android:resource="@xml/device_info" />
* </service></pre>
*/
+@FlaggedApi(FLAG_VIRTUAL_UMP)
public abstract class MidiUmpDeviceService extends Service {
private static final String TAG = "MidiUmpDeviceService";
+ @FlaggedApi(FLAG_VIRTUAL_UMP)
public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService";
private IMidiManager mMidiManager;
@@ -75,6 +80,7 @@
}
};
+ @FlaggedApi(FLAG_VIRTUAL_UMP)
@Override
public void onCreate() {
mMidiManager = IMidiManager.Stub.asInterface(
@@ -112,6 +118,7 @@
* The number of input and output ports must be equal and non-zero.
* @return list of MidiReceivers
*/
+ @FlaggedApi(FLAG_VIRTUAL_UMP)
public abstract @NonNull List<MidiReceiver> onGetInputPortReceivers();
/**
@@ -120,6 +127,7 @@
* The number of input and output ports must be equal and non-zero.
* @return the list of MidiReceivers
*/
+ @FlaggedApi(FLAG_VIRTUAL_UMP)
public final @NonNull List<MidiReceiver> getOutputPortReceivers() {
if (mServer == null) {
return new ArrayList<MidiReceiver>();
@@ -132,6 +140,7 @@
* Returns the {@link MidiDeviceInfo} instance for this service
* @return the MidiDeviceInfo of the virtual MIDI device if it was successfully created
*/
+ @FlaggedApi(FLAG_VIRTUAL_UMP)
public final @Nullable MidiDeviceInfo getDeviceInfo() {
return mDeviceInfo;
}
@@ -140,6 +149,7 @@
* Called to notify when the {@link MidiDeviceStatus} has changed
* @param status the current status of the MIDI device
*/
+ @FlaggedApi(FLAG_VIRTUAL_UMP)
public void onDeviceStatusChanged(@NonNull MidiDeviceStatus status) {
}
@@ -147,9 +157,11 @@
* Called to notify when the virtual MIDI device running in this service has been closed by
* all its clients
*/
+ @FlaggedApi(FLAG_VIRTUAL_UMP)
public void onClose() {
}
+ @FlaggedApi(FLAG_VIRTUAL_UMP)
@Override
public @Nullable IBinder onBind(@NonNull Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) {
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index d294601..31e65eb 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -156,4 +156,24 @@
+ ".permission.MANAGE_MEDIA_PROJECTION)")
void setUserReviewGrantedConsentResult(ReviewGrantedConsentResult consentResult,
in @nullable IMediaProjection projection);
+
+ /**
+ * Notifies system server that we are handling a particular state during the consent flow.
+ *
+ * <p>Only used for emitting atoms.
+ *
+ * @param hostUid The uid of the process requesting consent to capture, may be an app or
+ * SystemUI.
+ * @param state The state that SystemUI is handling during the consent flow.
+ * Must be a valid
+ * state defined in the MediaProjectionState enum.
+ * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED.
+ * Indicates the entry point for requesting the permission. Must be
+ * a valid state defined
+ * in the SessionCreationSource enum.
+ */
+ @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ oneway void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource);
}
diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml
index e886558..7d79a6c 100644
--- a/media/tests/MediaFrameworkTest/AndroidManifest.xml
+++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
diff --git a/media/tests/MediaFrameworkTest/AndroidTest.xml b/media/tests/MediaFrameworkTest/AndroidTest.xml
index 132028c..91c92cc1 100644
--- a/media/tests/MediaFrameworkTest/AndroidTest.xml
+++ b/media/tests/MediaFrameworkTest/AndroidTest.xml
@@ -23,5 +23,6 @@
<option name="package" value="com.android.mediaframeworktest" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
+ <option name="isolated-storage" value="false"/>
</test>
</configuration>
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
index 9be7004..30edfa4 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
@@ -44,7 +44,6 @@
@Override
public TestSuite getAllTests() {
TestSuite suite = new InstrumentationTestSuite(this);
- addMediaMetadataRetrieverStateUnitTests(suite);
addMediaRecorderStateUnitTests(suite);
addMediaPlayerStateUnitTests(suite);
addMediaScannerUnitTests(suite);
@@ -70,11 +69,6 @@
}
// Running all unit tests checking the state machine may be time-consuming.
- private void addMediaMetadataRetrieverStateUnitTests(TestSuite suite) {
- suite.addTestSuite(MediaMetadataRetrieverTest.class);
- }
-
- // Running all unit tests checking the state machine may be time-consuming.
private void addMediaRecorderStateUnitTests(TestSuite suite) {
suite.addTestSuite(MediaRecorderPrepareStateUnitTest.class);
suite.addTestSuite(MediaRecorderResetStateUnitTest.class);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
index bdca474..f70d2d1 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
@@ -16,26 +16,34 @@
package com.android.mediaframeworktest.unit;
+import static org.junit.Assert.assertTrue;
+
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
-import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.mediaframeworktest.MediaNames;
import com.android.mediaframeworktest.MediaProfileReader;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.FileOutputStream;
import java.io.IOException;
-public class MediaMetadataRetrieverTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class MediaMetadataRetrieverTest {
private static final String TAG = "MediaMetadataRetrieverTest";
// Test album art extraction.
@MediumTest
- public static void testGetEmbeddedPicture() throws Exception {
+ @Test
+ public void testGetEmbeddedPicture() throws Exception {
Log.v(TAG, "testGetEmbeddedPicture starts.");
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
boolean supportWMA = MediaProfileReader.getWMAEnable();
@@ -78,7 +86,8 @@
// Test frame capture
@LargeTest
- public static void testThumbnailCapture() throws Exception {
+ @Test
+ public void testThumbnailCapture() throws Exception {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
boolean supportWMA = MediaProfileReader.getWMAEnable();
boolean supportWMV = MediaProfileReader.getWMVEnable();
@@ -134,7 +143,8 @@
}
@LargeTest
- public static void testMetadataRetrieval() throws Exception {
+ @Test
+ public void testMetadataRetrieval() throws Exception {
boolean supportWMA = MediaProfileReader.getWMAEnable();
boolean supportWMV = MediaProfileReader.getWMVEnable();
boolean hasFailed = false;
@@ -169,7 +179,8 @@
// If the specified call order and valid media file is used, no exception
// should be thrown.
@MediumTest
- public static void testBasicNormalMethodCallSequence() throws Exception {
+ @Test
+ public void testBasicNormalMethodCallSequence() throws Exception {
boolean hasFailed = false;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
@@ -197,7 +208,8 @@
// If setDataSource() has not been called, both getFrameAtTime() and extractMetadata() must
// return null.
@MediumTest
- public static void testBasicAbnormalMethodCallSequence() {
+ @Test
+ public void testBasicAbnormalMethodCallSequence() {
boolean hasFailed = false;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
if (retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM) != null) {
@@ -213,7 +225,8 @@
// Test setDataSource()
@MediumTest
- public static void testSetDataSource() throws IOException {
+ @Test
+ public void testSetDataSource() throws IOException {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
boolean hasFailed = false;
diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp
index 58bcd1d..e71597a 100644
--- a/omapi/aidl/Android.bp
+++ b/omapi/aidl/Android.bp
@@ -24,6 +24,11 @@
backend: {
java: {
sdk_version: "module_current",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.nfcservices",
+ ],
+
},
rust: {
enabled: true,
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 4c313b2..3409c29 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -16,6 +16,8 @@
package com.android.externalstorage;
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.usage.StorageStatsManager;
@@ -64,7 +66,19 @@
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;
+import java.util.regex.Pattern;
+/**
+ * Presents content of the shared (a.k.a. "external") storage.
+ * <p>
+ * Starting with Android 11 (R), restricts access to the certain sections of the shared storage:
+ * {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/}, that will be hidden in
+ * the DocumentsUI by default.
+ * See <a href="https://developer.android.com/about/versions/11/privacy/storage">
+ * Storage updates in Android 11</a>.
+ * <p>
+ * Documents ID format: {@code root:path/to/file}.
+ */
public class ExternalStorageProvider extends FileSystemProvider {
private static final String TAG = "ExternalStorage";
@@ -75,7 +89,12 @@
private static final Uri BASE_URI =
new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build();
- // docId format: root:path/to/file
+ /**
+ * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and
+ * {@code /Android/sandbox/} along with all their subdirectories and content.
+ */
+ private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES =
+ Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE);
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
@@ -278,76 +297,91 @@
return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
}
+ /**
+ * Mark {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/} on the
+ * integrated shared ("external") storage along with all their content and subdirectories as
+ * hidden.
+ */
@Override
- public Cursor queryChildDocumentsForManage(
- String parentDocId, String[] projection, String sortOrder)
- throws FileNotFoundException {
- return queryChildDocumentsShowAll(parentDocId, projection, sortOrder);
+ protected boolean shouldHideDocument(@NonNull String documentId) {
+ // Don't need to hide anything on USB drives.
+ if (isOnRemovableUsbStorage(documentId)) {
+ return false;
+ }
+
+ final String path = getPathFromDocId(documentId);
+ return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches();
}
/**
* Check that the directory is the root of storage or blocked file from tree.
+ * <p>
+ * Note, that this is different from hidden documents: blocked documents <b>WILL</b> appear
+ * the UI, but the user <b>WILL NOT</b> be able to select them.
*
- * @param docId the docId of the directory to be checked
+ * @param documentId the docId of the directory to be checked
* @return true, should be blocked from tree. Otherwise, false.
+ *
+ * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE
*/
@Override
- protected boolean shouldBlockFromTree(@NonNull String docId) {
- try {
- final File dir = getFileForDocId(docId, false /* visible */);
-
- // the file is null or it is not a directory
- if (dir == null || !dir.isDirectory()) {
- return false;
- }
-
- // Allow all directories on USB, including the root.
- try {
- RootInfo rootInfo = getRootFromDocId(docId);
- if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) {
- return false;
- }
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Failed to determine rootInfo for docId");
- }
-
- final String path = getPathFromDocId(docId);
-
- // Block the root of the storage
- if (path.isEmpty()) {
- return true;
- }
-
- // Block Download folder from tree
- if (TextUtils.equals(Environment.DIRECTORY_DOWNLOADS.toLowerCase(Locale.ROOT),
- path.toLowerCase(Locale.ROOT))) {
- return true;
- }
-
- // Block /Android
- if (TextUtils.equals(Environment.DIRECTORY_ANDROID.toLowerCase(Locale.ROOT),
- path.toLowerCase(Locale.ROOT))) {
- return true;
- }
-
- // Block /Android/data, /Android/obb, /Android/sandbox and sub dirs
- if (shouldHide(dir)) {
- return true;
- }
-
+ protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId)
+ throws FileNotFoundException {
+ final File dir = getFileForDocId(documentId, false);
+ // The file is null or it is not a directory
+ if (dir == null || !dir.isDirectory()) {
return false;
- } catch (IOException e) {
- throw new IllegalArgumentException(
- "Failed to determine if " + docId + " should block from tree " + ": " + e);
}
+
+ // Allow all directories on USB, including the root.
+ if (isOnRemovableUsbStorage(documentId)) {
+ return false;
+ }
+
+ // Get canonical(!) path. Note that this path will have neither leading nor training "/".
+ // This the root's path will be just an empty string.
+ final String path = getPathFromDocId(documentId);
+
+ // Block the root of the storage
+ if (path.isEmpty()) {
+ return true;
+ }
+
+ // Block /Download/ and /Android/ folders from the tree.
+ if (equalIgnoringCase(path, Environment.DIRECTORY_DOWNLOADS) ||
+ equalIgnoringCase(path, Environment.DIRECTORY_ANDROID)) {
+ return true;
+ }
+
+ // This shouldn't really make a difference, but just in case - let's block hidden
+ // directories as well.
+ if (shouldHideDocument(documentId)) {
+ return true;
+ }
+
+ return false;
}
+ private boolean isOnRemovableUsbStorage(@NonNull String documentId) {
+ final RootInfo rootInfo;
+ try {
+ rootInfo = getRootFromDocId(documentId);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Failed to determine rootInfo for docId\"" + documentId + '"');
+ return false;
+ }
+
+ return (rootInfo.flags & Root.FLAG_REMOVABLE_USB) != 0;
+ }
+
+ @NonNull
@Override
- protected String getDocIdForFile(File file) throws FileNotFoundException {
+ protected String getDocIdForFile(@NonNull File file) throws FileNotFoundException {
return getDocIdForFileMaybeCreate(file, false);
}
- private String getDocIdForFileMaybeCreate(File file, boolean createNewDir)
+ @NonNull
+ private String getDocIdForFileMaybeCreate(@NonNull File file, boolean createNewDir)
throws FileNotFoundException {
String path = file.getAbsolutePath();
@@ -417,31 +451,33 @@
private File getFileForDocId(String docId, boolean visible, boolean mustExist)
throws FileNotFoundException {
RootInfo root = getRootFromDocId(docId);
- return buildFile(root, docId, visible, mustExist);
+ return buildFile(root, docId, mustExist);
}
- private Pair<RootInfo, File> resolveDocId(String docId, boolean visible)
- throws FileNotFoundException {
+ private Pair<RootInfo, File> resolveDocId(String docId) throws FileNotFoundException {
RootInfo root = getRootFromDocId(docId);
- return Pair.create(root, buildFile(root, docId, visible, true));
+ return Pair.create(root, buildFile(root, docId, /* mustExist */ true));
}
@VisibleForTesting
- static String getPathFromDocId(String docId) throws IOException {
+ static String getPathFromDocId(String docId) {
final int splitIndex = docId.indexOf(':', 1);
final String docIdPath = docId.substring(splitIndex + 1);
- // Get CanonicalPath and remove the first "/"
- final String canonicalPath = new File(docIdPath).getCanonicalPath().substring(1);
- if (canonicalPath.isEmpty()) {
- return canonicalPath;
+ // Canonicalize path and strip the leading "/"
+ final String path;
+ try {
+ path = new File(docIdPath).getCanonicalPath().substring(1);
+ } catch (IOException e) {
+ Log.w(TAG, "Could not canonicalize \"" + docIdPath + '"');
+ return "";
}
- // remove trailing "/"
- if (canonicalPath.charAt(canonicalPath.length() - 1) == '/') {
- return canonicalPath.substring(0, canonicalPath.length() - 1);
+ // Remove the trailing "/" as well.
+ if (!path.isEmpty() && path.charAt(path.length() - 1) == '/') {
+ return path.substring(0, path.length() - 1);
} else {
- return canonicalPath;
+ return path;
}
}
@@ -460,7 +496,7 @@
return root;
}
- private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist)
+ private File buildFile(RootInfo root, String docId, boolean mustExist)
throws FileNotFoundException {
final int splitIndex = docId.indexOf(':', 1);
final String path = docId.substring(splitIndex + 1);
@@ -544,7 +580,7 @@
@Override
public Path findDocumentPath(@Nullable String parentDocId, String childDocId)
throws FileNotFoundException {
- final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false);
+ final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId);
final RootInfo root = resolvedDocId.first;
File child = resolvedDocId.second;
@@ -648,6 +684,13 @@
}
}
+ /**
+ * Print the state into the given stream.
+ * Gets invoked when you run:
+ * <pre>
+ * adb shell dumpsys activity provider com.android.externalstorage/.ExternalStorageProvider
+ * </pre>
+ */
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160);
@@ -731,4 +774,8 @@
}
return bundle;
}
+
+ private static boolean equalIgnoringCase(@NonNull String a, @NonNull String b) {
+ return TextUtils.equals(a.toLowerCase(Locale.ROOT), b.toLowerCase(Locale.ROOT));
+ }
}
diff --git a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
index 18a8edc..0144b6e 100644
--- a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
+++ b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
@@ -16,47 +16,64 @@
package com.android.externalstorage;
+import static android.provider.DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID;
+
import static com.android.externalstorage.ExternalStorageProvider.AUTHORITY;
import static com.android.externalstorage.ExternalStorageProvider.getPathFromDocId;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.app.Instrumentation;
+import android.content.Context;
import android.content.pm.ProviderInfo;
+import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class ExternalStorageProviderTest {
+
+ @NonNull
+ private static final Instrumentation sInstrumentation =
+ InstrumentationRegistry.getInstrumentation();
+ @NonNull
+ private static final Context sTargetContext = sInstrumentation.getTargetContext();
+
+ private ExternalStorageProvider mExternalStorageProvider;
+
+ @Before
+ public void setUp() {
+ mExternalStorageProvider = new ExternalStorageProvider();
+ }
+
+
@Test
- public void onCreate_shouldUpdateVolumes() throws Exception {
- ExternalStorageProvider externalStorageProvider = new ExternalStorageProvider();
- ExternalStorageProvider spyProvider = spy(externalStorageProvider);
- ProviderInfo providerInfo = new ProviderInfo();
+ public void onCreate_shouldUpdateVolumes() {
+ final ExternalStorageProvider spyProvider = spy(mExternalStorageProvider);
+
+ final ProviderInfo providerInfo = new ProviderInfo();
providerInfo.authority = AUTHORITY;
providerInfo.grantUriPermissions = true;
providerInfo.exported = true;
- InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- spyProvider.attachInfoForTesting(
- InstrumentationRegistry.getTargetContext(), providerInfo);
- }
- });
+ sInstrumentation.runOnMainSync(() ->
+ spyProvider.attachInfoForTesting(sTargetContext, providerInfo));
verify(spyProvider, atLeast(1)).updateVolumes();
}
@Test
- public void testGetPathFromDocId() throws Exception {
+ public void test_getPathFromDocId() {
final String root = "root";
final String path = "abc/def/ghi";
String docId = root + ":" + path;
@@ -79,4 +96,62 @@
docId = root + ":" + twoDotPath;
assertEquals(getPathFromDocId(docId), path);
}
+
+ @Test
+ public void test_shouldHideDocument() {
+ // Should hide "Android/data", "Android/obb", "Android/sandbox" and all their
+ // "subtrees".
+ final String[] shouldHide = {
+ // "Android/data" and all its subdirectories
+ "Android/data",
+ "Android/data/com.my.app",
+ "Android/data/com.my.app/cache",
+ "Android/data/com.my.app/cache/image.png",
+ "Android/data/mydata",
+
+ // "Android/obb" and all its subdirectories
+ "Android/obb",
+ "Android/obb/com.my.app",
+ "Android/obb/com.my.app/file.blob",
+
+ // "Android/sandbox" and all its subdirectories
+ "Android/sandbox",
+ "Android/sandbox/com.my.app",
+
+ // Also make sure we are not allowing path traversals
+ "Android/./data",
+ "Android/Download/../data",
+ };
+ for (String path : shouldHide) {
+ final String docId = buildDocId(path);
+ assertTrue("ExternalStorageProvider should hide \"" + docId + "\", but it didn't",
+ mExternalStorageProvider.shouldHideDocument(docId));
+ }
+
+ // Should NOT hide anything else.
+ final String[] shouldNotHide = {
+ "Android",
+ "Android/datadir",
+ "Documents",
+ "Download",
+ "Music",
+ "Pictures",
+ };
+ for (String path : shouldNotHide) {
+ final String docId = buildDocId(path);
+ assertFalse("ExternalStorageProvider should NOT hide \"" + docId + "\", but it did",
+ mExternalStorageProvider.shouldHideDocument(docId));
+ }
+ }
+
+ @NonNull
+ private static String buildDocId(@NonNull String path) {
+ return buildDocId(EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID, path);
+ }
+
+ @NonNull
+ private static String buildDocId(@NonNull String root, @NonNull String path) {
+ // docId format: root:path/to/file
+ return root + ':' + path;
+ }
}
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 1cc2867..0b7a568 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -26,7 +26,7 @@
}
allprojects {
- extra["jetpackComposeVersion"] = "1.6.0-alpha02"
+ extra["jetpackComposeVersion"] = "1.6.0-alpha07"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 01596d2..d62b490 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -40,6 +40,7 @@
import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
import com.android.settingslib.spa.gallery.page.SliderPageProvider
+import com.android.settingslib.spa.gallery.preference.ListPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
import com.android.settingslib.spa.gallery.preference.PreferencePageProvider
@@ -74,6 +75,7 @@
PreferencePageProvider,
SwitchPreferencePageProvider,
MainSwitchPreferencePageProvider,
+ ListPreferencePageProvider,
TwoTargetSwitchPreferencePageProvider,
ArgumentPageProvider,
SliderPageProvider,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
similarity index 96%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
index df1d7d1..b001cad 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
@@ -18,8 +18,8 @@
import android.os.Bundle
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.Launch
import androidx.compose.material.icons.outlined.Delete
-import androidx.compose.material.icons.outlined.Launch
import androidx.compose.material.icons.outlined.WarningAmber
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
@@ -47,7 +47,7 @@
override fun Page(arguments: Bundle?) {
RegularScaffold(title = TITLE) {
val actionButtons = listOf(
- ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+ ActionButton(text = "Open", imageVector = Icons.AutoMirrored.Outlined.Launch) {},
ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {},
ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {},
)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
new file mode 100644
index 0000000..43b6d0b
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.ListPreference
+import com.android.settingslib.spa.widget.preference.ListPreferenceModel
+import com.android.settingslib.spa.widget.preference.ListPreferenceOption
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.flow
+
+private const val TITLE = "Sample ListPreference"
+
+object ListPreferencePageProvider : SettingsPageProvider {
+ override val name = "ListPreference"
+ private val owner = createSettingsPage()
+
+ override fun buildEntry(arguments: Bundle?) = listOf(
+ SettingsEntryBuilder.create("ListPreference", owner)
+ .setUiLayoutFn {
+ SampleListPreference()
+ }.build(),
+ SettingsEntryBuilder.create("ListPreference not changeable", owner)
+ .setUiLayoutFn {
+ SampleNotChangeableListPreference()
+ }.build(),
+ )
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner)
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
+ }
+
+ override fun getTitle(arguments: Bundle?) = TITLE
+}
+
+@Composable
+private fun SampleListPreference() {
+ val selectedId = rememberSaveable { mutableIntStateOf(1) }
+ ListPreference(remember {
+ object : ListPreferenceModel {
+ override val title = "Preferred network type"
+ override val options = listOf(
+ ListPreferenceOption(id = 1, text = "5G (recommended)"),
+ ListPreferenceOption(id = 2, text = "LTE"),
+ ListPreferenceOption(id = 3, text = "3G"),
+ )
+ override val selectedId = selectedId
+ override val onIdSelected: (id: Int) -> Unit = { selectedId.intValue = it }
+ }
+ })
+}
+
+@Composable
+private fun SampleNotChangeableListPreference() {
+ val selectedId = rememberSaveable { mutableIntStateOf(1) }
+ val enableFlow = flow {
+ var enabled = true
+ while (true) {
+ delay(3.seconds)
+ enabled = !enabled
+ emit(enabled)
+ }
+ }
+ val enabled = enableFlow.collectAsStateWithLifecycle(initialValue = true)
+ ListPreference(remember {
+ object : ListPreferenceModel {
+ override val title = "Preferred network type"
+ override val enabled = enabled
+ override val options = listOf(
+ ListPreferenceOption(id = 1, text = "5G (recommended)"),
+ ListPreferenceOption(id = 2, text = "LTE"),
+ ListPreferenceOption(id = 3, text = "3G"),
+ )
+ override val selectedId = selectedId
+ override val onIdSelected: (id: Int) -> Unit = { selectedId.intValue = it }
+ }
+ })
+}
+
+@Preview
+@Composable
+private fun ListPreferencePagePreview() {
+ SettingsTheme {
+ ListPreferencePageProvider.Page(null)
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
similarity index 95%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
index eddede7..ce9678b 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
@@ -36,6 +36,7 @@
PreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
SwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
MainSwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ ListPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
TwoTargetSwitchPreferencePageProvider.buildInjectEntry()
.setLink(fromPage = owner).build(),
)
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 8b56336..aafae5f 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,11 +15,11 @@
#
[versions]
-agp = "8.1.1"
+agp = "8.1.2"
compose-compiler = "1.5.1"
dexmaker-mockito = "2.28.3"
kotlin = "1.9.0"
-truth = "1.1"
+truth = "1.1.5"
[libraries]
dexmaker-mockito = { module = "com.linkedin.dexmaker:dexmaker-mockito", version.ref = "dexmaker-mockito" }
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index da04f42..ce89de6 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,7 +16,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index b810511..b73bbd8 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,13 +57,13 @@
api("androidx.slice:slice-builders:1.1.0-alpha02")
api("androidx.slice:slice-core:1.1.0-alpha02")
api("androidx.slice:slice-view:1.1.0-alpha02")
- api("androidx.compose.material3:material3:1.2.0-alpha04")
+ api("androidx.compose.material3:material3:1.2.0-alpha09")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.7.1")
+ api("androidx.navigation:navigation-compose:2.7.4")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.7.0-alpha03")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 7962e60..4088ffd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -40,8 +40,18 @@
/** The size when app icon is displayed in App info page. */
val appIconInfoSize = 48.dp
+ /** The vertical padding for buttons. */
+ val buttonPaddingVertical = 12.dp
+
/** The [PaddingValues] for buttons. */
- val buttonPadding = PaddingValues(horizontal = itemPaddingEnd, vertical = 12.dp)
+ val buttonPadding = PaddingValues(horizontal = itemPaddingEnd, vertical = buttonPaddingVertical)
+
+ /** The horizontal padding for dialog items. */
+ val dialogItemPaddingHorizontal = itemPaddingStart
+
+ /** The [PaddingValues] for dialog items. */
+ val dialogItemPadding =
+ PaddingValues(horizontal = dialogItemPaddingHorizontal, vertical = buttonPaddingVertical)
/** The sizes info of illustration widget. */
val illustrationMaxWidth = 412.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
index c8faef6..a9cd0e9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
@@ -16,10 +16,15 @@
package com.android.settingslib.spa.framework.theme
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+
object SettingsOpacity {
const val Full = 1f
const val Disabled = 0.38f
const val Divider = 0.2f
const val SurfaceTone = 0.14f
const val Hint = 0.9f
+
+ fun Modifier.alphaForEnabled(enabled: Boolean) = alpha(if (enabled) Full else Disabled)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
index c66e20a..8c862d4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
@@ -22,5 +22,5 @@
object SettingsShape {
val CornerMedium = RoundedCornerShape(12.dp)
- val CornerLarge = RoundedCornerShape(24.dp)
+ val CornerExtraLarge = RoundedCornerShape(28.dp)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
index 90c44b5..330755c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
@@ -30,11 +30,11 @@
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import com.android.settingslib.spa.framework.common.LocalEntryDataProvider
-import com.android.settingslib.spa.framework.theme.SettingsTheme
@Composable
-internal fun EntryHighlight(UiLayoutFn: @Composable () -> Unit) {
+internal fun EntryHighlight(content: @Composable () -> Unit) {
val entryData = LocalEntryDataProvider.current
val entryIsHighlighted = rememberSaveable { entryData.isHighlighted }
var localHighlighted by rememberSaveable { mutableStateOf(false) }
@@ -45,15 +45,16 @@
val backgroundColor by animateColorAsState(
targetValue = when {
localHighlighted -> MaterialTheme.colorScheme.surfaceVariant
- else -> SettingsTheme.colorScheme.background
+ else -> Color.Transparent
},
animationSpec = repeatable(
iterations = 3,
animation = tween(durationMillis = 500),
repeatMode = RepeatMode.Restart
- )
+ ),
+ label = "BackgroundColorAnimation",
)
Box(modifier = Modifier.background(color = backgroundColor)) {
- UiLayoutFn()
+ content()
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
index 1ad075c..979cf3b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
@@ -65,7 +65,7 @@
Row(
Modifier
.padding(SettingsDimension.buttonPadding)
- .clip(SettingsShape.CornerLarge)
+ .clip(SettingsShape.CornerExtraLarge)
.height(IntrinsicSize.Min)
) {
for ((index, actionButton) in actionButtons.withIndex()) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt
new file mode 100644
index 0000000..8b172da
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.dialog
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Card
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.window.Dialog
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsShape
+import com.android.settingslib.spa.widget.ui.SettingsTitle
+
+@Composable
+fun SettingsDialog(
+ title: String,
+ onDismissRequest: () -> Unit,
+ content: @Composable () -> Unit,
+) {
+ Dialog(onDismissRequest = onDismissRequest) {
+ Card(shape = SettingsShape.CornerExtraLarge) {
+ Column(modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingAround)) {
+ Box(modifier = Modifier.padding(SettingsDimension.dialogItemPadding)) {
+ SettingsTitle(title = title, useMediumWeight = true)
+ }
+ content()
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index 6330ddf..4d42fba 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -29,13 +29,12 @@
import androidx.compose.runtime.State
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsDimension
-import com.android.settingslib.spa.framework.theme.SettingsOpacity
+import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.ui.SettingsTitle
@@ -57,8 +56,7 @@
.padding(end = paddingEnd),
verticalAlignment = Alignment.CenterVertically,
) {
- val alphaModifier =
- Modifier.alpha(if (enabled.value) SettingsOpacity.Full else SettingsOpacity.Disabled)
+ val alphaModifier = Modifier.alphaForEnabled(enabled.value)
BaseIcon(icon, alphaModifier, paddingStart)
Titles(
title = title,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
new file mode 100644
index 0000000..19779f6
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.material3.RadioButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.IntState
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.dialog.SettingsDialog
+import com.android.settingslib.spa.widget.ui.SettingsDialogItem
+
+data class ListPreferenceOption(
+ val id: Int,
+ val text: String,
+)
+
+/**
+ * The widget model for [ListPreference] widget.
+ */
+interface ListPreferenceModel {
+ /**
+ * The title of this [ListPreference].
+ */
+ val title: String
+
+ /**
+ * The icon of this [ListPreference].
+ *
+ * Default is `null` which means no icon.
+ */
+ val icon: (@Composable () -> Unit)?
+ get() = null
+
+ /**
+ * Indicates whether this [ListPreference] is enabled.
+ *
+ * Disabled [ListPreference] will be displayed in disabled style.
+ */
+ val enabled: State<Boolean>
+ get() = stateOf(true)
+
+ val options: List<ListPreferenceOption>
+
+ val selectedId: IntState
+
+ val onIdSelected: (id: Int) -> Unit
+}
+
+@Composable
+fun ListPreference(model: ListPreferenceModel) {
+ var dialogOpened by rememberSaveable { mutableStateOf(false) }
+ if (dialogOpened) {
+ SettingsDialog(
+ title = model.title,
+ onDismissRequest = { dialogOpened = false },
+ ) {
+ Column(modifier = Modifier.selectableGroup()) {
+ for (option in model.options) {
+ Radio(option, model.selectedId, model.enabled) {
+ dialogOpened = false
+ model.onIdSelected(it)
+ }
+ }
+ }
+ }
+ }
+ Preference(model = remember(model) {
+ object : PreferenceModel {
+ override val title = model.title
+ override val summary = derivedStateOf {
+ model.options.find { it.id == model.selectedId.intValue }?.text ?: ""
+ }
+ override val icon = model.icon
+ override val enabled = model.enabled
+ override val onClick = { dialogOpened = true }.takeIf { model.options.isNotEmpty() }
+ }
+ })
+}
+
+@Composable
+private fun Radio(
+ option: ListPreferenceOption,
+ selectedId: IntState,
+ enabledState: State<Boolean>,
+ onIdSelected: (id: Int) -> Unit,
+) {
+ val selected = option.id == selectedId.intValue
+ val enabled = enabledState.value
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .selectable(
+ selected = selected,
+ enabled = enabled,
+ onClick = { onIdSelected(option.id) },
+ role = Role.RadioButton,
+ )
+ .padding(SettingsDimension.dialogItemPadding),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ RadioButton(selected = selected, onClick = null, enabled = enabled)
+ Spacer(modifier = Modifier.width(SettingsDimension.itemPaddingEnd))
+ SettingsDialogItem(text = option.text, enabled = enabled)
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
index 3e04b16..0c16c8b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
@@ -39,7 +39,7 @@
true -> MaterialTheme.colorScheme.primaryContainer
else -> MaterialTheme.colorScheme.secondaryContainer
},
- shape = SettingsShape.CornerLarge,
+ shape = SettingsShape.CornerExtraLarge,
) {
InternalSwitchPreference(
title = model.title,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
index 6ef4590..5f320f7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
@@ -18,19 +18,14 @@
import androidx.appcompat.R
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material.icons.outlined.FindInPage
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
-import androidx.compose.ui.draw.scale
-import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.LayoutDirection
import com.android.settingslib.spa.framework.compose.LocalNavController
-import androidx.compose.material.icons.automirrored.outlined.ArrowBack
/** Action that navigates back to last page. */
@Composable
@@ -55,7 +50,6 @@
Icon(
imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
contentDescription = contentDescription,
- modifier = Modifier.autoMirrored(),
)
}
}
@@ -81,10 +75,3 @@
)
}
}
-
-private fun Modifier.autoMirrored() = composed {
- when (LocalLayoutDirection.current) {
- LayoutDirection.Rtl -> scale(scaleX = -1f, scaleY = 1f)
- else -> this
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
index 57319e7..7f1acff 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -30,6 +30,7 @@
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.theme.toMediumWeight
@@ -48,6 +49,17 @@
}
@Composable
+fun SettingsDialogItem(text: String, enabled: Boolean = true) {
+ Text(
+ text = text,
+ modifier = Modifier.alphaForEnabled(enabled),
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.bodyLarge,
+ overflow = TextOverflow.Ellipsis,
+ )
+}
+
+@Composable
fun SettingsBody(
body: String,
maxLines: Int = Int.MAX_VALUE,
@@ -82,6 +94,9 @@
private fun BasePreferencePreview() {
SettingsTheme {
Column(Modifier.width(100.dp)) {
+ SettingsTitle(
+ title = "Title",
+ )
SettingsBody(
body = "Long long long long long long text",
)
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsDialogTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsDialogTest.kt
new file mode 100644
index 0000000..c7582b2
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsDialogTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.dialog
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.onDialogText
+import com.android.settingslib.spa.widget.ui.SettingsDialogItem
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsDialogTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent {
+ SettingsDialog(title = TITLE, onDismissRequest = {}) {}
+ }
+
+ composeTestRule.onDialogText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun text_displayed() {
+ composeTestRule.setContent {
+ SettingsDialog(title = "", onDismissRequest = {}) {
+ SettingsDialogItem(text = TEXT)
+ }
+ }
+
+ composeTestRule.onDialogText(TEXT).assertIsDisplayed()
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ const val TEXT = "Text"
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
new file mode 100644
index 0000000..997a023
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.testutils.onDialogText
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ListPreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent {
+ ListPreference(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val options = emptyList<ListPreferenceOption>()
+ override val selectedId = mutableIntStateOf(0)
+ override val onIdSelected: (Int) -> Unit = {}
+ }
+ })
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun summary_showSelectedText() {
+ composeTestRule.setContent {
+ ListPreference(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val options = listOf(ListPreferenceOption(id = 1, text = "A"))
+ override val selectedId = mutableIntStateOf(1)
+ override val onIdSelected: (Int) -> Unit = {}
+ }
+ })
+ }
+
+ composeTestRule.onNodeWithText("A").assertIsDisplayed()
+ }
+
+ @Test
+ fun click_optionsIsEmpty_notShowDialog() {
+ composeTestRule.setContent {
+ ListPreference(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val options = emptyList<ListPreferenceOption>()
+ override val selectedId = mutableIntStateOf(0)
+ override val onIdSelected: (Int) -> Unit = {}
+ }
+ })
+ }
+
+ composeTestRule.onNodeWithText(TITLE).performClick()
+
+ composeTestRule.onDialogText(TITLE).assertDoesNotExist()
+ }
+
+ @Test
+ fun click_notEnabled_notShowDialog() {
+ composeTestRule.setContent {
+ ListPreference(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val enabled = stateOf(false)
+ override val options = listOf(ListPreferenceOption(id = 1, text = "A"))
+ override val selectedId = mutableIntStateOf(1)
+ override val onIdSelected: (Int) -> Unit = {}
+ }
+ })
+ }
+
+ composeTestRule.onNodeWithText(TITLE).performClick()
+
+ composeTestRule.onDialogText(TITLE).assertDoesNotExist()
+ }
+
+ @Test
+ fun click_optionsNotEmpty_showDialog() {
+ composeTestRule.setContent {
+ ListPreference(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val options = listOf(ListPreferenceOption(id = 1, text = "A"))
+ override val selectedId = mutableIntStateOf(1)
+ override val onIdSelected: (Int) -> Unit = {}
+ }
+ })
+ }
+
+ composeTestRule.onNodeWithText(TITLE).performClick()
+
+ composeTestRule.onDialogText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun select() {
+ val selectedId = mutableIntStateOf(1)
+ composeTestRule.setContent {
+ ListPreference(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val options = listOf(
+ ListPreferenceOption(id = 1, text = "A"),
+ ListPreferenceOption(id = 2, text = "B"),
+ )
+ override val selectedId = selectedId
+ override val onIdSelected = { id: Int -> selectedId.intValue = id }
+ }
+ })
+ }
+
+ composeTestRule.onNodeWithText(TITLE).performClick()
+ composeTestRule.onDialogText("B").performClick()
+
+ composeTestRule.onNodeWithText("B").assertIsDisplayed()
+ }
+
+ @Test
+ fun select_dialogOpenThenDisable_itemAlsoDisabled() {
+ val selectedId = mutableIntStateOf(1)
+ val enabledState = mutableStateOf(true)
+ composeTestRule.setContent {
+ ListPreference(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val enabled = enabledState
+ override val options = listOf(
+ ListPreferenceOption(id = 1, text = "A"),
+ ListPreferenceOption(id = 2, text = "B"),
+ )
+ override val selectedId = selectedId
+ override val onIdSelected = { id: Int -> selectedId.intValue = id }
+ }
+ })
+ }
+
+ composeTestRule.onNodeWithText(TITLE).performClick()
+ enabledState.value = false
+
+ composeTestRule.onDialogText("B").assertIsDisplayed().assertIsNotEnabled()
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ }
+}
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle.kts b/packages/SettingsLib/Spa/testutils/build.gradle.kts
index 50243dc..cce8235 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle.kts
+++ b/packages/SettingsLib/Spa/testutils/build.gradle.kts
@@ -41,7 +41,7 @@
api("androidx.arch.core:core-testing:2.2.0-alpha01")
api("androidx.compose.ui:ui-test-junit4:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-runtime-testing")
- api("org.mockito.kotlin:mockito-kotlin:5.1.0")
+ api("org.mockito.kotlin:mockito-kotlin:2.2.11")
api("org.mockito:mockito-core") {
version {
strictly("2.28.2")
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index f54de15..09cb98e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -22,6 +22,8 @@
import android.os.UserManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settingslib.RestrictedLockUtils
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
@@ -32,15 +34,15 @@
import com.android.settingslib.widget.restricted.R
data class Restrictions(
- val userId: Int,
+ val userId: Int = UserHandle.myUserId(),
val keys: List<String>,
)
sealed interface RestrictedMode
-object NoRestricted : RestrictedMode
+data object NoRestricted : RestrictedMode
-object BaseUserRestricted : RestrictedMode
+data object BaseUserRestricted : RestrictedMode
interface BlockedByAdmin : RestrictedMode {
fun getSummary(checked: Boolean?): String
@@ -79,6 +81,17 @@
typealias RestrictionsProviderFactory = (Context, Restrictions) -> RestrictionsProvider
+@Composable
+internal fun RestrictionsProviderFactory.rememberRestrictedMode(
+ restrictions: Restrictions,
+): State<RestrictedMode?> {
+ val context = LocalContext.current
+ val restrictionsProvider = remember(restrictions) {
+ this(context, restrictions)
+ }
+ return restrictionsProvider.restrictedModeState()
+}
+
internal class RestrictionsProviderImpl(
private val context: Context,
private val restrictions: Restrictions,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 1fa854a..17e9708 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -45,6 +45,7 @@
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
import kotlinx.coroutines.flow.Flow
@@ -149,14 +150,13 @@
@Composable
fun getSummary(record: T): State<String> {
- val restrictionsProvider = remember(record.app.userId) {
- val restrictions = Restrictions(
+ val restrictions = remember(record.app.userId) {
+ Restrictions(
userId = record.app.userId,
keys = listModel.switchRestrictionKeys,
)
- restrictionsProviderFactory(context, restrictions)
}
- val restrictedMode = restrictionsProvider.restrictedModeState()
+ val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions)
val allowed = listModel.isAllowed(record)
return remember {
derivedStateOf {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
new file mode 100644
index 0000000..50490c0
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
+
+@Composable
+fun RestrictedPreference(
+ model: PreferenceModel,
+ restrictions: Restrictions,
+) {
+ RestrictedPreference(model, restrictions, ::RestrictionsProviderImpl)
+}
+
+@VisibleForTesting
+@Composable
+internal fun RestrictedPreference(
+ model: PreferenceModel,
+ restrictions: Restrictions,
+ restrictionsProviderFactory: RestrictionsProviderFactory,
+) {
+ if (restrictions.keys.isEmpty()) {
+ Preference(model)
+ return
+ }
+ val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
+ val restrictedSwitchModel = remember(restrictedMode) {
+ RestrictedPreferenceModel(model, restrictedMode)
+ }
+ restrictedSwitchModel.RestrictionWrapper {
+ Preference(restrictedSwitchModel)
+ }
+}
+
+private class RestrictedPreferenceModel(
+ model: PreferenceModel,
+ private val restrictedMode: RestrictedMode?,
+) : PreferenceModel {
+ override val title = model.title
+ override val summary = model.summary
+ override val icon = model.icon
+
+ override val enabled = when (restrictedMode) {
+ NoRestricted -> model.enabled
+ else -> stateOf(false)
+ }
+
+ override val onClick = when (restrictedMode) {
+ NoRestricted -> model.onClick
+ // Need to passthrough onClick for clickable semantics, although since enabled is false so
+ // this will not be called.
+ BaseUserRestricted -> model.onClick
+ else -> null
+ }
+
+ @Composable
+ fun RestrictionWrapper(content: @Composable () -> Unit) {
+ if (restrictedMode !is BlockedByAdmin) {
+ content()
+ return
+ }
+ Box(
+ Modifier
+ .clickable(
+ role = Role.Button,
+ onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() },
+ )
+ ) { content() }
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index e77dcd4..2129403 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.spaprivileged.template.preference
import android.content.Context
+import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
@@ -40,22 +41,29 @@
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
@Composable
fun RestrictedSwitchPreference(
model: SwitchPreferenceModel,
restrictions: Restrictions,
- restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl,
+) {
+ RestrictedSwitchPreference(model, restrictions, ::RestrictionsProviderImpl)
+}
+
+@VisibleForTesting
+@Composable
+internal fun RestrictedSwitchPreference(
+ model: SwitchPreferenceModel,
+ restrictions: Restrictions,
+ restrictionsProviderFactory: RestrictionsProviderFactory,
) {
if (restrictions.keys.isEmpty()) {
SwitchPreference(model)
return
}
val context = LocalContext.current
- val restrictionsProvider = remember(restrictions) {
- restrictionsProviderFactory(context, restrictions)
- }
- val restrictedMode = restrictionsProvider.restrictedModeState().value
+ val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
val restrictedSwitchModel = remember(restrictedMode) {
RestrictedSwitchPreferenceModel(context, model, restrictedMode)
}
@@ -112,8 +120,8 @@
override val onCheckedChange = when (restrictedMode) {
null -> null
is NoRestricted -> model.onCheckedChange
- // Need to pass a non null onCheckedChange to enable semantics ToggleableState, although
- // since changeable is false this will not be called.
+ // Need to passthrough onCheckedChange for toggleable semantics, although since changeable
+ // is false so this will not be called.
is BaseUserRestricted -> model.onCheckedChange
// Pass null since semantics ToggleableState is provided in RestrictionWrapper.
is BlockedByAdmin -> null
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
index 86b6f02..f9abefc 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
@@ -16,15 +16,15 @@
package com.android.settingslib.spaprivileged.template.scaffold
+import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope
import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
@Composable
fun MoreOptionsScope.RestrictedMenuItem(
@@ -35,6 +35,7 @@
RestrictedMenuItemImpl(text, restrictions, onClick, ::RestrictionsProviderImpl)
}
+@VisibleForTesting
@Composable
internal fun MoreOptionsScope.RestrictedMenuItemImpl(
text: String,
@@ -42,12 +43,8 @@
onClick: () -> Unit,
restrictionsProviderFactory: RestrictionsProviderFactory,
) {
- val context = LocalContext.current
- val restrictionsProvider = remember(restrictions) {
- restrictionsProviderFactory(context, restrictions)
- }
- val restrictedMode = restrictionsProvider.restrictedModeState().value
- MenuItem(text = text, enabled = restrictedMode !is BaseUserRestricted) {
+ val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
+ MenuItem(text = text, enabled = restrictedMode !== BaseUserRestricted) {
when (restrictedMode) {
is BlockedByAdmin -> restrictedMode.sendShowAdminSupportDetailsIntent()
else -> onClick()
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt
new file mode 100644
index 0000000..eadf0ca
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RestrictedPreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+
+ private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+ private var clicked = false
+
+ private val preferenceModel = object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = { clicked = true }
+ }
+
+ @Test
+ fun whenRestrictionsKeysIsEmpty_enabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ }
+
+ @Test
+ fun whenRestrictionsKeysIsEmpty_clickable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(clicked).isTrue()
+ }
+
+ @Test
+ fun whenNoRestricted_enabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ }
+
+ @Test
+ fun whenNoRestricted_clickable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(clicked).isTrue()
+ }
+
+ @Test
+ fun whenBaseUserRestricted_disabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsNotEnabled()
+ }
+
+ @Test
+ fun whenBaseUserRestricted_notClickable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(clicked).isFalse()
+ }
+
+ @Test
+ fun whenBlockedByAdmin_widgetInEnableStateToAllowClick() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ }
+
+ @Test
+ fun whenBlockedByAdmin_click() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+ }
+
+ private fun setContent(restrictions: Restrictions) {
+ composeTestRule.setContent {
+ RestrictedPreference(preferenceModel, restrictions) { _, _ ->
+ fakeRestrictionsProvider
+ }
+ }
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ const val USER_ID = 0
+ const val RESTRICTION_KEY = "restriction_key"
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 1bb00b3..ce0772f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -54,6 +54,7 @@
import com.android.internal.util.UserIcons;
import com.android.launcher3.icons.BaseIconFactory.IconOptions;
import com.android.launcher3.icons.IconFactory;
+import com.android.launcher3.util.UserIconInfo;
import com.android.settingslib.drawable.UserIconDrawable;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.settingslib.utils.BuildCompatUtils;
@@ -597,15 +598,25 @@
/** Get the corresponding adaptive icon drawable. */
public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) {
- UserManager um = context.getSystemService(UserManager.class);
- boolean isClone = um.getProfiles(user.getIdentifier()).stream()
- .anyMatch(profile ->
- profile.isCloneProfile() && profile.id == user.getIdentifier());
+ int userType = UserIconInfo.TYPE_MAIN;
+ try {
+ UserInfo ui = context.getSystemService(UserManager.class).getUserInfo(
+ user.getIdentifier());
+ if (ui != null) {
+ if (ui.isCloneProfile()) {
+ userType = UserIconInfo.TYPE_CLONED;
+ } else if (ui.isManagedProfile()) {
+ userType = UserIconInfo.TYPE_WORK;
+ }
+ }
+ } catch (Exception e) {
+ // Ignore
+ }
try (IconFactory iconFactory = IconFactory.obtain(context)) {
return iconFactory
.createBadgedIconBitmap(
icon,
- new IconOptions().setUser(user).setIsCloneProfile(isClone))
+ new IconOptions().setUser(new UserIconInfo(user, userType)))
.newIcon(context);
}
}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java
new file mode 100644
index 0000000..a9fd380
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.testutils.shadow;
+
+import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.hardware.display.ColorDisplayManager;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(ColorDisplayManager.class)
+public class ShadowColorDisplayManager extends org.robolectric.shadows.ShadowColorDisplayManager {
+
+ private boolean mIsReduceBrightColorsActivated;
+
+ @Implementation
+ @SystemApi
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ public boolean setReduceBrightColorsActivated(boolean activated) {
+ mIsReduceBrightColorsActivated = activated;
+ return true;
+ }
+
+ @Implementation
+ @SystemApi
+ public boolean isReduceBrightColorsActivated() {
+ return mIsReduceBrightColorsActivated;
+ }
+
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index fe39c4f..59c3cd3 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -84,6 +84,7 @@
Settings.System.RING_VIBRATION_INTENSITY,
Settings.System.HAPTIC_FEEDBACK_INTENSITY,
Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY,
+ Settings.System.KEYBOARD_VIBRATION_ENABLED,
Settings.System.HAPTIC_FEEDBACK_ENABLED,
Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT, // must precede DISPLAY_COLOR_MODE
Settings.System.DISPLAY_COLOR_MODE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index eba74ab..572303a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -138,6 +138,7 @@
VALIDATORS.put(System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
VALIDATORS.put(System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
VALIDATORS.put(System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
+ VALIDATORS.put(System.KEYBOARD_VIBRATION_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.HAPTIC_FEEDBACK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.RINGTONE, URI_VALIDATOR);
VALIDATORS.put(System.NOTIFICATION_SOUND, URI_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 46cd725..4e2fad0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3243,7 +3243,7 @@
}
if (success && criticalSettings != null && criticalSettings.contains(name)) {
- settingsState.persistSyncLocked();
+ settingsState.persistSettingsLocked();
}
if (forceNotify || success) {
@@ -3294,7 +3294,7 @@
}
if (success && criticalSettings != null && criticalSettings.contains(name)) {
- settingsState.persistSyncLocked();
+ settingsState.persistSettingsLocked();
}
if (forceNotify || success) {
@@ -3319,7 +3319,7 @@
}
if (success && criticalSettings != null && criticalSettings.contains(name)) {
- settingsState.persistSyncLocked();
+ settingsState.persistSettingsLocked();
}
if (forceNotify || success) {
@@ -3385,7 +3385,7 @@
}
}
if (someSettingChanged) {
- settingsState.persistSyncLocked();
+ settingsState.persistSettingsLocked();
success = true;
}
}
@@ -3407,7 +3407,7 @@
}
}
if (someSettingChanged) {
- settingsState.persistSyncLocked();
+ settingsState.persistSettingsLocked();
success = true;
}
}
@@ -3435,7 +3435,7 @@
}
}
if (someSettingChanged) {
- settingsState.persistSyncLocked();
+ settingsState.persistSettingsLocked();
success = true;
}
}
@@ -3460,7 +3460,7 @@
logSettingChanged(userId, name, type, CHANGE_TYPE_DELETE);
}
if (someSettingChanged) {
- settingsState.persistSyncLocked();
+ settingsState.persistSettingsLocked();
success = true;
}
}
@@ -3559,7 +3559,7 @@
ensureSettingsStateLocked(systemKey);
SettingsState systemSettings = mSettingsStates.get(systemKey);
migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM);
- systemSettings.persistSyncLocked();
+ systemSettings.persistSettingsLocked();
// Move over the secure settings.
// Do this after System settings, since this is the first thing we check when deciding
@@ -3569,7 +3569,7 @@
SettingsState secureSettings = mSettingsStates.get(secureKey);
migrateLegacySettingsLocked(secureSettings, database, TABLE_SECURE);
ensureSecureSettingAndroidIdSetLocked(secureSettings);
- secureSettings.persistSyncLocked();
+ secureSettings.persistSettingsLocked();
// Move over the global settings if owner.
// Do this last, since this is the first thing we check when deciding
@@ -3585,7 +3585,7 @@
mSettingsCreationBuildId, null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
}
- globalSettings.persistSyncLocked();
+ globalSettings.persistSettingsLocked();
}
// Drop the database as now all is moved and persisted.
@@ -4404,16 +4404,16 @@
if (userId == UserHandle.USER_SYSTEM) {
SettingsState globalSettings = getGlobalSettingsLocked();
ensureLegacyDefaultValueAndSystemSetUpdatedLocked(globalSettings, userId);
- globalSettings.persistSyncLocked();
+ globalSettings.persistSettingsLocked();
}
SettingsState secureSettings = getSecureSettingsLocked(mUserId);
ensureLegacyDefaultValueAndSystemSetUpdatedLocked(secureSettings, userId);
- secureSettings.persistSyncLocked();
+ secureSettings.persistSettingsLocked();
SettingsState systemSettings = getSystemSettingsLocked(mUserId);
ensureLegacyDefaultValueAndSystemSetUpdatedLocked(systemSettings, userId);
- systemSettings.persistSyncLocked();
+ systemSettings.persistSettingsLocked();
currentVersion = 146;
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index e9533e5..7cec99d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -72,6 +72,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
/**
* This class contains the state for one type of settings. It is responsible
@@ -589,9 +590,10 @@
}
// The settings provider must hold its lock when calling here.
- public void persistSyncLocked() {
+ public void persistSettingsLocked() {
mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
- doWriteState();
+ // schedule a write operation right away
+ mHandler.obtainMessage(MyHandler.MSG_PERSIST_SETTINGS).sendToTarget();
}
// The settings provider must hold its lock when calling here.
@@ -1725,4 +1727,20 @@
return mPackageToMemoryUsage.getOrDefault(packageName, 0);
}
}
+
+ /**
+ * Allow tests to wait for the handler to finish handling all the remaining messages
+ */
+ @VisibleForTesting
+ public void waitForHandler() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ synchronized (mLock) {
+ mHandler.post(latch::countDown);
+ }
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ // ignored
+ }
+ }
}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index df4d2a1..02a7bc1 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -76,6 +76,14 @@
mSettingsFile.delete();
}
+ @Override
+ protected void tearDown() throws Exception {
+ if (mSettingsFile != null) {
+ mSettingsFile.delete();
+ }
+ super.tearDown();
+ }
+
public void testIsBinary() {
assertFalse(SettingsState.isBinary(" abc 日本語"));
@@ -149,11 +157,10 @@
* Make sure settings can be written to a file and also can be read.
*/
public void testReadWrite() {
- final File file = new File(getContext().getCacheDir(), "setting.xml");
- file.delete();
final Object lock = new Object();
- final SettingsState ssWriter = new SettingsState(getContext(), lock, file, 1,
+ assertFalse(mSettingsFile.exists());
+ final SettingsState ssWriter = new SettingsState(getContext(), lock, mSettingsFile, 1,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
ssWriter.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING);
@@ -162,11 +169,13 @@
ssWriter.insertSettingLocked("k3", null, null, false, "p2");
ssWriter.insertSettingLocked("k4", CRAZY_STRING, null, false, "p3");
synchronized (lock) {
- ssWriter.persistSyncLocked();
+ ssWriter.persistSettingsLocked();
}
-
- final SettingsState ssReader = new SettingsState(getContext(), lock, file, 1,
+ ssWriter.waitForHandler();
+ assertTrue(mSettingsFile.exists());
+ final SettingsState ssReader = new SettingsState(getContext(), lock, mSettingsFile, 1,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
synchronized (lock) {
assertEquals("\u0000", ssReader.getSettingLocked("k1").getValue());
assertEquals("abc", ssReader.getSettingLocked("k2").getValue());
@@ -179,10 +188,8 @@
* In version 120, value "null" meant {code NULL}.
*/
public void testUpgrade() throws Exception {
- final File file = new File(getContext().getCacheDir(), "setting.xml");
- file.delete();
final Object lock = new Object();
- final PrintStream os = new PrintStream(new FileOutputStream(file));
+ final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile));
os.print(
"<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>" +
"<settings version=\"120\">" +
@@ -192,7 +199,7 @@
"</settings>");
os.close();
- final SettingsState ss = new SettingsState(getContext(), lock, file, 1,
+ final SettingsState ss = new SettingsState(getContext(), lock, mSettingsFile, 1,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
synchronized (lock) {
SettingsState.Setting s;
@@ -213,7 +220,8 @@
public void testInitializeSetting_preserveFlagNotSet() {
SettingsState settingsWriter = getSettingStateObject();
settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
- settingsWriter.persistSyncLocked();
+ settingsWriter.persistSettingsLocked();
+ settingsWriter.waitForHandler();
SettingsState settingsReader = getSettingStateObject();
assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
@@ -223,7 +231,8 @@
SettingsState settingsWriter = getSettingStateObject();
settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, TEST_PACKAGE);
- settingsWriter.persistSyncLocked();
+ settingsWriter.persistSettingsLocked();
+ settingsWriter.waitForHandler();
SettingsState settingsReader = getSettingStateObject();
assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
@@ -234,7 +243,8 @@
settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, false, TEST_PACKAGE,
/* overrideableByRestore */ true);
- settingsWriter.persistSyncLocked();
+ settingsWriter.persistSettingsLocked();
+ settingsWriter.waitForHandler();
SettingsState settingsReader = getSettingStateObject();
assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
@@ -250,7 +260,8 @@
// already been set to true.
settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, false, TEST_PACKAGE,
/* overrideableByRestore */ true);
- settingsWriter.persistSyncLocked();
+ settingsWriter.persistSettingsLocked();
+ settingsWriter.waitForHandler();
SettingsState settingsReader = getSettingStateObject();
assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
@@ -481,8 +492,11 @@
settingsState.insertSettingLocked(
FLAG_NAME_1_STAGED, VALUE1, null, false, TEST_PACKAGE);
settingsState.insertSettingLocked(FLAG_NAME_2, VALUE2, null, false, TEST_PACKAGE);
- settingsState.persistSyncLocked();
+ settingsState.persistSettingsLocked();
+ }
+ settingsState.waitForHandler();
+ synchronized (lock) {
assertEquals(VALUE1, settingsState.getSettingLocked(FLAG_NAME_1_STAGED).getValue());
assertEquals(VALUE2, settingsState.getSettingLocked(FLAG_NAME_2).getValue());
}
@@ -522,7 +536,10 @@
synchronized (lock) {
settingsState.insertSettingLocked(INVALID_STAGED_FLAG_1,
VALUE2, null, false, TEST_PACKAGE);
- settingsState.persistSyncLocked();
+ settingsState.persistSettingsLocked();
+ }
+ settingsState.waitForHandler();
+ synchronized (lock) {
assertEquals(VALUE2, settingsState.getSettingLocked(INVALID_STAGED_FLAG_1).getValue());
}
diff --git a/packages/SoundPicker/Android.bp b/packages/SoundPicker/Android.bp
index 235e672..2c89d6d 100644
--- a/packages/SoundPicker/Android.bp
+++ b/packages/SoundPicker/Android.bp
@@ -7,40 +7,22 @@
default_applicable_licenses: ["frameworks_base_license"],
}
-android_library {
- name: "SoundPickerLib",
- srcs: [
- "src/**/*.java",
- ],
- resource_dirs: [
- "res",
- ],
- static_libs: [
- "androidx.appcompat_appcompat",
- "hilt_android",
- "guava",
- "androidx.recyclerview_recyclerview",
- "androidx-constraintlayout_constraintlayout",
- "androidx.viewpager2_viewpager2",
- "com.google.android.material_material",
- ],
-}
-
android_app {
name: "SoundPicker",
defaults: ["platform_app_defaults"],
manifest: "AndroidManifest.xml",
- static_libs: ["SoundPickerLib"],
+
+ static_libs: [
+ "androidx.appcompat_appcompat",
+ ],
+ resource_dirs: [
+ "res",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ],
+
platform_apis: true,
certificate: "media",
privileged: true,
-
- optimize: {
- enabled: true,
- optimize: true,
- shrink: true,
- shrink_resources: true,
- obfuscate: false,
- proguard_compatibility: false,
- },
}
diff --git a/packages/SoundPicker/AndroidManifest.xml b/packages/SoundPicker/AndroidManifest.xml
index 934b003..44295a5 100644
--- a/packages/SoundPicker/AndroidManifest.xml
+++ b/packages/SoundPicker/AndroidManifest.xml
@@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.soundpicker"
- android:sharedUserId="android.media">
+ package="com.android.soundpicker"
+ android:sharedUserId="android.media">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@@ -9,16 +9,12 @@
<uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
- <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-
<application
- android:name=".RingtonePickerApplication"
- android:allowBackup="false"
- android:label="@string/app_label"
- android:theme="@style/Theme.AppCompat"
- android:supportsRtl="true">
+ android:allowBackup="false"
+ android:label="@string/app_label"
+ android:supportsRtl="true">
<receiver android:name="RingtoneReceiver"
- android:exported="true">
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
</intent-filter>
@@ -27,17 +23,14 @@
<service android:name="RingtoneOverlayService" />
<activity android:name="RingtonePickerActivity"
- android:theme="@style/Theme.AppCompat.Dialog"
- android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
- android:excludeFromRecents="true"
- android:exported="true">
+ android:theme="@style/PickerDialogTheme"
+ android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
+ android:excludeFromRecents="true"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.RINGTONE_PICKER" />
<category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.RINGTONE_PICKER_SOUND" />
- <category android:name="android.intent.category.RINGTONE_PICKER_VIBRATION" />
- <category android:name="android.intent.category.RINGTONE_PICKER_RINGTONE" />
</intent-filter>
</activity>
</application>
-</manifest>
+</manifest>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/add_new_sound_item.xml b/packages/SoundPicker/res/layout/add_new_sound_item.xml
index 024b97e..57b70d7 100644
--- a/packages/SoundPicker/res/layout/add_new_sound_item.xml
+++ b/packages/SoundPicker/res/layout/add_new_sound_item.xml
@@ -19,9 +19,7 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
- android:background="?android:attr/selectableItemBackground"
- android:focusable="true"
- android:clickable="true">
+ android:background="?android:attr/selectableItemBackground">
<ImageView
android:layout_width="24dp"
@@ -31,19 +29,19 @@
android:scaleType="centerCrop"
android:layout_marginRight="24dp"
android:layout_marginLeft="24dp"
- android:src="@drawable/ic_add"/>
+ android:src="@drawable/ic_add" />
- <TextView
- android:id="@+id/add_new_sound_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
- android:text="@null"
- android:textColor="?android:attr/colorAccent"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:maxLines="3"
- android:gravity="center_vertical"
- android:paddingEnd="?android:attr/dialogPreferredPadding"
- android:drawablePadding="20dp"
- android:ellipsize="marquee"/>
+ <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/add_new_sound_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:text="@null"
+ android:textColor="?android:attr/colorAccent"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:maxLines="3"
+ android:gravity="center_vertical"
+ android:paddingEnd="?android:attr/dialogPreferredPadding"
+ android:drawablePadding="20dp"
+ android:ellipsize="marquee" />
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/radio_with_work_badge.xml b/packages/SoundPicker/res/layout/radio_with_work_badge.xml
index 36ac93e..2e44b6f 100644
--- a/packages/SoundPicker/res/layout/radio_with_work_badge.xml
+++ b/packages/SoundPicker/res/layout/radio_with_work_badge.xml
@@ -14,14 +14,12 @@
limitations under the License.
-->
-<com.android.soundpicker.CheckedListItem
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:background="?android:attr/selectableItemBackground"
- android:focusable="true"
- android:clickable="true">
+<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ >
<CheckedTextView
android:id="@+id/checked_text_view"
@@ -37,7 +35,7 @@
android:drawablePadding="20dp"
android:ellipsize="marquee"
android:layout_toLeftOf="@+id/work_icon"
- android:maxLines="3"/>
+ android:maxLines="3" />
<ImageView
android:id="@id/work_icon"
@@ -46,5 +44,5 @@
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:scaleType="centerCrop"
- android:layout_marginRight="20dp"/>
-</com.android.soundpicker.CheckedListItem>
+ android:layout_marginRight="20dp" />
+</com.android.soundpicker.CheckedListItem>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/values/strings.xml b/packages/SoundPicker/res/values/strings.xml
index ab7b95a..04a2c2b 100644
--- a/packages/SoundPicker/res/values/strings.xml
+++ b/packages/SoundPicker/res/values/strings.xml
@@ -40,8 +40,4 @@
<!-- Text for the name of the app. [CHAR LIMIT=12] -->
<string name="app_label">Sounds</string>
-
- <string name="empty_list">The list is empty</string>
- <string name="sound_page_title">Sound</string>
- <string name="vibration_page_title">Vibration</string>
</resources>
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
index 90a14f9..ea46c0c 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -16,19 +16,43 @@
package com.android.soundpicker;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.media.AudioAttributes;
+import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.MediaStore;
+import android.provider.Settings;
import android.util.Log;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.CursorAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentTransaction;
-import androidx.lifecycle.ViewModelProvider;
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
-import dagger.hilt.android.AndroidEntryPoint;
+import java.io.IOException;
+import java.util.regex.Pattern;
/**
* The {@link RingtonePickerActivity} allows the user to choose one from all of the
@@ -36,183 +60,727 @@
*
* @see RingtoneManager#ACTION_RINGTONE_PICKER
*/
-@AndroidEntryPoint(AppCompatActivity.class)
-public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity {
+public final class RingtonePickerActivity extends AlertActivity implements
+ AdapterView.OnItemSelectedListener, Runnable, DialogInterface.OnClickListener,
+ AlertController.AlertParams.OnPrepareListViewListener {
+
+ private static final int POS_UNKNOWN = -1;
private static final String TAG = "RingtonePickerActivity";
- // TODO: Use the extra keys from RingtoneManager once they're added.
- private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY";
- private static final String EXTRA_VIBRATION_SHOW_DEFAULT = "EXTRA_VIBRATION_SHOW_DEFAULT";
- private static final String EXTRA_VIBRATION_DEFAULT_URI = "EXTRA_VIBRATION_DEFAULT_URI";
- private static final String EXTRA_VIBRATION_SHOW_SILENT = "EXTRA_VIBRATION_SHOW_SILENT";
- private static final String EXTRA_VIBRATION_EXISTING_URI = "EXTRA_VIBRATION_EXISTING_URI";
- private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false;
- private RingtonePickerViewModel mRingtonePickerViewModel;
+ private static final int DELAY_MS_SELECTION_PLAYED = 300;
+
+ private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE;
+
+ private static final String SAVE_CLICKED_POS = "clicked_pos";
+
+ private static final String SOUND_NAME_RES_PREFIX = "sound_name_";
+
+ private static final int ADD_FILE_REQUEST_CODE = 300;
+
+ private RingtoneManager mRingtoneManager;
+ private int mType;
+
+ private Cursor mCursor;
+ private Handler mHandler;
+ private BadgedRingtoneAdapter mAdapter;
+
+ /** The position in the list of the 'Silent' item. */
+ private int mSilentPos = POS_UNKNOWN;
+
+ /** The position in the list of the 'Default' item. */
+ private int mDefaultRingtonePos = POS_UNKNOWN;
+
+ /** The position in the list of the ringtone to sample. */
+ private int mSampleRingtonePos = POS_UNKNOWN;
+
+ /** Whether this list has the 'Silent' item. */
+ private boolean mHasSilentItem;
+
+ /** The Uri to place a checkmark next to. */
+ private Uri mExistingUri;
+
+ /** The number of static items in the list. */
+ private int mStaticItemCount;
+
+ /** Whether this list has the 'Default' item. */
+ private boolean mHasDefaultItem;
+
+ /** The Uri to play when the 'Default' item is clicked. */
+ private Uri mUriForDefaultItem;
+
+ /** Id of the user to which the ringtone picker should list the ringtones */
+ private int mPickerUserId;
+
+ /** Context of the user specified by mPickerUserId */
+ private Context mTargetContext;
+
+ /**
+ * A Ringtone for the default ringtone. In most cases, the RingtoneManager
+ * will stop the previous ringtone. However, the RingtoneManager doesn't
+ * manage the default ringtone for us, so we should stop this one manually.
+ */
+ private Ringtone mDefaultRingtone;
+
+ /**
+ * The ringtone that's currently playing, unless the currently playing one is the default
+ * ringtone.
+ */
+ private Ringtone mCurrentRingtone;
+
+ /**
+ * Stable ID for the ringtone that is currently checked (may be -1 if no ringtone is checked).
+ */
+ private long mCheckedItemId = -1;
+
private int mAttributesFlags;
+ private boolean mShowOkCancelButtons;
+
+ /**
+ * Keep the currently playing ringtone around when changing orientation, so that it
+ * can be stopped later, after the activity is recreated.
+ */
+ private static Ringtone sPlayingRingtone;
+
+ private DialogInterface.OnClickListener mRingtoneClickListener =
+ new DialogInterface.OnClickListener() {
+
+ /*
+ * On item clicked
+ */
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == mCursor.getCount() + mStaticItemCount) {
+ // The "Add new ringtone" item was clicked. Start a file picker intent to select
+ // only audio files (MIME type "audio/*")
+ final Intent chooseFile = getMediaFilePickerIntent();
+ startActivityForResult(chooseFile, ADD_FILE_REQUEST_CODE);
+ return;
+ }
+
+ // Save the position of most recently clicked item
+ setCheckedItem(which);
+
+ // In the buttonless (watch-only) version, preemptively set our result since we won't
+ // have another chance to do so before the activity closes.
+ if (!mShowOkCancelButtons) {
+ setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+ }
+
+ // Play clip
+ playRingtone(which, 0);
+ }
+
+ };
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_ringtone_picker);
- mRingtonePickerViewModel = new ViewModelProvider(this).get(RingtonePickerViewModel.class);
+ mHandler = new Handler();
Intent intent = getIntent();
- /**
- * Id of the user to which the ringtone picker should list the ringtones
- */
- int pickerUserId = UserHandle.myUserId();
+ mPickerUserId = UserHandle.myUserId();
+ mTargetContext = this;
// Get the types of ringtones to show
- int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,
- RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN);
+ mType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, -1);
+ initRingtoneManager();
+ /*
+ * Get whether to show the 'Default' item, and the URI to play when the
+ * default is clicked
+ */
+ mHasDefaultItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+ mUriForDefaultItem = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
+ if (mUriForDefaultItem == null) {
+ if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+ mUriForDefaultItem = Settings.System.DEFAULT_NOTIFICATION_URI;
+ } else if (mType == RingtoneManager.TYPE_ALARM) {
+ mUriForDefaultItem = Settings.System.DEFAULT_ALARM_ALERT_URI;
+ } else if (mType == RingtoneManager.TYPE_RINGTONE) {
+ mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
+ } else {
+ // or leave it null for silence.
+ mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
+ }
+ }
+
+ // Get whether to show the 'Silent' item
+ mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
// AudioAttributes flags
mAttributesFlags |= intent.getIntExtra(
RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
0 /*defaultValue == no flags*/);
- boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
-
- String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
- if (title == null) {
- title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType));
- }
- String ringtonePickerCategory = intent.getStringExtra(EXTRA_RINGTONE_PICKER_CATEGORY);
- RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType(
- ringtonePickerCategory);
-
- RingtoneListHandler.Config soundListConfig = getSoundListConfig(pickerType, intent,
- ringtoneType);
- RingtoneListHandler.Config vibrationListConfig = getVibrationListConfig(pickerType, intent);
-
- RingtonePickerViewModel.Config pickerConfig =
- new RingtonePickerViewModel.Config(title, pickerUserId, ringtoneType,
- showOkCancelButtons, mAttributesFlags, pickerType);
-
- mRingtonePickerViewModel.init(pickerConfig, soundListConfig, vibrationListConfig);
-
- if (savedInstanceState == null) {
- TabbedDialogFragment dialogFragment = new TabbedDialogFragment();
-
- FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
- Fragment prev = getSupportFragmentManager().findFragmentByTag(TabbedDialogFragment.TAG);
- if (prev != null) {
- ft.remove(prev);
- }
- ft.addToBackStack(null);
- dialogFragment.show(ft, TabbedDialogFragment.TAG);
- }
+ mShowOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
// The volume keys will control the stream that we are choosing a ringtone for
- setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType());
- }
+ setVolumeControlStream(mRingtoneManager.inferStreamType());
- private RingtoneListHandler.Config getSoundListConfig(
- RingtonePickerViewModel.PickerType pickerType, Intent intent, int ringtoneType) {
- if (pickerType != RingtonePickerViewModel.PickerType.SOUND_PICKER
- && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
- // This ringtone picker does not require a sound picker.
- return null;
- }
-
- // Get whether to show the 'Default' sound item, and the URI to play when it's clicked
- boolean hasDefaultSoundItem =
- intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
-
- // The Uri to play when the 'Default' sound item is clicked.
- Uri uriForDefaultSoundItem =
- intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
- if (uriForDefaultSoundItem == null) {
- uriForDefaultSoundItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType);
- }
-
- // Get whether this list has the 'Silent' sound item.
- boolean hasSilentSoundItem =
- intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
-
- // AudioAttributes flags
- mAttributesFlags |= intent.getIntExtra(
- RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
- 0 /*defaultValue == no flags*/);
-
- // Get the sound URI whose list item should have a checkmark
- Uri existingSoundUri = intent
+ // Get the URI whose list item should have a checkmark
+ mExistingUri = intent
.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
- return new RingtoneListHandler.Config(hasDefaultSoundItem,
- uriForDefaultSoundItem, hasSilentSoundItem, existingSoundUri);
- }
-
- private RingtoneListHandler.Config getVibrationListConfig(
- RingtonePickerViewModel.PickerType pickerType, Intent intent) {
- if (pickerType != RingtonePickerViewModel.PickerType.VIBRATION_PICKER
- && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
- // This ringtone picker does not require a vibration picker.
- return null;
+ // Create the list of ringtones and hold on to it so we can update later.
+ mAdapter = new BadgedRingtoneAdapter(this, mCursor,
+ /* isManagedProfile = */ UserManager.get(this).isManagedProfile(mPickerUserId));
+ if (savedInstanceState != null) {
+ setCheckedItem(savedInstanceState.getInt(SAVE_CLICKED_POS, POS_UNKNOWN));
}
- // Get whether to show the 'Default' vibration item, and the URI to play when it's clicked
- boolean hasDefaultVibrationItem =
- intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_DEFAULT, false);
+ final AlertController.AlertParams p = mAlertParams;
+ p.mAdapter = mAdapter;
+ p.mOnClickListener = mRingtoneClickListener;
+ p.mLabelColumn = COLUMN_LABEL;
+ p.mIsSingleChoice = true;
+ p.mOnItemSelectedListener = this;
+ if (mShowOkCancelButtons) {
+ p.mPositiveButtonText = getString(com.android.internal.R.string.ok);
+ p.mPositiveButtonListener = this;
+ p.mNegativeButtonText = getString(com.android.internal.R.string.cancel);
+ p.mPositiveButtonListener = this;
+ }
+ p.mOnPrepareListViewListener = this;
- // The Uri to play when the 'Default' vibration item is clicked.
- Uri uriForDefaultVibrationItem = intent.getParcelableExtra(EXTRA_VIBRATION_DEFAULT_URI);
+ p.mTitle = intent.getCharSequenceExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
+ if (p.mTitle == null) {
+ if (mType == RingtoneManager.TYPE_ALARM) {
+ p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title_alarm);
+ } else if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+ p.mTitle =
+ getString(com.android.internal.R.string.ringtone_picker_title_notification);
+ } else {
+ p.mTitle = getString(com.android.internal.R.string.ringtone_picker_title);
+ }
+ }
- // Get whether this list has the 'Silent' vibration item.
- boolean hasSilentVibrationItem =
- intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_SILENT, true);
+ setupAlert();
- // Get the vibration URI whose list item should have a checkmark
- Uri existingVibrationUri = intent.getParcelableExtra(EXTRA_VIBRATION_EXISTING_URI);
-
- return new RingtoneListHandler.Config(
- hasDefaultVibrationItem, uriForDefaultVibrationItem, hasSilentVibrationItem,
- existingVibrationUri);
+ ListView listView = mAlert.getListView();
+ if (listView != null) {
+ // List view needs to gain focus in order for RSB to work.
+ if (!listView.requestFocus()) {
+ Log.e(TAG, "Unable to gain focus! RSB may not work properly.");
+ }
+ }
+ }
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(SAVE_CLICKED_POS, getCheckedItem());
}
@Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode == ADD_FILE_REQUEST_CODE && resultCode == RESULT_OK) {
+ // Add the custom ringtone in a separate thread
+ final AsyncTask<Uri, Void, Uri> installTask = new AsyncTask<Uri, Void, Uri>() {
+ @Override
+ protected Uri doInBackground(Uri... params) {
+ try {
+ return mRingtoneManager.addCustomExternalRingtone(params[0], mType);
+ } catch (IOException | IllegalArgumentException e) {
+ Log.e(TAG, "Unable to add new ringtone", e);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Uri ringtoneUri) {
+ if (ringtoneUri != null) {
+ requeryForAdapter();
+ } else {
+ // Ringtone was not added, display error Toast
+ Toast.makeText(RingtonePickerActivity.this, R.string.unable_to_add_ringtone,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ };
+ installTask.execute(data.getData());
+ }
+ }
+
+ // Disabled because context menus aren't Material Design :(
+ /*
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+ int position = ((AdapterContextMenuInfo) menuInfo).position;
+
+ Ringtone ringtone = getRingtone(getRingtoneManagerPosition(position));
+ if (ringtone != null && mRingtoneManager.isCustomRingtone(ringtone.getUri())) {
+ // It's a custom ringtone so we display the context menu
+ menu.setHeaderTitle(ringtone.getTitle(this));
+ menu.add(Menu.NONE, Menu.FIRST, Menu.NONE, R.string.delete_ringtone_text);
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case Menu.FIRST: {
+ int deletedRingtonePos = ((AdapterContextMenuInfo) item.getMenuInfo()).position;
+ Uri deletedRingtoneUri = getRingtone(
+ getRingtoneManagerPosition(deletedRingtonePos)).getUri();
+ if(mRingtoneManager.deleteExternalRingtone(deletedRingtoneUri)) {
+ requeryForAdapter();
+ } else {
+ Toast.makeText(this, R.string.unable_to_delete_ringtone, Toast.LENGTH_SHORT)
+ .show();
+ }
+ return true;
+ }
+ default: {
+ return false;
+ }
+ }
+ }
+ */
+
+ @Override
public void onDestroy() {
- mRingtonePickerViewModel.cancelPendingAsyncTasks();
+ if (mHandler != null) {
+ mHandler.removeCallbacksAndMessages(null);
+ }
+ if (mCursor != null) {
+ mCursor.close();
+ mCursor = null;
+ }
super.onDestroy();
}
+ public void onPrepareListView(ListView listView) {
+ // Reset the static item count, as this method can be called multiple times
+ mStaticItemCount = 0;
+
+ if (mHasDefaultItem) {
+ mDefaultRingtonePos = addDefaultRingtoneItem(listView);
+
+ if (getCheckedItem() == POS_UNKNOWN && RingtoneManager.isDefault(mExistingUri)) {
+ setCheckedItem(mDefaultRingtonePos);
+ }
+ }
+
+ if (mHasSilentItem) {
+ mSilentPos = addSilentItem(listView);
+
+ // The 'Silent' item should use a null Uri
+ if (getCheckedItem() == POS_UNKNOWN && mExistingUri == null) {
+ setCheckedItem(mSilentPos);
+ }
+ }
+
+ if (getCheckedItem() == POS_UNKNOWN) {
+ setCheckedItem(getListPosition(mRingtoneManager.getRingtonePosition(mExistingUri)));
+ }
+
+ // In the buttonless (watch-only) version, preemptively set our result since we won't
+ // have another chance to do so before the activity closes.
+ if (!mShowOkCancelButtons) {
+ setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+ }
+ // If external storage is available, add a button to install sounds from storage.
+ if (resolvesMediaFilePicker()
+ && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ addNewSoundItem(listView);
+ }
+
+ // Enable context menu in ringtone items
+ registerForContextMenu(listView);
+ }
+
+ /**
+ * Re-query RingtoneManager for the most recent set of installed ringtones. May move the
+ * selected item position to match the new position of the chosen sound.
+ *
+ * This should only need to happen after adding or removing a ringtone.
+ */
+ private void requeryForAdapter() {
+ // Refresh and set a new cursor, closing the old one.
+ initRingtoneManager();
+ mAdapter.changeCursor(mCursor);
+
+ // Update checked item location.
+ int checkedPosition = POS_UNKNOWN;
+ for (int i = 0; i < mAdapter.getCount(); i++) {
+ if (mAdapter.getItemId(i) == mCheckedItemId) {
+ checkedPosition = getListPosition(i);
+ break;
+ }
+ }
+ if (mHasSilentItem && checkedPosition == POS_UNKNOWN) {
+ checkedPosition = mSilentPos;
+ }
+ setCheckedItem(checkedPosition);
+ setupAlert();
+ }
+
+ /**
+ * Adds a static item to the top of the list. A static item is one that is not from the
+ * RingtoneManager.
+ *
+ * @param listView The ListView to add to.
+ * @param textResId The resource ID of the text for the item.
+ * @return The position of the inserted item.
+ */
+ private int addStaticItem(ListView listView, int textResId) {
+ TextView textView = (TextView) getLayoutInflater().inflate(
+ com.android.internal.R.layout.select_dialog_singlechoice_material, listView, false);
+ textView.setText(textResId);
+ listView.addHeaderView(textView);
+ mStaticItemCount++;
+ return listView.getHeaderViewsCount() - 1;
+ }
+
+ private int addDefaultRingtoneItem(ListView listView) {
+ if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+ return addStaticItem(listView, R.string.notification_sound_default);
+ } else if (mType == RingtoneManager.TYPE_ALARM) {
+ return addStaticItem(listView, R.string.alarm_sound_default);
+ }
+
+ return addStaticItem(listView, R.string.ringtone_default);
+ }
+
+ private int addSilentItem(ListView listView) {
+ return addStaticItem(listView, com.android.internal.R.string.ringtone_silent);
+ }
+
+ private void addNewSoundItem(ListView listView) {
+ View view = getLayoutInflater().inflate(R.layout.add_new_sound_item, listView,
+ false /* attachToRoot */);
+ TextView text = (TextView)view.findViewById(R.id.add_new_sound_text);
+
+ if (mType == RingtoneManager.TYPE_ALARM) {
+ text.setText(R.string.add_alarm_text);
+ } else if (mType == RingtoneManager.TYPE_NOTIFICATION) {
+ text.setText(R.string.add_notification_text);
+ } else {
+ text.setText(R.string.add_ringtone_text);
+ }
+ listView.addFooterView(view);
+ }
+
+ private void initRingtoneManager() {
+ // Reinstantiate the RingtoneManager. Cursor.requery() was deprecated and calling it
+ // causes unexpected behavior.
+ mRingtoneManager = new RingtoneManager(mTargetContext, /* includeParentRingtones */ true);
+ if (mType != -1) {
+ mRingtoneManager.setType(mType);
+ }
+ mCursor = new LocalizedCursor(mRingtoneManager.getCursor(), getResources(), COLUMN_LABEL);
+ }
+
+ private Ringtone getRingtone(int ringtoneManagerPosition) {
+ if (ringtoneManagerPosition < 0) {
+ return null;
+ }
+ return mRingtoneManager.getRingtone(ringtoneManagerPosition);
+ }
+
+ private int getCheckedItem() {
+ return mAlertParams.mCheckedItem;
+ }
+
+ private void setCheckedItem(int pos) {
+ mAlertParams.mCheckedItem = pos;
+ mCheckedItemId = mAdapter.getItemId(getRingtoneManagerPosition(pos));
+ }
+
+ /*
+ * On click of Ok/Cancel buttons
+ */
+ public void onClick(DialogInterface dialog, int which) {
+ boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE;
+
+ // Stop playing the previous ringtone
+ mRingtoneManager.stopPreviousRingtone();
+
+ if (positiveResult) {
+ setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+ } else {
+ setResult(RESULT_CANCELED);
+ }
+
+ finish();
+ }
+
+ /*
+ * On item selected via keys
+ */
+ public void onItemSelected(AdapterView parent, View view, int position, long id) {
+ // footer view
+ if (position >= mCursor.getCount() + mStaticItemCount) {
+ return;
+ }
+
+ playRingtone(position, DELAY_MS_SELECTION_PLAYED);
+
+ // In the buttonless (watch-only) version, preemptively set our result since we won't
+ // have another chance to do so before the activity closes.
+ if (!mShowOkCancelButtons) {
+ setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
+ }
+ }
+
+ public void onNothingSelected(AdapterView parent) {
+ }
+
+ private void playRingtone(int position, int delayMs) {
+ mHandler.removeCallbacks(this);
+ mSampleRingtonePos = position;
+ mHandler.postDelayed(this, delayMs);
+ }
+
+ public void run() {
+ stopAnyPlayingRingtone();
+ if (mSampleRingtonePos == mSilentPos) {
+ return;
+ }
+
+ Ringtone ringtone;
+ if (mSampleRingtonePos == mDefaultRingtonePos) {
+ if (mDefaultRingtone == null) {
+ mDefaultRingtone = RingtoneManager.getRingtone(this, mUriForDefaultItem);
+ }
+ /*
+ * Stream type of mDefaultRingtone is not set explicitly here.
+ * It should be set in accordance with mRingtoneManager of this Activity.
+ */
+ if (mDefaultRingtone != null) {
+ mDefaultRingtone.setStreamType(mRingtoneManager.inferStreamType());
+ }
+ ringtone = mDefaultRingtone;
+ mCurrentRingtone = null;
+ } else {
+ ringtone = mRingtoneManager.getRingtone(getRingtoneManagerPosition(mSampleRingtonePos));
+ mCurrentRingtone = ringtone;
+ }
+
+ if (ringtone != null) {
+ if (mAttributesFlags != 0) {
+ ringtone.setAudioAttributes(
+ new AudioAttributes.Builder(ringtone.getAudioAttributes())
+ .setFlags(mAttributesFlags)
+ .build());
+ }
+ ringtone.play();
+ }
+ }
+
@Override
protected void onStop() {
super.onStop();
- mRingtonePickerViewModel.onStop(isChangingConfigurations());
+
+ if (!isChangingConfigurations()) {
+ stopAnyPlayingRingtone();
+ } else {
+ saveAnyPlayingRingtone();
+ }
}
@Override
protected void onPause() {
super.onPause();
- mRingtonePickerViewModel.onPause(isChangingConfigurations());
- }
-
- /**
- * Maps the ringtone picker category to the appropriate PickerType.
- * If the category is null or the feature is still not released, then it defaults to sound
- * picker.
- *
- * @param category the ringtone picker category.
- * @return the corresponding picker type.
- */
- private static RingtonePickerViewModel.PickerType mapCategoryToPickerType(String category) {
- if (category == null || !RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED) {
- return RingtonePickerViewModel.PickerType.SOUND_PICKER;
- }
-
- switch (category) {
- case "android.intent.category.RINGTONE_PICKER_RINGTONE":
- return RingtonePickerViewModel.PickerType.RINGTONE_PICKER;
- case "android.intent.category.RINGTONE_PICKER_SOUND":
- return RingtonePickerViewModel.PickerType.SOUND_PICKER;
- case "android.intent.category.RINGTONE_PICKER_VIBRATION":
- return RingtonePickerViewModel.PickerType.VIBRATION_PICKER;
- default:
- Log.w(TAG, "Unrecognized category: " + category + ". Defaulting to sound picker.");
- return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+ if (!isChangingConfigurations()) {
+ stopAnyPlayingRingtone();
}
}
-}
+
+ private void setSuccessResultWithRingtone(Uri ringtoneUri) {
+ setResult(RESULT_OK,
+ new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, ringtoneUri));
+ }
+
+ private Uri getCurrentlySelectedRingtoneUri() {
+ if (getCheckedItem() == POS_UNKNOWN) {
+ // When the getCheckItem is POS_UNKNOWN, it is not the case we expected.
+ // We return null for this case.
+ return null;
+ } else if (getCheckedItem() == mDefaultRingtonePos) {
+ // Use the default Uri that they originally gave us.
+ return mUriForDefaultItem;
+ } else if (getCheckedItem() == mSilentPos) {
+ // Use a null Uri for the 'Silent' item.
+ return null;
+ } else {
+ return mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(getCheckedItem()));
+ }
+ }
+
+ private void saveAnyPlayingRingtone() {
+ if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
+ sPlayingRingtone = mDefaultRingtone;
+ } else if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) {
+ sPlayingRingtone = mCurrentRingtone;
+ }
+ }
+
+ private void stopAnyPlayingRingtone() {
+ if (sPlayingRingtone != null && sPlayingRingtone.isPlaying()) {
+ sPlayingRingtone.stop();
+ }
+ sPlayingRingtone = null;
+
+ if (mDefaultRingtone != null && mDefaultRingtone.isPlaying()) {
+ mDefaultRingtone.stop();
+ }
+
+ if (mRingtoneManager != null) {
+ mRingtoneManager.stopPreviousRingtone();
+ }
+ }
+
+ private int getRingtoneManagerPosition(int listPos) {
+ return listPos - mStaticItemCount;
+ }
+
+ private int getListPosition(int ringtoneManagerPos) {
+
+ // If the manager position is -1 (for not found), return that
+ if (ringtoneManagerPos < 0) return ringtoneManagerPos;
+
+ return ringtoneManagerPos + mStaticItemCount;
+ }
+
+ private Intent getMediaFilePickerIntent() {
+ final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
+ chooseFile.setType("audio/*");
+ chooseFile.putExtra(Intent.EXTRA_MIME_TYPES,
+ new String[] { "audio/*", "application/ogg" });
+ return chooseFile;
+ }
+
+ private boolean resolvesMediaFilePicker() {
+ return getMediaFilePickerIntent().resolveActivity(getPackageManager()) != null;
+ }
+
+ private static class LocalizedCursor extends CursorWrapper {
+
+ final int mTitleIndex;
+ final Resources mResources;
+ String mNamePrefix;
+ final Pattern mSanitizePattern;
+
+ LocalizedCursor(Cursor cursor, Resources resources, String columnLabel) {
+ super(cursor);
+ mTitleIndex = mCursor.getColumnIndex(columnLabel);
+ mResources = resources;
+ mSanitizePattern = Pattern.compile("[^a-zA-Z0-9]");
+ if (mTitleIndex == -1) {
+ Log.e(TAG, "No index for column " + columnLabel);
+ mNamePrefix = null;
+ } else {
+ try {
+ // Build the prefix for the name of the resource to look up
+ // format is: "ResourcePackageName::ResourceTypeName/"
+ // (the type name is expected to be "string" but let's not hardcode it).
+ // Here we use an existing resource "notification_sound_default" which is
+ // always expected to be found.
+ mNamePrefix = String.format("%s:%s/%s",
+ mResources.getResourcePackageName(R.string.notification_sound_default),
+ mResources.getResourceTypeName(R.string.notification_sound_default),
+ SOUND_NAME_RES_PREFIX);
+ } catch (NotFoundException e) {
+ mNamePrefix = null;
+ }
+ }
+ }
+
+ /**
+ * Process resource name to generate a valid resource name.
+ * @param input
+ * @return a non-null String
+ */
+ private String sanitize(String input) {
+ if (input == null) {
+ return "";
+ }
+ return mSanitizePattern.matcher(input).replaceAll("_").toLowerCase();
+ }
+
+ @Override
+ public String getString(int columnIndex) {
+ final String defaultName = mCursor.getString(columnIndex);
+ if ((columnIndex != mTitleIndex) || (mNamePrefix == null)) {
+ return defaultName;
+ }
+ TypedValue value = new TypedValue();
+ try {
+ // the name currently in the database is used to derive a name to match
+ // against resource names in this package
+ mResources.getValue(mNamePrefix + sanitize(defaultName), value, false);
+ } catch (NotFoundException e) {
+ // no localized string, use the default string
+ return defaultName;
+ }
+ if ((value != null) && (value.type == TypedValue.TYPE_STRING)) {
+ Log.d(TAG, String.format("Replacing name %s with %s",
+ defaultName, value.string.toString()));
+ return value.string.toString();
+ } else {
+ Log.e(TAG, "Invalid value when looking up localized name, using " + defaultName);
+ return defaultName;
+ }
+ }
+ }
+
+ private class BadgedRingtoneAdapter extends CursorAdapter {
+ private final boolean mIsManagedProfile;
+
+ public BadgedRingtoneAdapter(Context context, Cursor cursor, boolean isManagedProfile) {
+ super(context, cursor);
+ mIsManagedProfile = isManagedProfile;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ if (position < 0) {
+ return position;
+ }
+ return super.getItemId(position);
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ return inflater.inflate(R.layout.radio_with_work_badge, parent, false);
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ // Set text as the title of the ringtone
+ ((TextView) view.findViewById(R.id.checked_text_view))
+ .setText(cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX));
+
+ boolean isWorkRingtone = false;
+ if (mIsManagedProfile) {
+ /*
+ * Display the work icon if the ringtone belongs to a work profile. We can tell that
+ * a ringtone belongs to a work profile if the picker user is a managed profile, the
+ * ringtone Uri is in external storage, and either the uri has no user id or has the
+ * id of the picker user
+ */
+ Uri currentUri = mRingtoneManager.getRingtoneUri(cursor.getPosition());
+ int uriUserId = ContentProvider.getUserIdFromUri(currentUri, mPickerUserId);
+ Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri);
+
+ if (uriUserId == mPickerUserId && uriWithoutUserId.toString()
+ .startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {
+ isWorkRingtone = true;
+ }
+ }
+
+ ImageView workIcon = (ImageView) view.findViewById(R.id.work_icon);
+ if(isWorkRingtone) {
+ workIcon.setImageDrawable(getPackageManager().getUserBadgeForDensityNoBackground(
+ UserHandle.of(mPickerUserId), -1 /* density */));
+ workIcon.setVisibility(View.VISIBLE);
+ } else {
+ workIcon.setVisibility(View.GONE);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SoundPicker2/Android.bp b/packages/SoundPicker2/Android.bp
new file mode 100644
index 0000000..f4d8bf2
--- /dev/null
+++ b/packages/SoundPicker2/Android.bp
@@ -0,0 +1,46 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+ name: "SoundPicker2Lib",
+ srcs: [
+ "src/**/*.java",
+ ],
+ resource_dirs: [
+ "res",
+ ],
+ static_libs: [
+ "androidx.appcompat_appcompat",
+ "hilt_android",
+ "guava",
+ "androidx.recyclerview_recyclerview",
+ "androidx-constraintlayout_constraintlayout",
+ "androidx.viewpager2_viewpager2",
+ "com.google.android.material_material",
+ ],
+}
+
+android_app {
+ name: "SoundPicker2",
+ defaults: ["platform_app_defaults"],
+ manifest: "AndroidManifest.xml",
+ static_libs: ["SoundPicker2Lib"],
+ platform_apis: true,
+ certificate: "media",
+ privileged: true,
+
+ optimize: {
+ enabled: true,
+ optimize: true,
+ shrink: true,
+ shrink_resources: true,
+ obfuscate: false,
+ proguard_compatibility: false,
+ },
+}
diff --git a/packages/SoundPicker2/AndroidManifest.xml b/packages/SoundPicker2/AndroidManifest.xml
new file mode 100644
index 0000000..934b003
--- /dev/null
+++ b/packages/SoundPicker2/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.soundpicker"
+ android:sharedUserId="android.media">
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+
+ <application
+ android:name=".RingtonePickerApplication"
+ android:allowBackup="false"
+ android:label="@string/app_label"
+ android:theme="@style/Theme.AppCompat"
+ android:supportsRtl="true">
+ <receiver android:name="RingtoneReceiver"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
+ </intent-filter>
+ </receiver>
+
+ <service android:name="RingtoneOverlayService" />
+
+ <activity android:name="RingtonePickerActivity"
+ android:theme="@style/Theme.AppCompat.Dialog"
+ android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
+ android:excludeFromRecents="true"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.RINGTONE_PICKER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.RINGTONE_PICKER_SOUND" />
+ <category android:name="android.intent.category.RINGTONE_PICKER_VIBRATION" />
+ <category android:name="android.intent.category.RINGTONE_PICKER_RINGTONE" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/packages/SoundPicker2/OWNERS b/packages/SoundPicker2/OWNERS
new file mode 100644
index 0000000..5bf46e0
--- /dev/null
+++ b/packages/SoundPicker2/OWNERS
@@ -0,0 +1,2 @@
+# Haptics team works on the SoundPicker
+include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SoundPicker2/res/drawable/ic_add.xml b/packages/SoundPicker2/res/drawable/ic_add.xml
new file mode 100644
index 0000000..22b3fe9
--- /dev/null
+++ b/packages/SoundPicker2/res/drawable/ic_add.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright (C) 2016 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="48.0"
+ android:viewportHeight="48.0">
+ <path
+ android:fillColor="?android:attr/colorAccent"
+ android:pathData="M38.0,26.0L26.0,26.0l0.0,12.0l-4.0,0.0L22.0,26.0L10.0,26.0l0.0,-4.0l12.0,0.0L22.0,10.0l4.0,0.0l0.0,12.0l12.0,0.0l0.0,4.0z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/drawable/ic_add_padded.xml b/packages/SoundPicker2/res/drawable/ic_add_padded.xml
new file mode 100644
index 0000000..c376867
--- /dev/null
+++ b/packages/SoundPicker2/res/drawable/ic_add_padded.xml
@@ -0,0 +1,22 @@
+<!--
+ Copyright (C) 2017 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.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/ic_add"
+ android:insetTop="4dp"
+ android:insetRight="4dp"
+ android:insetBottom="4dp"
+ android:insetLeft="4dp"/>
diff --git a/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml b/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml
new file mode 100644
index 0000000..edfc0ab
--- /dev/null
+++ b/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 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.
+-->
+
+<!--
+ Currently, no file manager app on watch could handle ACTION_GET_CONTENT intent.
+ Make the visibility to "gone" to prevent failures.
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/add_new_sound_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text="@null"
+ android:textColor="?android:attr/colorAccent"
+ android:gravity="center_vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:drawableStart="@drawable/ic_add_padded"
+ android:drawablePadding="8dp"
+ android:ellipsize="marquee"
+ android:visibility="gone" />
diff --git a/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml b/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml
new file mode 100644
index 0000000..ee29a37
--- /dev/null
+++ b/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ >
+
+ <CheckedTextView
+ android:id="@+id/checked_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:gravity="center_vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:drawableStart="?android:attr/listChoiceIndicatorSingle"
+ android:drawablePadding="8dp"
+ android:ellipsize="marquee"
+ android:layout_toLeftOf="@+id/work_icon"
+ android:maxLines="3" />
+
+ <ImageView
+ android:id="@id/work_icon"
+ android:layout_width="18dp"
+ android:layout_height="18dp"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:scaleType="centerCrop"
+ android:layout_marginRight="20dp" />
+</com.android.soundpicker.CheckedListItem>
diff --git a/packages/SoundPicker/res/layout/activity_ringtone_picker.xml b/packages/SoundPicker2/res/layout/activity_ringtone_picker.xml
similarity index 100%
rename from packages/SoundPicker/res/layout/activity_ringtone_picker.xml
rename to packages/SoundPicker2/res/layout/activity_ringtone_picker.xml
diff --git a/packages/SoundPicker2/res/layout/add_new_sound_item.xml b/packages/SoundPicker2/res/layout/add_new_sound_item.xml
new file mode 100644
index 0000000..024b97e
--- /dev/null
+++ b/packages/SoundPicker2/res/layout/add_new_sound_item.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ android:focusable="true"
+ android:clickable="true">
+
+ <ImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:scaleType="centerCrop"
+ android:layout_marginRight="24dp"
+ android:layout_marginLeft="24dp"
+ android:src="@drawable/ic_add"/>
+
+ <TextView
+ android:id="@+id/add_new_sound_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:text="@null"
+ android:textColor="?android:attr/colorAccent"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:maxLines="3"
+ android:gravity="center_vertical"
+ android:paddingEnd="?android:attr/dialogPreferredPadding"
+ android:drawablePadding="20dp"
+ android:ellipsize="marquee"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker/res/layout/fragment_ringtone_picker.xml b/packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml
similarity index 100%
rename from packages/SoundPicker/res/layout/fragment_ringtone_picker.xml
rename to packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml
diff --git a/packages/SoundPicker/res/layout/fragment_tabbed_dialog.xml b/packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml
similarity index 100%
rename from packages/SoundPicker/res/layout/fragment_tabbed_dialog.xml
rename to packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml
diff --git a/packages/SoundPicker2/res/layout/radio_with_work_badge.xml b/packages/SoundPicker2/res/layout/radio_with_work_badge.xml
new file mode 100644
index 0000000..36ac93e
--- /dev/null
+++ b/packages/SoundPicker2/res/layout/radio_with_work_badge.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<com.android.soundpicker.CheckedListItem
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ android:focusable="true"
+ android:clickable="true">
+
+ <CheckedTextView
+ android:id="@+id/checked_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:gravity="center_vertical"
+ android:paddingStart="20dp"
+ android:paddingEnd="?android:attr/dialogPreferredPadding"
+ android:drawableStart="?android:attr/listChoiceIndicatorSingle"
+ android:drawablePadding="20dp"
+ android:ellipsize="marquee"
+ android:layout_toLeftOf="@+id/work_icon"
+ android:maxLines="3"/>
+
+ <ImageView
+ android:id="@id/work_icon"
+ android:layout_width="18dp"
+ android:layout_height="18dp"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:scaleType="centerCrop"
+ android:layout_marginRight="20dp"/>
+</com.android.soundpicker.CheckedListItem>
diff --git a/packages/SoundPicker2/res/raw/default_alarm_alert.ogg b/packages/SoundPicker2/res/raw/default_alarm_alert.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker2/res/raw/default_alarm_alert.ogg
diff --git a/packages/SoundPicker2/res/raw/default_notification_sound.ogg b/packages/SoundPicker2/res/raw/default_notification_sound.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker2/res/raw/default_notification_sound.ogg
diff --git a/packages/SoundPicker2/res/raw/default_ringtone.ogg b/packages/SoundPicker2/res/raw/default_ringtone.ogg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/SoundPicker2/res/raw/default_ringtone.ogg
diff --git a/packages/SoundPicker2/res/values/config.xml b/packages/SoundPicker2/res/values/config.xml
new file mode 100644
index 0000000..4e237a2
--- /dev/null
+++ b/packages/SoundPicker2/res/values/config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. Do not translate.
+
+ NOTE: The naming convention is "config_camelCaseValue". -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- True if the ringtone picker should show the ok/cancel buttons. If it is not shown, the
+ ringtone will be automatically selected when the picker is closed. -->
+ <bool name="config_showOkCancelButtons">true</bool>
+</resources>
diff --git a/packages/SoundPicker2/res/values/strings.xml b/packages/SoundPicker2/res/values/strings.xml
new file mode 100644
index 0000000..ab7b95a
--- /dev/null
+++ b/packages/SoundPicker2/res/values/strings.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Choice in the ringtone picker. If chosen, the default ringtone will be used. -->
+ <string name="ringtone_default">Default ringtone</string>
+
+ <!-- Choice in the notification sound picker. If chosen, the default notification sound will be
+ used. -->
+ <string name="notification_sound_default">Default notification sound</string>
+
+ <!-- Choice in the alarm sound picker. If chosen, the default alarm sound will be used. -->
+ <string name="alarm_sound_default">Default alarm sound</string>
+
+ <!-- Text for the RingtonePicker item that allows adding a new ringtone. -->
+ <string name="add_ringtone_text">Add ringtone</string>
+ <!-- Text for the RingtonePicker item that allows adding a new alarm. -->
+ <string name="add_alarm_text">Add alarm</string>
+ <!-- Text for the RingtonePicker item that allows adding a new notification. -->
+ <string name="add_notification_text">Add notification</string>
+ <!-- Text for the RingtonePicker item ContextMenu that allows deleting a custom ringtone. -->
+ <string name="delete_ringtone_text">Delete</string>
+ <!-- Text for the Toast displayed when adding a custom ringtone fails. -->
+ <string name="unable_to_add_ringtone">Unable to add custom ringtone</string>
+ <!-- Text for the Toast displayed when deleting a custom ringtone fails. -->
+ <string name="unable_to_delete_ringtone">Unable to delete custom ringtone</string>
+
+ <!-- Text for the name of the app. [CHAR LIMIT=12] -->
+ <string name="app_label">Sounds</string>
+
+ <string name="empty_list">The list is empty</string>
+ <string name="sound_page_title">Sound</string>
+ <string name="vibration_page_title">Vibration</string>
+</resources>
diff --git a/packages/SoundPicker2/res/values/styles.xml b/packages/SoundPicker2/res/values/styles.xml
new file mode 100644
index 0000000..d22d9c4
--- /dev/null
+++ b/packages/SoundPicker2/res/values/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <style name="PickerDialogTheme" parent="@*android:style/Theme.DeviceDefault.Settings.Dialog">
+ </style>
+
+</resources>
diff --git a/packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java
rename to packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java b/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java
new file mode 100644
index 0000000..819ae98
--- /dev/null
+++ b/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 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.soundpicker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Checkable;
+import android.widget.CheckedTextView;
+import android.widget.RelativeLayout;
+
+/**
+ * The {@link CheckedListItem} is a layout item that represents a ringtone, and is used in
+ * {@link RingtonePickerActivity}. It contains the ringtone's name, and a work badge to right of the
+ * name if the ringtone belongs to a work profile.
+ */
+public class CheckedListItem extends RelativeLayout implements Checkable {
+
+ public CheckedListItem(Context context) {
+ super(context);
+ }
+
+ public CheckedListItem(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ getCheckedTextView().setChecked(checked);
+ }
+
+ @Override
+ public boolean isChecked() {
+ return getCheckedTextView().isChecked();
+ }
+
+ @Override
+ public void toggle() {
+ getCheckedTextView().toggle();
+ }
+
+ private CheckedTextView getCheckedTextView() {
+ return (CheckedTextView) findViewById(R.id.checked_text_view);
+ }
+
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/ListeningExecutorServiceFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
rename to packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/LocalizedCursor.java b/packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/LocalizedCursor.java
rename to packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneManagerFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneManagerFactory.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java
new file mode 100644
index 0000000..b94ebeb
--- /dev/null
+++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 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.soundpicker;
+
+import android.app.Service;
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.IBinder;
+import android.provider.MediaStore;
+import android.provider.Settings.System;
+import android.util.Log;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Service to copy and set customization of default sounds
+ */
+public class RingtoneOverlayService extends Service {
+ private static final String TAG = "RingtoneOverlayService";
+ private static final boolean DEBUG = false;
+
+ @Override
+ public int onStartCommand(@Nullable final Intent intent, final int flags, final int startId) {
+ AsyncTask.execute(() -> {
+ updateRingtones();
+ stopSelf();
+ });
+
+ // Try again later if we are killed before we finish.
+ return Service.START_REDELIVER_INTENT;
+ }
+
+ @Override
+ public IBinder onBind(@Nullable final Intent intent) {
+ return null;
+ }
+
+ private void updateRingtones() {
+ copyResourceAndSetAsSound(R.raw.default_ringtone,
+ System.RINGTONE, Environment.DIRECTORY_RINGTONES);
+ copyResourceAndSetAsSound(R.raw.default_notification_sound,
+ System.NOTIFICATION_SOUND, Environment.DIRECTORY_NOTIFICATIONS);
+ copyResourceAndSetAsSound(R.raw.default_alarm_alert,
+ System.ALARM_ALERT, Environment.DIRECTORY_ALARMS);
+ }
+
+ /* If the resource contains any data, copy a resource to the file system, scan it, and set the
+ * file URI as the default for a sound. */
+ private void copyResourceAndSetAsSound(@IdRes final int id, @NonNull final String name,
+ @NonNull final String subPath) {
+ final File destDir = Environment.getExternalStoragePublicDirectory(subPath);
+ if (!destDir.exists() && !destDir.mkdirs()) {
+ Log.e(TAG, "can't create " + destDir.getAbsolutePath());
+ return;
+ }
+
+ final File dest = new File(destDir, "default_" + name + ".ogg");
+ try (
+ InputStream is = getResources().openRawResource(id);
+ FileOutputStream os = new FileOutputStream(dest);
+ ) {
+ if (is.available() > 0) {
+ FileUtils.copy(is, os);
+ final Uri uri = scanFile(dest);
+ if (uri != null) {
+ set(name, uri);
+ }
+ } else {
+ // TODO Shall we remove any former copied resource in this case and unset
+ // the defaults if we use this event a second time to clear the data?
+ if (DEBUG) Log.d(TAG, "Resource for " + name + " has no overlay");
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to open resource for " + name + ": " + e);
+ }
+ }
+
+ private Uri scanFile(@NonNull final File file) {
+ return MediaStore.scanFile(getContentResolver(), file);
+ }
+
+ private void set(@NonNull final String name, @NonNull final Uri uri) {
+ final Uri settingUri = System.getUriFor(name);
+ RingtoneManager.setActualDefaultRingtoneUri(this,
+ RingtoneManager.getDefaultType(settingUri), uri);
+ System.putInt(getContentResolver(), name + "_set", 1);
+ }
+}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java
new file mode 100644
index 0000000..90a14f9
--- /dev/null
+++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2007 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.soundpicker;
+
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.lifecycle.ViewModelProvider;
+
+import dagger.hilt.android.AndroidEntryPoint;
+
+/**
+ * The {@link RingtonePickerActivity} allows the user to choose one from all of the
+ * available ringtones. The chosen ringtone's URI will be persisted as a string.
+ *
+ * @see RingtoneManager#ACTION_RINGTONE_PICKER
+ */
+@AndroidEntryPoint(AppCompatActivity.class)
+public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity {
+
+ private static final String TAG = "RingtonePickerActivity";
+ // TODO: Use the extra keys from RingtoneManager once they're added.
+ private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY";
+ private static final String EXTRA_VIBRATION_SHOW_DEFAULT = "EXTRA_VIBRATION_SHOW_DEFAULT";
+ private static final String EXTRA_VIBRATION_DEFAULT_URI = "EXTRA_VIBRATION_DEFAULT_URI";
+ private static final String EXTRA_VIBRATION_SHOW_SILENT = "EXTRA_VIBRATION_SHOW_SILENT";
+ private static final String EXTRA_VIBRATION_EXISTING_URI = "EXTRA_VIBRATION_EXISTING_URI";
+ private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false;
+
+ private RingtonePickerViewModel mRingtonePickerViewModel;
+ private int mAttributesFlags;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_ringtone_picker);
+
+ mRingtonePickerViewModel = new ViewModelProvider(this).get(RingtonePickerViewModel.class);
+
+ Intent intent = getIntent();
+ /**
+ * Id of the user to which the ringtone picker should list the ringtones
+ */
+ int pickerUserId = UserHandle.myUserId();
+
+ // Get the types of ringtones to show
+ int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,
+ RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN);
+
+ // AudioAttributes flags
+ mAttributesFlags |= intent.getIntExtra(
+ RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
+ 0 /*defaultValue == no flags*/);
+
+ boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
+
+ String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
+ if (title == null) {
+ title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType));
+ }
+ String ringtonePickerCategory = intent.getStringExtra(EXTRA_RINGTONE_PICKER_CATEGORY);
+ RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType(
+ ringtonePickerCategory);
+
+ RingtoneListHandler.Config soundListConfig = getSoundListConfig(pickerType, intent,
+ ringtoneType);
+ RingtoneListHandler.Config vibrationListConfig = getVibrationListConfig(pickerType, intent);
+
+ RingtonePickerViewModel.Config pickerConfig =
+ new RingtonePickerViewModel.Config(title, pickerUserId, ringtoneType,
+ showOkCancelButtons, mAttributesFlags, pickerType);
+
+ mRingtonePickerViewModel.init(pickerConfig, soundListConfig, vibrationListConfig);
+
+ if (savedInstanceState == null) {
+ TabbedDialogFragment dialogFragment = new TabbedDialogFragment();
+
+ FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+ Fragment prev = getSupportFragmentManager().findFragmentByTag(TabbedDialogFragment.TAG);
+ if (prev != null) {
+ ft.remove(prev);
+ }
+ ft.addToBackStack(null);
+ dialogFragment.show(ft, TabbedDialogFragment.TAG);
+ }
+
+ // The volume keys will control the stream that we are choosing a ringtone for
+ setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType());
+ }
+
+ private RingtoneListHandler.Config getSoundListConfig(
+ RingtonePickerViewModel.PickerType pickerType, Intent intent, int ringtoneType) {
+ if (pickerType != RingtonePickerViewModel.PickerType.SOUND_PICKER
+ && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
+ // This ringtone picker does not require a sound picker.
+ return null;
+ }
+
+ // Get whether to show the 'Default' sound item, and the URI to play when it's clicked
+ boolean hasDefaultSoundItem =
+ intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+
+ // The Uri to play when the 'Default' sound item is clicked.
+ Uri uriForDefaultSoundItem =
+ intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
+ if (uriForDefaultSoundItem == null) {
+ uriForDefaultSoundItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType);
+ }
+
+ // Get whether this list has the 'Silent' sound item.
+ boolean hasSilentSoundItem =
+ intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
+
+ // AudioAttributes flags
+ mAttributesFlags |= intent.getIntExtra(
+ RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
+ 0 /*defaultValue == no flags*/);
+
+ // Get the sound URI whose list item should have a checkmark
+ Uri existingSoundUri = intent
+ .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
+
+ return new RingtoneListHandler.Config(hasDefaultSoundItem,
+ uriForDefaultSoundItem, hasSilentSoundItem, existingSoundUri);
+ }
+
+ private RingtoneListHandler.Config getVibrationListConfig(
+ RingtonePickerViewModel.PickerType pickerType, Intent intent) {
+ if (pickerType != RingtonePickerViewModel.PickerType.VIBRATION_PICKER
+ && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
+ // This ringtone picker does not require a vibration picker.
+ return null;
+ }
+
+ // Get whether to show the 'Default' vibration item, and the URI to play when it's clicked
+ boolean hasDefaultVibrationItem =
+ intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_DEFAULT, false);
+
+ // The Uri to play when the 'Default' vibration item is clicked.
+ Uri uriForDefaultVibrationItem = intent.getParcelableExtra(EXTRA_VIBRATION_DEFAULT_URI);
+
+ // Get whether this list has the 'Silent' vibration item.
+ boolean hasSilentVibrationItem =
+ intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_SILENT, true);
+
+ // Get the vibration URI whose list item should have a checkmark
+ Uri existingVibrationUri = intent.getParcelableExtra(EXTRA_VIBRATION_EXISTING_URI);
+
+ return new RingtoneListHandler.Config(
+ hasDefaultVibrationItem, uriForDefaultVibrationItem, hasSilentVibrationItem,
+ existingVibrationUri);
+ }
+
+ @Override
+ public void onDestroy() {
+ mRingtonePickerViewModel.cancelPendingAsyncTasks();
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mRingtonePickerViewModel.onStop(isChangingConfigurations());
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mRingtonePickerViewModel.onPause(isChangingConfigurations());
+ }
+
+ /**
+ * Maps the ringtone picker category to the appropriate PickerType.
+ * If the category is null or the feature is still not released, then it defaults to sound
+ * picker.
+ *
+ * @param category the ringtone picker category.
+ * @return the corresponding picker type.
+ */
+ private static RingtonePickerViewModel.PickerType mapCategoryToPickerType(String category) {
+ if (category == null || !RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED) {
+ return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+ }
+
+ switch (category) {
+ case "android.intent.category.RINGTONE_PICKER_RINGTONE":
+ return RingtonePickerViewModel.PickerType.RINGTONE_PICKER;
+ case "android.intent.category.RINGTONE_PICKER_SOUND":
+ return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+ case "android.intent.category.RINGTONE_PICKER_VIBRATION":
+ return RingtonePickerViewModel.PickerType.VIBRATION_PICKER;
+ default:
+ Log.w(TAG, "Unrecognized category: " + category + ". Defaulting to sound picker.");
+ return RingtonePickerViewModel.PickerType.SOUND_PICKER;
+ }
+ }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerApplication.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtonePickerApplication.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java
rename to packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java
new file mode 100644
index 0000000..6a34936
--- /dev/null
+++ b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2007 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.soundpicker;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class RingtoneReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_DEVICE_CUSTOMIZATION_READY.equals(action)) {
+ initResourceRingtones(context);
+ }
+ }
+
+ private void initResourceRingtones(Context context) {
+ context.startService(
+ new Intent(context, RingtoneOverlayService.class));
+ }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java
rename to packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java
rename to packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java
rename to packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java
diff --git a/packages/SoundPicker/src/com/android/soundpicker/ViewPagerAdapter.java b/packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java
similarity index 100%
rename from packages/SoundPicker/src/com/android/soundpicker/ViewPagerAdapter.java
rename to packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java
diff --git a/packages/SoundPicker/tests/Android.bp b/packages/SoundPicker2/tests/Android.bp
similarity index 94%
rename from packages/SoundPicker/tests/Android.bp
rename to packages/SoundPicker2/tests/Android.bp
index c38426f..d88d442 100644
--- a/packages/SoundPicker/tests/Android.bp
+++ b/packages/SoundPicker2/tests/Android.bp
@@ -17,7 +17,7 @@
}
android_test {
- name: "SoundPickerTests",
+ name: "SoundPicker2Tests",
certificate: "platform",
libs: [
"android.test.runner",
@@ -30,7 +30,7 @@
"androidx.test.ext.truth",
"mockito-target-minus-junit4",
"guava-android-testlib",
- "SoundPickerLib",
+ "SoundPicker2Lib",
],
srcs: [
"src/**/*.java",
diff --git a/packages/SoundPicker/tests/AndroidManifest.xml b/packages/SoundPicker2/tests/AndroidManifest.xml
similarity index 100%
rename from packages/SoundPicker/tests/AndroidManifest.xml
rename to packages/SoundPicker2/tests/AndroidManifest.xml
diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
similarity index 100%
rename from packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
rename to packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
similarity index 100%
rename from packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
rename to packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index f65f5a3..9d3200d 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -62,6 +62,7 @@
static_libs: [
"CommunalLayoutLib",
"PlatformComposeCore",
+ "PlatformComposeSceneTransitionLayout",
"androidx.compose.runtime_runtime",
"androidx.compose.material3_material3",
@@ -164,7 +165,6 @@
"SystemUISharedLib",
"SystemUI-statsd",
"SettingsLib",
- "com_android_systemui_communal_flags_lib",
"com_android_systemui_flags_lib",
"androidx.core_core-ktx",
"androidx.viewpager2_viewpager2",
@@ -243,9 +243,6 @@
"tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt",
"tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt",
- /* Log fakes */
- "tests/src/com/android/systemui/log/core/FakeLogBuffer.kt",
-
/* QS fakes */
"tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt",
],
@@ -437,6 +434,7 @@
"SystemUI-statsd",
"SettingsLib",
"com_android_systemui_flags_lib",
+ "flag-junit-base",
"androidx.viewpager2_viewpager2",
"androidx.legacy_legacy-support-v4",
"androidx.recyclerview_recyclerview",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 9bfc4be..5881631 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -83,6 +83,7 @@
<uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/>
<uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL"/>
<uses-permission android:name="android.permission.LOCATION_HARDWARE" />
+ <uses-permission android:name="android.permission.NETWORK_FACTORY" />
<!-- Physical hardware -->
<uses-permission android:name="android.permission.MANAGE_USB" />
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
@@ -1062,5 +1063,9 @@
<meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer"
tools:node="remove" />
</provider>
+
+ <!-- Allow SystemUI to listen for the capabilities defined in the linked xml -->
+ <property android:name="android.net.PROPERTY_SELF_CERTIFIED_CAPABILITIES"
+ android:value="@xml/self_certified_network_capabilities_both" />
</application>
</manifest>
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 0623d4a..de73b77 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -32,22 +32,6 @@
]
},
{
- "name": "SystemUIGoogleScreenshotTests",
- "options": [
- {
- "exclude-annotation": "org.junit.Ignore"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "android.platform.test.annotations.Postsubmit"
- }
- ],
- // The test doesn't run on AOSP Cuttlefish
- "keywords": ["internal"]
- },
- {
// TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
"name": "SystemUIGoogleBiometricsScreenshotTests",
"options": [
@@ -144,9 +128,32 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
"exclude-annotation": "android.platform.test.annotations.Postsubmit"
}
]
}
+ ],
+ // v2/sysui/suite/test-mapping-sysui-screenshot-test-staged
+ "sysui-screenshot-test-staged": [
+ {
+ "name": "SystemUIGoogleScreenshotTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Postsubmit"
+ }
+ ]
+ }
]
}
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index b18c790..dc4208e 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -2,8 +2,7 @@
name: "com_android_systemui_flags",
package: "com.android.systemui",
srcs: [
- "systemui.aconfig",
- "accessibility.aconfig",
+ "*.aconfig",
],
}
@@ -11,16 +10,3 @@
name: "com_android_systemui_flags_lib",
aconfig_declarations: "com_android_systemui_flags",
}
-
-aconfig_declarations {
- name: "com_android_systemui_communal_flags",
- package: "com.android.systemui.communal",
- srcs: [
- "communal.aconfig",
- ],
-}
-
-java_aconfig_library {
- name: "com_android_systemui_communal_flags_lib",
- aconfig_declarations: "com_android_systemui_communal_flags",
-}
diff --git a/packages/SystemUI/aconfig/communal.aconfig b/packages/SystemUI/aconfig/communal.aconfig
index 8ecb984..2c6ff97 100644
--- a/packages/SystemUI/aconfig/communal.aconfig
+++ b/packages/SystemUI/aconfig/communal.aconfig
@@ -1,4 +1,4 @@
-package: "com.android.systemui.communal"
+package: "com.android.systemui"
flag {
name: "communal_hub"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 2509cfd..211af90 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -29,3 +29,10 @@
"Notification Manager Service"
bug: "299448097"
}
+
+flag {
+ name: "scene_container"
+ namespace: "systemui"
+ description: "Enables the scene container framework go/flexiglass."
+ bug: "283121968"
+}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
index 4ed78b3..33024f7 100644
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.layout.ui.compose
+import android.util.SizeF
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -54,7 +55,14 @@
Row(
modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)),
) {
- cardInfo.card.Content(Modifier.fillMaxSize())
+ cardInfo.card.Content(
+ modifier = Modifier.fillMaxSize(),
+ size =
+ SizeF(
+ layoutConfig.cardWidth.value,
+ layoutConfig.cardHeight(cardInfo.size).value,
+ ),
+ )
}
}
}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
index ac8aa67..4b2a156 100644
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.layout.ui.compose.config
+import android.util.SizeF
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -26,8 +27,11 @@
*
* To host non-Compose views, see
* https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose.
+ *
+ * @param size The size given to the card. Content of the card should fill all this space, given
+ * that margins and paddings have been taken care of by the layout.
*/
- @Composable abstract fun Content(modifier: Modifier)
+ @Composable abstract fun Content(modifier: Modifier, size: SizeF)
/**
* Sizes supported by the card.
diff --git a/packages/SystemUI/communal/layout/tests/Android.bp b/packages/SystemUI/communal/layout/tests/Android.bp
index a60b1de..9a05504 100644
--- a/packages/SystemUI/communal/layout/tests/Android.bp
+++ b/packages/SystemUI/communal/layout/tests/Android.bp
@@ -32,7 +32,7 @@
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
"testables",
- "truth-prebuilt",
+ "truth",
],
libs: [
"android.test.mock",
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
index fdf65f5..c1974ca 100644
--- a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
+++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.communal.layout
+import android.util.SizeF
import androidx.compose.material3.Card
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -91,7 +92,7 @@
override val supportedSizes = listOf(size)
@Composable
- override fun Content(modifier: Modifier) {
+ override fun Content(modifier: Modifier, size: SizeF) {
Card(modifier = modifier, content = {})
}
}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index 3d670b8..ddd1c67 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -22,6 +22,7 @@
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.scene.shared.model.Scene
@@ -66,10 +67,15 @@
override fun createCommunalView(
context: Context,
+ viewModel: CommunalViewModel,
): View {
throwComposeUnavailableError()
}
+ override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
+ throwComposeUnavailableError()
+ }
+
private fun throwComposeUnavailableError(): Nothing {
error(
"Compose is not available. Make sure to check isComposeAvailable() before calling any" +
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 7b11ac7..eeda6c6 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -30,7 +30,9 @@
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
+import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.compose.CommunalHub
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.people.ui.compose.PeopleScreen
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -96,8 +98,17 @@
override fun createCommunalView(
context: Context,
+ viewModel: CommunalViewModel,
): View {
- return ComposeView(context).apply { setContent { PlatformTheme { CommunalHub() } } }
+ return ComposeView(context).apply {
+ setContent { PlatformTheme { CommunalHub(viewModel = viewModel) } }
+ }
+ }
+
+ override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
+ return ComposeView(context).apply {
+ setContent { PlatformTheme { CommunalContainer(viewModel = viewModel) } }
+ }
}
// TODO(b/298525212): remove once Compose exposes window inset bounds.
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index e4426fe..16c2437 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -31,8 +31,10 @@
],
static_libs: [
+ "CommunalLayoutLib",
"SystemUI-core",
"PlatformComposeCore",
+ "PlatformComposeSceneTransitionLayout",
"androidx.compose.runtime_runtime",
"androidx.compose.animation_animation-graphics",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
new file mode 100644
index 0000000..46d418a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -0,0 +1,123 @@
+package com.android.systemui.communal.ui.compose
+
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+
+object Scenes {
+ val Blank = SceneKey(name = "blank")
+ val Communal = SceneKey(name = "communal")
+}
+
+object Communal {
+ object Elements {
+ val Content = ElementKey("CommunalContent")
+ }
+}
+
+val sceneTransitions = transitions {
+ from(Scenes.Blank, to = Scenes.Communal) {
+ spec = tween(durationMillis = 500)
+
+ translate(Communal.Elements.Content, Edge.Right)
+ fade(Communal.Elements.Content)
+ }
+}
+
+/**
+ * View containing a [SceneTransitionLayout] that shows the communal UI and handles transitions.
+ *
+ * This is a temporary container to allow the communal UI to use [SceneTransitionLayout] for gesture
+ * handling and transitions before the full Flexiglass layout is ready.
+ */
+@Composable
+fun CommunalContainer(modifier: Modifier = Modifier, viewModel: CommunalViewModel) {
+ val (currentScene, setCurrentScene) = remember { mutableStateOf(Scenes.Blank) }
+
+ // Failsafe to hide the whole SceneTransitionLayout in case of bugginess.
+ var showSceneTransitionLayout by remember { mutableStateOf(true) }
+ if (!showSceneTransitionLayout) {
+ return
+ }
+
+ SceneTransitionLayout(
+ modifier = modifier.fillMaxSize(),
+ currentScene = currentScene,
+ onChangeScene = setCurrentScene,
+ transitions = sceneTransitions,
+ ) {
+ scene(Scenes.Blank, userActions = mapOf(Swipe.Left to Scenes.Communal)) {
+ BlankScene { showSceneTransitionLayout = false }
+ }
+
+ scene(
+ Scenes.Communal,
+ userActions = mapOf(Swipe.Right to Scenes.Blank),
+ ) {
+ CommunalScene(viewModel, modifier = modifier)
+ }
+ }
+}
+
+/**
+ * Blank scene that shows over keyguard/dream. This scene will eventually show nothing at all and is
+ * only used to allow for transitions to the communal scene.
+ */
+@Composable
+private fun BlankScene(
+ modifier: Modifier = Modifier,
+ hideSceneTransitionLayout: () -> Unit,
+) {
+ Box(modifier.fillMaxSize()) {
+ Column(
+ Modifier.fillMaxHeight()
+ .width(100.dp)
+ .align(Alignment.CenterEnd)
+ .background(Color(0x55e9f2eb)),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text("Default scene")
+
+ IconButton(onClick = hideSceneTransitionLayout) {
+ Icon(Icons.Filled.Close, contentDescription = "Close button")
+ }
+ }
+ }
+}
+
+/** Scene containing the glanceable hub UI. */
+@Composable
+private fun SceneScope.CommunalScene(
+ viewModel: CommunalViewModel,
+ modifier: Modifier = Modifier,
+) {
+ Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 4d2978d..b8fb264 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -1,22 +1,101 @@
package com.android.systemui.communal.ui.compose
+import android.appwidget.AppWidgetHostView
+import android.os.Bundle
+import android.util.SizeF
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Text
+import androidx.compose.material3.Card
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.integerResource
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.communal.layout.ui.compose.CommunalGridLayout
+import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
+import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.model.CommunalContentUiModel
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.res.R
@Composable
-fun CommunalHub(modifier: Modifier = Modifier) {
+fun CommunalHub(
+ modifier: Modifier = Modifier,
+ viewModel: CommunalViewModel,
+) {
+ val showTutorial by viewModel.showTutorialContent.collectAsState(initial = false)
+ val widgetContent by viewModel.widgetContent.collectAsState(initial = emptyList())
Box(
modifier = modifier.fillMaxSize().background(Color.White),
) {
- Text(
- modifier = Modifier.align(Alignment.Center),
- text = "Hello Communal!",
+ CommunalGridLayout(
+ modifier = Modifier.align(Alignment.CenterStart),
+ layoutConfig =
+ CommunalGridLayoutConfig(
+ gridColumnSize = dimensionResource(R.dimen.communal_grid_column_size),
+ gridGutter = dimensionResource(R.dimen.communal_grid_gutter_size),
+ gridHeight = dimensionResource(R.dimen.communal_grid_height),
+ gridColumnsPerCard = integerResource(R.integer.communal_grid_columns_per_card),
+ ),
+ communalCards = if (showTutorial) tutorialContent else widgetContent.map(::contentCard),
)
}
}
+
+private val tutorialContent =
+ listOf(
+ tutorialCard(CommunalGridLayoutCard.Size.FULL),
+ tutorialCard(CommunalGridLayoutCard.Size.THIRD),
+ tutorialCard(CommunalGridLayoutCard.Size.THIRD),
+ tutorialCard(CommunalGridLayoutCard.Size.THIRD),
+ tutorialCard(CommunalGridLayoutCard.Size.HALF),
+ tutorialCard(CommunalGridLayoutCard.Size.HALF),
+ tutorialCard(CommunalGridLayoutCard.Size.HALF),
+ tutorialCard(CommunalGridLayoutCard.Size.HALF),
+ )
+
+private fun tutorialCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard {
+ return object : CommunalGridLayoutCard() {
+ override val supportedSizes = listOf(size)
+
+ @Composable
+ override fun Content(modifier: Modifier, size: SizeF) {
+ Card(modifier = modifier, content = {})
+ }
+ }
+}
+
+private fun contentCard(model: CommunalContentUiModel): CommunalGridLayoutCard {
+ return object : CommunalGridLayoutCard() {
+ override val supportedSizes = listOf(convertToCardSize(model.size))
+ override val priority = model.priority
+
+ @Composable
+ override fun Content(modifier: Modifier, size: SizeF) {
+ AndroidView(
+ modifier = modifier,
+ factory = {
+ model.view.apply {
+ if (this is AppWidgetHostView) {
+ updateAppWidgetSize(Bundle(), listOf(size))
+ }
+ }
+ },
+ )
+ }
+ }
+}
+
+private fun convertToCardSize(size: CommunalContentSize): CommunalGridLayoutCard.Size {
+ return when (size) {
+ CommunalContentSize.FULL -> CommunalGridLayoutCard.Size.FULL
+ CommunalContentSize.HALF -> CommunalGridLayoutCard.Size.HALF
+ CommunalContentSize.THIRD -> CommunalGridLayoutCard.Size.THIRD
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index d1c12ac..f3bef7b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -19,6 +19,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
@@ -32,7 +33,11 @@
/** The communal scene shows glanceable hub when the device is locked and docked. */
@SysUISingleton
-class CommunalScene @Inject constructor() : ComposableScene {
+class CommunalScene
+@Inject
+constructor(
+ private val viewModel: CommunalViewModel,
+) : ComposableScene {
override val key = SceneKey.Communal
override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
@@ -45,6 +50,6 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- CommunalHub(modifier)
+ CommunalHub(modifier, viewModel)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 591fa76..4bbb78b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -371,7 +371,8 @@
val iconContainer = StatusIconContainer(context, null)
val iconManager = createTintedIconManager(iconContainer, StatusBarLocation.QS)
iconManager.setTint(
- Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
+ Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary),
+ Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimaryInverse),
)
statusBarIconController.addIconGroup(iconManager)
diff --git a/packages/SystemUI/compose/scene/Android.bp b/packages/SystemUI/compose/scene/Android.bp
new file mode 100644
index 0000000..050d1d5
--- /dev/null
+++ b/packages/SystemUI/compose/scene/Android.bp
@@ -0,0 +1,39 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+ name: "PlatformComposeSceneTransitionLayout",
+ manifest: "AndroidManifest.xml",
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.material3_material3",
+ ],
+
+ kotlincflags: ["-Xjvm-default=all"],
+ use_resource_processor: true,
+}
diff --git a/packages/SystemUI/compose/scene/AndroidManifest.xml b/packages/SystemUI/compose/scene/AndroidManifest.xml
new file mode 100644
index 0000000..81131bb
--- /dev/null
+++ b/packages/SystemUI/compose/scene/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.compose.animation.scene">
+
+
+</manifest>
diff --git a/packages/SystemUI/compose/scene/OWNERS b/packages/SystemUI/compose/scene/OWNERS
new file mode 100644
index 0000000..33a59c2
--- /dev/null
+++ b/packages/SystemUI/compose/scene/OWNERS
@@ -0,0 +1,13 @@
+set noparent
+
+# Bug component: 1184816
+
+jdemeulenaere@google.com
+omarmt@google.com
+
+# SysUI Dr No's.
+# Don't send reviews here.
+dsandler@android.com
+cinek@google.com
+juliacr@google.com
+pixel@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/TEST_MAPPING b/packages/SystemUI/compose/scene/TEST_MAPPING
new file mode 100644
index 0000000..f644a23
--- /dev/null
+++ b/packages/SystemUI/compose/scene/TEST_MAPPING
@@ -0,0 +1,48 @@
+{
+ "presubmit": [
+ {
+ "name": "PlatformComposeSceneTransitionLayoutTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "PlatformComposeCoreTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "SystemUIComposeFeaturesTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "SystemUIComposeGalleryTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
similarity index 71%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 566967f..041fc48 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -17,12 +17,10 @@
package com.android.compose.animation.scene
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.DisposableEffectResult
-import androidx.compose.runtime.DisposableEffectScope
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.unit.Dp
@@ -45,6 +43,20 @@
}
/**
+ * Animate a shared Int value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedIntAsState(
+ value: Int,
+ debugName: String,
+ canOverflow: Boolean = true,
+): State<Int> {
+ return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+}
+
+/**
* Animate a shared Float value.
*
* @see SceneScope.animateSharedValueAsState
@@ -60,6 +72,20 @@
}
/**
+ * Animate a shared Float value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedFloatAsState(
+ value: Float,
+ debugName: String,
+ canOverflow: Boolean = true,
+): State<Float> {
+ return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+}
+
+/**
* Animate a shared Dp value.
*
* @see SceneScope.animateSharedValueAsState
@@ -75,6 +101,20 @@
}
/**
+ * Animate a shared Dp value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedDpAsState(
+ value: Dp,
+ debugName: String,
+ canOverflow: Boolean = true,
+): State<Dp> {
+ return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+}
+
+/**
* Animate a shared Color value.
*
* @see SceneScope.animateSharedValueAsState
@@ -88,6 +128,19 @@
return animateSharedValueAsState(value, key, element, ::lerp, canOverflow = false)
}
+/**
+ * Animate a shared Color value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedColorAsState(
+ value: Color,
+ debugName: String,
+): State<Color> {
+ return animateSharedValueAsState(value, debugName, ::lerp, canOverflow = false)
+}
+
@Composable
internal fun <T> animateSharedValueAsState(
layoutImpl: SceneTransitionLayoutImpl,
@@ -98,33 +151,22 @@
lerp: (T, T, Float) -> T,
canOverflow: Boolean,
): State<T> {
- val sharedValue = remember(key) { Element.SharedValue(key, value) }
+ val sharedValue =
+ Snapshot.withoutReadObservation {
+ element.sceneValues.getValue(scene.key).sharedValues.getOrPut(key) {
+ Element.SharedValue(key, value)
+ } as Element.SharedValue<T>
+ }
+
if (value != sharedValue.value) {
sharedValue.value = value
}
- DisposableEffect(element, scene, sharedValue) {
- addSharedValueToElement(element, scene, sharedValue)
- }
-
return remember(layoutImpl, element, sharedValue, lerp, canOverflow) {
derivedStateOf { computeValue(layoutImpl, element, sharedValue, lerp, canOverflow) }
}
}
-private fun <T> DisposableEffectScope.addSharedValueToElement(
- element: Element,
- scene: Scene,
- sharedValue: Element.SharedValue<T>,
-): DisposableEffectResult {
- val sceneValues =
- element.sceneValues[scene.key] ?: error("Element $element is not present in $scene")
- val sharedValues = sceneValues.sharedValues
-
- sharedValues[sharedValue.key] = sharedValue
- return onDispose { sharedValues.remove(sharedValue.key) }
-}
-
private fun <T> computeValue(
layoutImpl: SceneTransitionLayoutImpl,
element: Element,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
similarity index 92%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index ce96bbf..abc62c4 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -43,6 +43,7 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.round
import com.android.compose.animation.scene.transformation.PropertyTransformation
+import com.android.compose.animation.scene.transformation.SharedElementTransformation
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.util.lerp
@@ -196,29 +197,44 @@
state.fromScene == state.toScene ||
!layoutImpl.isTransitionReady(state) ||
state.fromScene !in element.sceneValues ||
- state.toScene !in element.sceneValues ||
- !isSharedElementEnabled(layoutImpl, state, element.key)
+ state.toScene !in element.sceneValues
) {
return true
}
- val otherScene =
- layoutImpl.scenes.getValue(
- if (scene.key == state.fromScene) {
- state.toScene
- } else {
- state.fromScene
- }
- )
-
- // When the element is shared, draw the one in the highest scene unless it is a background, i.e.
- // it is usually drawn below everything else.
- val isHighestScene = scene.zIndex > otherScene.zIndex
- return if (element.key.isBackground) {
- !isHighestScene
- } else {
- isHighestScene
+ val sharedTransformation = sharedElementTransformation(layoutImpl, state, element.key)
+ if (sharedTransformation?.enabled == false) {
+ return true
}
+
+ return shouldDrawOrComposeSharedElement(
+ layoutImpl,
+ state,
+ scene.key,
+ element.key,
+ sharedTransformation,
+ )
+}
+
+internal fun shouldDrawOrComposeSharedElement(
+ layoutImpl: SceneTransitionLayoutImpl,
+ transition: TransitionState.Transition,
+ scene: SceneKey,
+ element: ElementKey,
+ sharedTransformation: SharedElementTransformation?
+): Boolean {
+ val scenePicker = sharedTransformation?.scenePicker ?: DefaultSharedElementScenePicker
+ val fromScene = transition.fromScene
+ val toScene = transition.toScene
+
+ return scenePicker.sceneDuringTransition(
+ element = element,
+ fromScene = fromScene,
+ toScene = toScene,
+ progress = transition::progress,
+ fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex,
+ toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex,
+ ) == scene
}
private fun isSharedElementEnabled(
@@ -226,6 +242,14 @@
transition: TransitionState.Transition,
element: ElementKey,
): Boolean {
+ return sharedElementTransformation(layoutImpl, transition, element)?.enabled ?: true
+}
+
+internal fun sharedElementTransformation(
+ layoutImpl: SceneTransitionLayoutImpl,
+ transition: TransitionState.Transition,
+ element: ElementKey,
+): SharedElementTransformation? {
val spec = layoutImpl.transitions.transitionSpec(transition.fromScene, transition.toScene)
val sharedInFromScene = spec.transformations(element, transition.fromScene).shared
val sharedInToScene = spec.transformations(element, transition.toScene).shared
@@ -238,7 +262,7 @@
)
}
- return sharedInFromScene?.enabled ?: true
+ return sharedInFromScene
}
/**
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
similarity index 79%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 11bbf2a..fa385d0 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -21,6 +21,9 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.ui.Modifier
@@ -36,8 +39,6 @@
private const val TAG = "MovableElement"
-private object MovableElementScopeImpl : MovableElementScope
-
@Composable
internal fun MovableElement(
layoutImpl: SceneTransitionLayoutImpl,
@@ -51,13 +52,26 @@
// every time an element is added/removed from SceneTransitionLayoutImpl.elements, so we
// disable read observation during the look-up in that map.
val element = Snapshot.withoutReadObservation { layoutImpl.elements.getValue(key) }
+ val movableElementScope =
+ remember(layoutImpl, element, scene) {
+ MovableElementScopeImpl(layoutImpl, element, scene)
+ }
// The [Picture] to which we save the last drawing commands of this element. This is
// necessary because the content of this element might not be composed in this scene, in
// which case we still need to draw it.
val picture = remember { Picture() }
- if (shouldComposeMovableElement(layoutImpl, scene.key, element)) {
+ // Whether we should compose the movable element here. The scene picker logic to know in
+ // which scene we should compose/draw a movable element might depend on the current
+ // transition progress, so we put this in a derivedStateOf to prevent many recompositions
+ // during the transition.
+ val shouldComposeMovableElement by
+ remember(layoutImpl, scene.key, element) {
+ derivedStateOf { shouldComposeMovableElement(layoutImpl, scene.key, element) }
+ }
+
+ if (shouldComposeMovableElement) {
Box(
Modifier.drawWithCache {
val width = size.width.toInt()
@@ -77,7 +91,7 @@
}
}
) {
- element.movableContent { MovableElementScopeImpl.content() }
+ element.movableContent { movableElementScope.content() }
}
} else {
// If we are not composed, we draw the previous drawing commands at the same size as the
@@ -169,12 +183,28 @@
return scene == fromScene
}
- // If we are ready in both scenes, then compose in the scene that has the highest zIndex (unless
- // it is a background) given that this is the one that is going to be drawn.
- val isHighestScene = layoutImpl.scene(scene).zIndex > layoutImpl.scene(otherScene).zIndex
- return if (element.key.isBackground) {
- !isHighestScene
- } else {
- isHighestScene
+ return shouldDrawOrComposeSharedElement(
+ layoutImpl,
+ transitionState,
+ scene,
+ element.key,
+ sharedElementTransformation(layoutImpl, transitionState, element.key),
+ )
+}
+
+private class MovableElementScopeImpl(
+ private val layoutImpl: SceneTransitionLayoutImpl,
+ private val element: Element,
+ private val scene: Scene,
+) : MovableElementScope {
+ @Composable
+ override fun <T> animateSharedValueAsState(
+ value: T,
+ debugName: String,
+ lerp: (start: T, stop: T, fraction: Float) -> T,
+ canOverflow: Boolean,
+ ): State<T> {
+ val key = remember { ValueKey(debugName) }
+ return animateSharedValueAsState(layoutImpl, scene, element, key, value, lerp, canOverflow)
}
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
similarity index 96%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 4283c0e..74e66d2 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -160,7 +160,16 @@
// TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey
// arguments to allow sharing values inside a movable element.
-@ElementDsl interface MovableElementScope
+@ElementDsl
+interface MovableElementScope {
+ @Composable
+ fun <T> animateSharedValueAsState(
+ value: T,
+ debugName: String,
+ lerp: (start: T, stop: T, fraction: Float) -> T,
+ canOverflow: Boolean,
+ ): State<T>
+}
/** An action performed by the user. */
sealed interface UserAction
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
similarity index 84%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 4966977..7b7ddfa 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -120,8 +120,14 @@
*
* @param enabled whether the matched element(s) should actually be shared in this transition.
* Defaults to true.
+ * @param scenePicker the [SharedElementScenePicker] to use when deciding in which scene we
+ * should draw or compose this shared element.
*/
- fun sharedElement(matcher: ElementMatcher, enabled: Boolean = true)
+ fun sharedElement(
+ matcher: ElementMatcher,
+ enabled: Boolean = true,
+ scenePicker: SharedElementScenePicker = DefaultSharedElementScenePicker,
+ )
/**
* Punch a hole in the element(s) matching [matcher] that has the same bounds as [bounds] and
@@ -144,6 +150,44 @@
fun reversed(builder: TransitionBuilder.() -> Unit)
}
+interface SharedElementScenePicker {
+ /**
+ * Return the scene in which [element] should be drawn (when using `Modifier.element(key)`) or
+ * composed (when using `MovableElement(key)`) during the transition from [fromScene] to
+ * [toScene].
+ */
+ fun sceneDuringTransition(
+ element: ElementKey,
+ fromScene: SceneKey,
+ toScene: SceneKey,
+ progress: () -> Float,
+ fromSceneZIndex: Float,
+ toSceneZIndex: Float,
+ ): SceneKey
+}
+
+object DefaultSharedElementScenePicker : SharedElementScenePicker {
+ override fun sceneDuringTransition(
+ element: ElementKey,
+ fromScene: SceneKey,
+ toScene: SceneKey,
+ progress: () -> Float,
+ fromSceneZIndex: Float,
+ toSceneZIndex: Float
+ ): SceneKey {
+ // By default shared elements are drawn in the highest scene possible, unless it is a
+ // background.
+ return if (
+ (fromSceneZIndex > toSceneZIndex && !element.isBackground) ||
+ (fromSceneZIndex < toSceneZIndex && element.isBackground)
+ ) {
+ fromScene
+ } else {
+ toScene
+ }
+ }
+}
+
@TransitionDsl
interface PropertyTransformationBuilder {
/**
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
similarity index 97%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index f1c2717..d2bfd91 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -111,8 +111,12 @@
range = null
}
- override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) {
- transformations.add(SharedElementTransformation(matcher, enabled))
+ override fun sharedElement(
+ matcher: ElementMatcher,
+ enabled: Boolean,
+ scenePicker: SharedElementScenePicker,
+ ) {
+ transformations.add(SharedElementTransformation(matcher, enabled, scenePicker))
}
override fun timestampRange(
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
similarity index 97%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index 2ef8d56..0db8469 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -21,6 +21,7 @@
import com.android.compose.animation.scene.ElementMatcher
import com.android.compose.animation.scene.Scene
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.SharedElementScenePicker
import com.android.compose.animation.scene.TransitionState
/** A transformation applied to one or more elements during a transition. */
@@ -48,6 +49,7 @@
internal class SharedElementTransformation(
override val matcher: ElementMatcher,
internal val enabled: Boolean,
+ internal val scenePicker: SharedElementScenePicker,
) : Transformation
/** A transformation that is applied on the element during the whole transition. */
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt
similarity index 97%
rename from packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt
index 27f0948..790665a 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt
@@ -116,9 +116,14 @@
if (sizeCache.rowHeights.size != rows) {
sizeCache.rowHeights = IntArray(rows) { 0 }
+ } else {
+ repeat(rows) { i -> sizeCache.rowHeights[i] = 0 }
}
+
if (sizeCache.columnWidths.size != columns) {
sizeCache.columnWidths = IntArray(columns) { 0 }
+ } else {
+ repeat(columns) { i -> sizeCache.columnWidths[i] = 0 }
}
val totalHorizontalSpacingBetweenChildren =
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
similarity index 100%
rename from packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
rename to packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
diff --git a/packages/SystemUI/compose/scene/tests/Android.bp b/packages/SystemUI/compose/scene/tests/Android.bp
new file mode 100644
index 0000000..b53fae2
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/Android.bp
@@ -0,0 +1,50 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_test {
+ name: "PlatformComposeSceneTransitionLayoutTests",
+ manifest: "AndroidManifest.xml",
+ test_suites: ["device-tests"],
+ sdk_version: "current",
+ certificate: "platform",
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "PlatformComposeSceneTransitionLayout",
+
+ "androidx.test.runner",
+ "androidx.test.ext.junit",
+
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.ui_ui-test-junit4",
+ "androidx.compose.ui_ui-test-manifest",
+
+ "truth",
+ ],
+
+ kotlincflags: ["-Xjvm-default=all"],
+ use_resource_processor: true,
+}
diff --git a/packages/SystemUI/compose/scene/tests/AndroidManifest.xml b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml
new file mode 100644
index 0000000..1a9172e
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.compose.animation.scene.tests" >
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.compose.animation.scene.tests"
+ android:label="Tests for SceneTransitionLayout"/>
+
+</manifest>
\ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
new file mode 100644
index 0000000..7b7695e
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -0,0 +1,218 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.lerp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.ui.util.lerp
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AnimatedSharedAsStateTest {
+ @get:Rule val rule = createComposeRule()
+
+ private data class Values(
+ val int: Int,
+ val float: Float,
+ val dp: Dp,
+ val color: Color,
+ )
+
+ private fun lerp(start: Values, stop: Values, fraction: Float): Values {
+ return Values(
+ int = lerp(start.int, stop.int, fraction),
+ float = lerp(start.float, stop.float, fraction),
+ dp = lerp(start.dp, stop.dp, fraction),
+ color = lerp(start.color, stop.color, fraction),
+ )
+ }
+
+ @Composable
+ private fun SceneScope.Foo(
+ targetValues: Values,
+ onCurrentValueChanged: (Values) -> Unit,
+ ) {
+ val key = TestElements.Foo
+ Box(Modifier.element(key)) {
+ val int by animateSharedIntAsState(targetValues.int, TestValues.Value1, key)
+ val float by animateSharedFloatAsState(targetValues.float, TestValues.Value2, key)
+ val dp by animateSharedDpAsState(targetValues.dp, TestValues.Value3, key)
+ val color by animateSharedColorAsState(targetValues.color, TestValues.Value4, key)
+
+ // Make sure we read the values during composition, so that we recompose and call
+ // onCurrentValueChanged() with the latest values.
+ val currentValues = Values(int, float, dp, color)
+ SideEffect { onCurrentValueChanged(currentValues) }
+ }
+ }
+
+ @Composable
+ private fun SceneScope.MovableFoo(
+ targetValues: Values,
+ onCurrentValueChanged: (Values) -> Unit,
+ ) {
+ val key = TestElements.Foo
+ MovableElement(key = key, Modifier) {
+ val int by
+ animateSharedIntAsState(targetValues.int, debugName = TestValues.Value1.debugName)
+ val float by
+ animateSharedFloatAsState(
+ targetValues.float,
+ debugName = TestValues.Value2.debugName
+ )
+ val dp by
+ animateSharedDpAsState(targetValues.dp, debugName = TestValues.Value3.debugName)
+ val color by
+ animateSharedColorAsState(
+ targetValues.color,
+ debugName = TestValues.Value4.debugName
+ )
+
+ // Make sure we read the values during composition, so that we recompose and call
+ // onCurrentValueChanged() with the latest values.
+ val currentValues = Values(int, float, dp, color)
+ SideEffect { onCurrentValueChanged(currentValues) }
+ }
+ }
+
+ @Test
+ fun animateSharedValues() {
+ val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red)
+ val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue)
+
+ var lastValueInFrom = fromValues
+ var lastValueInTo = toValues
+
+ rule.testTransition(
+ fromSceneContent = {
+ Foo(targetValues = fromValues, onCurrentValueChanged = { lastValueInFrom = it })
+ },
+ toSceneContent = {
+ Foo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it })
+ },
+ transition = {
+ // The transition lasts 64ms = 4 frames.
+ spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+ },
+ fromScene = TestScenes.SceneA,
+ toScene = TestScenes.SceneB,
+ ) {
+ before {
+ assertThat(lastValueInFrom).isEqualTo(fromValues)
+
+ // to was not composed yet, so lastValueInTo was not set yet.
+ assertThat(lastValueInTo).isEqualTo(toValues)
+ }
+
+ at(16) {
+ // Given that we use Modifier.element() here, animateSharedXAsState is composed in
+ // both scenes and values should be interpolated with the transition fraction.
+ val expectedValues = lerp(fromValues, toValues, fraction = 0.25f)
+ assertThat(lastValueInFrom).isEqualTo(expectedValues)
+ assertThat(lastValueInTo).isEqualTo(expectedValues)
+ }
+
+ at(32) {
+ val expectedValues = lerp(fromValues, toValues, fraction = 0.5f)
+ assertThat(lastValueInFrom).isEqualTo(expectedValues)
+ assertThat(lastValueInTo).isEqualTo(expectedValues)
+ }
+
+ at(48) {
+ val expectedValues = lerp(fromValues, toValues, fraction = 0.75f)
+ assertThat(lastValueInFrom).isEqualTo(expectedValues)
+ assertThat(lastValueInTo).isEqualTo(expectedValues)
+ }
+
+ after {
+ assertThat(lastValueInFrom).isEqualTo(toValues)
+ assertThat(lastValueInTo).isEqualTo(toValues)
+ }
+ }
+ }
+
+ @Test
+ fun movableAnimateSharedValues() {
+ val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red)
+ val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue)
+
+ var lastValueInFrom = fromValues
+ var lastValueInTo = toValues
+
+ rule.testTransition(
+ fromSceneContent = {
+ MovableFoo(
+ targetValues = fromValues,
+ onCurrentValueChanged = { lastValueInFrom = it }
+ )
+ },
+ toSceneContent = {
+ MovableFoo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it })
+ },
+ transition = {
+ // The transition lasts 64ms = 4 frames.
+ spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+ },
+ fromScene = TestScenes.SceneA,
+ toScene = TestScenes.SceneB,
+ ) {
+ before {
+ assertThat(lastValueInFrom).isEqualTo(fromValues)
+
+ // to was not composed yet, so lastValueInTo was not set yet.
+ assertThat(lastValueInTo).isEqualTo(toValues)
+ }
+
+ at(16) {
+ // Given that we use MovableElement here, animateSharedXAsState is composed only
+ // once, in the highest scene (in this case, in toScene).
+ assertThat(lastValueInFrom).isEqualTo(fromValues)
+ assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.25f))
+ }
+
+ at(32) {
+ assertThat(lastValueInFrom).isEqualTo(fromValues)
+ assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.5f))
+ }
+
+ at(48) {
+ assertThat(lastValueInFrom).isEqualTo(fromValues)
+ assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.75f))
+ }
+
+ after {
+ assertThat(lastValueInFrom).isEqualTo(fromValues)
+ assertThat(lastValueInTo).isEqualTo(toValues)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
similarity index 75%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 4204cd5..83af630 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -144,7 +144,36 @@
rule.testTransition(
fromSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(50.dp)) },
toSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(100.dp)) },
- transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) },
+ transition = {
+ spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+ sharedElement(
+ TestElements.Foo,
+ scenePicker =
+ object : SharedElementScenePicker {
+ override fun sceneDuringTransition(
+ element: ElementKey,
+ fromScene: SceneKey,
+ toScene: SceneKey,
+ progress: () -> Float,
+ fromSceneZIndex: Float,
+ toSceneZIndex: Float
+ ): SceneKey {
+ assertThat(fromScene).isEqualTo(TestScenes.SceneA)
+ assertThat(toScene).isEqualTo(TestScenes.SceneB)
+ assertThat(fromSceneZIndex).isEqualTo(0)
+ assertThat(toSceneZIndex).isEqualTo(1)
+
+ // Compose Foo in Scene A if progress < 0.65f, otherwise compose it
+ // in Scene B.
+ return if (progress() < 0.65f) {
+ TestScenes.SceneA
+ } else {
+ TestScenes.SceneB
+ }
+ }
+ }
+ )
+ },
fromScene = TestScenes.SceneA,
toScene = TestScenes.SceneB,
) {
@@ -170,9 +199,12 @@
at(32) {
// During the transition, there is a single counter that is moved, with the current
- // value.
+ // value. Given that progress = 0.5f, it is currently composed in SceneA.
rule
- .onNode(hasText("count: 3"))
+ .onNode(
+ hasText("count: 3") and
+ hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA))
+ )
.assertIsDisplayed()
.assertSizeIsEqualTo(75.dp, 75.dp)
@@ -186,6 +218,26 @@
.isEqualTo(1)
}
+ at(48) {
+ // During the transition, there is a single counter that is moved, with the current
+ // value. Given that progress = 0.75f, it is currently composed in SceneB.
+ rule
+ .onNode(
+ hasText("count: 3") and
+ hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB))
+ )
+ .assertIsDisplayed()
+
+ // There are no other counters.
+ assertThat(
+ rule
+ .onAllNodesWithText("count: ", substring = true)
+ .fetchSemanticsNodes()
+ .size
+ )
+ .isEqualTo(1)
+ }
+
after {
// At the end of the transition, the counter still has the current value.
rule
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt
similarity index 94%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt
index 8357262..b4c393e 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt
@@ -37,6 +37,9 @@
/** Value keys that can be reused by tests. */
object TestValues {
val Value1 = ValueKey("Value1")
+ val Value2 = ValueKey("Value2")
+ val Value3 = ValueKey("Value3")
+ val Value4 = ValueKey("Value4")
}
// We use a transition duration of 480ms here because it is a multiple of 16, the time of a frame in
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
similarity index 99%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
index 2af3638..e94eff3 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
@@ -32,7 +32,6 @@
import com.android.compose.animation.scene.TestScenes
import com.android.compose.animation.scene.inScene
import com.android.compose.animation.scene.testTransition
-import com.android.compose.modifiers.size
import com.android.compose.test.assertSizeIsEqualTo
import com.android.compose.test.onEach
import org.junit.Rule
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
similarity index 100%
rename from packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
rename to packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
index a5e5aaa..a9d2ee3 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
@@ -71,7 +71,11 @@
*/
void applyDark(DarkReceiver object);
+ /** The default tint (applicable for dark backgrounds) is white */
int DEFAULT_ICON_TINT = Color.WHITE;
+ /** To support an icon which wants to create contrast, the default tint is black-on-white. */
+ int DEFAULT_INVERSE_ICON_TINT = Color.BLACK;
+
Rect sTmpRect = new Rect();
int[] sTmpInt2 = new int[2];
@@ -88,6 +92,18 @@
}
/**
+ * @return the tint to apply to a foreground, given that the background is tinted
+ * per {@link #getTint}
+ */
+ static int getInverseTint(Collection<Rect> tintAreas, View view, int inverseColor) {
+ if (isInAreas(tintAreas, view)) {
+ return inverseColor;
+ } else {
+ return DEFAULT_INVERSE_ICON_TINT;
+ }
+ }
+
+ /**
* @return true if more than half of the view area are in any of the given
* areas, false otherwise
*/
@@ -129,7 +145,40 @@
*/
@ProvidesInterface(version = DarkReceiver.VERSION)
interface DarkReceiver {
- int VERSION = 2;
+ int VERSION = 3;
+
+ /**
+ * @param areas list of regions on screen where the tint applies
+ * @param darkIntensity float representing the level of tint. In the range [0,1]
+ * @param tint the tint applicable as a foreground contrast to the dark regions. This value
+ * is interpolated between a default light and dark tone, and is therefore
+ * usable as-is, as long as the view is in one of the areas defined in
+ * {@code areas}.
+ *
+ * @see DarkIconDispatcher#isInArea(Rect, View) for utilizing {@code areas}
+ *
+ * Note: only one of {@link #onDarkChanged(ArrayList, float, int)} or
+ * {@link #onDarkChangedWithContrast(ArrayList, int, int)} need to be implemented, as both
+ * will be called in the same circumstances.
+ */
void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint);
+
+ /**
+ * New version of onDarkChanged, which describes a tint plus an optional contrastTint
+ * that can be used if the tint is applied to the background of an icon.
+ *
+ * We use the 2 here to avoid the case where an existing override of onDarkChanged
+ * might pass in parameters as bare numbers (e.g. 0 instead of 0f) which might get
+ * mistakenly cast to (int) and therefore trigger this method.
+ *
+ * @param areas list of areas where dark tint applies
+ * @param tint int describing the tint color to use
+ * @param contrastTint if desired, a contrasting color that can be used for a foreground
+ *
+ * Note: only one of {@link #onDarkChanged(ArrayList, float, int)} or
+ * {@link #onDarkChangedWithContrast(ArrayList, int, int)} need to be implemented, as both
+ * will be called in the same circumstances.
+ */
+ default void onDarkChangedWithContrast(ArrayList<Rect> areas, int tint, int contrastTint) {}
}
}
diff --git a/packages/SystemUI/res-keyguard/drawable/progress_bar.xml b/packages/SystemUI/res-keyguard/drawable/progress_bar.xml
new file mode 100644
index 0000000..910a74a
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/progress_bar.xml
@@ -0,0 +1,45 @@
+<?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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:paddingMode="stack">
+ <item
+ android:id="@android:id/background"
+ android:gravity="center_vertical|fill_horizontal">
+ <shape
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:shape="rectangle">
+ <corners android:radius="30dp" />
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest" />
+ </shape>
+ </item>
+ <item
+ android:id="@android:id/progress"
+ android:gravity="center_vertical|fill_horizontal">
+ <clip>
+ <shape
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:shape="rectangle">
+ <corners android:radius="30dp" />
+ <solid android:color="?androidprv:attr/textColorPrimary" />
+ </shape>
+ </clip>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml
new file mode 100644
index 0000000..183f0e5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml
@@ -0,0 +1,32 @@
+<?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.
+ ~
+ -->
+
+<LinearLayout android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:orientation="vertical"
+ android:layoutDirection="ltr"
+ android:gravity="center"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <ProgressBar
+ android:id="@+id/side_fps_progress_bar"
+ android:layout_width="55dp"
+ android:layout_height="10dp"
+ android:indeterminateOnly="false"
+ android:min="0"
+ android:max="100"
+ android:progressDrawable="@drawable/progress_bar" />
+</LinearLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
index a1e2dc3..a8017f6 100644
--- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -52,15 +52,22 @@
android:visibility="gone"
/>
</FrameLayout>
- <ImageView
- android:id="@+id/mobile_type"
- android:layout_height="@dimen/status_bar_mobile_type_size"
+ <FrameLayout
+ android:id="@+id/mobile_type_container"
+ android:layout_height="@dimen/status_bar_mobile_container_height"
android:layout_width="wrap_content"
- android:layout_gravity="center_vertical"
- android:adjustViewBounds="true"
- android:paddingStart="2.5sp"
- android:paddingEnd="1sp"
- android:visibility="gone" />
+ android:layout_marginStart="2.5sp"
+ android:layout_marginEnd="1sp"
+ android:visibility="gone"
+ >
+ <ImageView
+ android:id="@+id/mobile_type"
+ android:layout_height="@dimen/status_bar_mobile_type_size"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:adjustViewBounds="true"
+ />
+ </FrameLayout>
<Space
android:id="@+id/mobile_roaming_space"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/drawable/mobile_network_type_background.xml b/packages/SystemUI/res/drawable/mobile_network_type_background.xml
new file mode 100644
index 0000000..db25c89
--- /dev/null
+++ b/packages/SystemUI/res/drawable/mobile_network_type_background.xml
@@ -0,0 +1,30 @@
+<?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.
+ ~
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ >
+ <corners
+ android:topLeftRadius="0dp"
+ android:topRightRadius="@dimen/status_bar_mobile_container_corner_radius"
+ android:bottomRightRadius="0dp"
+ android:bottomLeftRadius="@dimen/status_bar_mobile_container_corner_radius"/>
+ <solid android:color="#FFF" />
+ <padding
+ android:left="2sp"
+ android:right="2sp"/>
+</shape>
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
index a51c55e..8cfcb68 100644
--- a/packages/SystemUI/res/layout/connected_display_dialog.xml
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -15,8 +15,9 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/cd_bottom_sheet"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingHorizontal="@dimen/dialog_side_padding"
@@ -26,11 +27,14 @@
<ImageView
android:id="@+id/connected_display_dialog_icon"
- android:layout_width="@dimen/screenrecord_logo_size"
- android:layout_height="@dimen/screenrecord_logo_size"
+ android:layout_width="@dimen/connected_display_dialog_logo_size"
+ android:layout_height="@dimen/connected_display_dialog_logo_size"
+ android:background="@drawable/circular_background"
+ android:backgroundTint="?androidprv:attr/materialColorPrimary"
android:importantForAccessibility="no"
+ android:padding="6dp"
android:src="@drawable/stat_sys_connected_display"
- android:tint="?androidprv:attr/materialColorPrimary" />
+ android:tint="?androidprv:attr/materialColorOnPrimary" />
<TextView
android:id="@+id/connected_display_dialog_title"
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index acee425..8957903 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -83,6 +83,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
+ <!-- Placeholder for the communal UI that will be replaced if the feature is enabled. -->
+ <ViewStub
+ android:id="@+id/communal_ui_stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
<include layout="@layout/brightness_mirror_container" />
<com.android.systemui.scrim.ScrimView
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index ea3c012..1f671ac 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -37,6 +37,9 @@
<bool name="config_use_large_screen_shade_header">true</bool>
+ <!-- Whether to show bottom sheets edge to edge -->
+ <bool name="config_edgeToEdgeBottomSheetDialog">false</bool>
+
<!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a
string with two parts: the ID of the slot and the comma-delimited list of affordance IDs,
separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1add90f..572f6ff 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -531,19 +531,25 @@
</string>
<!-- A path similar to frameworks/base/core/res/res/values/config.xml
- config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a display
- cutout. If present as well as config_enableDisplayCutoutProtection is set to true, then
- SystemUI will draw this "protection path" instead of the display cutout path that is normally
- used for anti-aliasing.
+ config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a outer
+ display cutout. If present as well as config_enableDisplayCutoutProtection is set to true,
+ then SystemUI will draw this "protection path" instead of the display cutout path that is
+ normally used for anti-aliasing.
This path will only be drawn when the front-facing camera turns on, otherwise the main
DisplayCutout path will be rendered
-->
<string translatable="false" name="config_frontBuiltInDisplayCutoutProtection"></string>
- <!-- ID for the camera that needs extra protection -->
+ <!-- ID for the camera of outer display that needs extra protection -->
<string translatable="false" name="config_protectedCameraId"></string>
+ <!-- Similar to config_frontBuiltInDisplayCutoutProtection but for inner display. -->
+ <string translatable="false" name="config_innerBuiltInDisplayCutoutProtection"></string>
+
+ <!-- ID for the camera of inner display that needs extra protection -->
+ <string translatable="false" name="config_protectedInnerCameraId"></string>
+
<!-- Comma-separated list of packages to exclude from camera protection e.g.
"com.android.systemui,com.android.xyz" -->
<string translatable="false" name="config_cameraProtectionExcludedPackages"></string>
@@ -730,6 +736,9 @@
<!-- Whether the communal service should be enabled -->
<bool name="config_communalServiceEnabled">false</bool>
+ <!-- Component names of allowed communal widgets -->
+ <string-array name="config_communalWidgetAllowlist" translatable="false" />
+
<!-- Component name of communal source service -->
<string name="config_communalSourceComponent" translatable="false">@null</string>
@@ -947,6 +956,9 @@
<!-- Flag controlling whether visual query attention detection has been enabled. -->
<bool name="config_enableVisualQueryAttentionDetection">false</bool>
+ <!-- Whether to show bottom sheets edge to edge -->
+ <bool name="config_edgeToEdgeBottomSheetDialog">true</bool>
+
<!--
Whether the scene container framework is enabled.
@@ -954,4 +966,15 @@
bouncer, lockscreen, shade, and quick settings.
-->
<bool name="config_sceneContainerFrameworkEnabled">true</bool>
+
+ <!--
+ Time in milliseconds the user has to touch the side FPS sensor to successfully authenticate
+ TODO(b/302332976) Get this value from the HAL if they can provide an API for it.
+ -->
+ <integer name="config_restToUnlockDuration">300</integer>
+
+ <!--
+ Width in pixels of the Side FPS sensor.
+ -->
+ <integer name="config_sfpsSensorWidth">200</integer>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5a83c7d..10fd8b5 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -177,6 +177,12 @@
<!-- Size of the view displaying the mobile signal icon in the status bar. This value should
match the viewport height of mobile signal drawables such as ic_lte_mobiledata -->
<dimen name="status_bar_mobile_type_size">16sp</dimen>
+ <!-- Size of the view that contains the network type. Should be equal to
+ status_bar_mobile_type_size + 2, to account for 1sp top and bottom padding -->
+ <dimen name="status_bar_mobile_container_height">18sp</dimen>
+ <!-- Corner radius for the background of the network type indicator. Should be equal to
+ status_bar_mobile_container_height / 2 -->
+ <dimen name="status_bar_mobile_container_corner_radius">9sp</dimen>
<!-- Size of the view displaying the mobile roam icon in the status bar. This value should
match the viewport size of drawable stat_sys_roaming -->
<dimen name="status_bar_mobile_roam_size">8sp</dimen>
@@ -760,6 +766,8 @@
<dimen name="keyguard_clock_switch_y_shift">14dp</dimen>
<!-- When large clock is showing, offset the smartspace by this amount -->
<dimen name="keyguard_smartspace_top_offset">12dp</dimen>
+ <!-- The amount to translate lockscreen elements on the GONE->AOD transition -->
+ <dimen name="keyguard_enter_from_top_translation_y">-100dp</dimen>
<dimen name="notification_scrim_corner_radius">32dp</dimen>
@@ -1355,6 +1363,9 @@
<dimen name="screenrecord_options_padding_bottom">16dp</dimen>
<dimen name="screenrecord_buttons_margin_top">20dp</dimen>
+ <!-- Connected display dialog -->
+ <dimen name="connected_display_dialog_logo_size">48dp</dimen>
+
<!-- Keyguard user switcher -->
<dimen name="kg_user_switcher_text_size">16sp</dimen>
@@ -1658,6 +1669,15 @@
<!-- Height percentage of the parent container occupied by the communal view -->
<item name="communal_source_height_percentage" format="float" type="dimen">0.80</item>
+ <!-- Size of each communal grid column -->
+ <dimen name="communal_grid_column_size">64dp</dimen>
+ <!-- Size of each communal grid gutter between columns -->
+ <dimen name="communal_grid_gutter_size">16dp</dimen>
+ <!-- Height of the communal grid layout -->
+ <dimen name="communal_grid_height">630dp</dimen>
+ <!-- Number of columns for each communal card -->
+ <integer name="communal_grid_columns_per_card">6</integer>
+
<dimen name="drag_and_drop_icon_size">70dp</dimen>
<dimen name="qs_tile_service_request_dialog_width">304dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 321594f..7a6d29a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -209,6 +209,8 @@
<string name="screenshot_saved_title">Screenshot saved</string>
<!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] -->
<string name="screenshot_failed_title">Couldn\'t save screenshot</string>
+ <!-- Appended to the notification content when a screenshot failure happens on an external display. [CHAR LIMIT=50] -->
+ <string name="screenshot_failed_external_display_indication">External Display</string>
<!-- Notification text displayed when we fail to save a screenshot due to locked storage. [CHAR LIMIT=100] -->
<string name="screenshot_failed_to_save_user_locked_text">Device must be unlocked before screenshot can be saved</string>
<!-- Notification text displayed when we fail to save a screenshot for unknown reasons. [CHAR LIMIT=100] -->
@@ -711,8 +713,8 @@
<!-- QuickSettings: Cast detail panel, default device description [CHAR LIMIT=NONE] -->
<!-- QuickSettings: Cast detail panel, text when there are no items [CHAR LIMIT=NONE] -->
<string name="quick_settings_cast_detail_empty_text">No devices available</string>
- <!-- QuickSettings: Cast unavailable, text when not connected to WiFi [CHAR LIMIT=NONE] -->
- <string name="quick_settings_cast_no_wifi">Wi\u2011Fi not connected</string>
+ <!-- QuickSettings: Cast unavailable, text when not connected to WiFi or ethernet[CHAR LIMIT=NONE] -->
+ <string name="quick_settings_cast_no_network">No Wi\u2011Fi or Ethernet connection</string>
<!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] -->
<string name="quick_settings_brightness_dialog_title">Brightness</string>
<!-- QuickSettings: Label for the toggle that controls whether display inversion is enabled. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml b/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml
new file mode 100644
index 0000000..4b430e1
--- /dev/null
+++ b/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml
@@ -0,0 +1,20 @@
+<?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.
+ ~
+ -->
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+</network-capabilities-declaration>
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
index db46ccf..80f70a0 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
@@ -33,6 +33,10 @@
val hasFingerprint: Boolean
get() = fingerprintProperties != null
+ /** If SFPS authentication is available. */
+ val hasSfps: Boolean
+ get() = hasFingerprint && fingerprintProperties!!.isAnySidefpsType
+
/** If fingerprint authentication is available (and [faceProperties] is non-null). */
val hasFace: Boolean
get() = faceProperties != null
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 1e89614..400f652 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -702,13 +702,18 @@
@Override
public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
- // Only hide the icon if the top task changes its requestedOrientation
- // Launcher can alter its requestedOrientation while it's not on top, don't hide on this
- Optional.ofNullable(ActivityManagerWrapper.getInstance())
- .map(ActivityManagerWrapper::getRunningTask)
- .ifPresent(a -> {
- if (a.id == taskId) setRotateSuggestionButtonState(false /* visible */);
- });
+ mBgExecutor.execute(() -> {
+ // Only hide the icon if the top task changes its requestedOrientation Launcher can
+ // alter its requestedOrientation while it's not on top, don't hide on this
+ Optional.ofNullable(ActivityManagerWrapper.getInstance())
+ .map(ActivityManagerWrapper::getRunningTask)
+ .ifPresent(a -> {
+ if (a.id == taskId) {
+ mMainThreadHandler.post(() ->
+ setRotateSuggestionButtonState(false /* visible */));
+ }
+ });
+ });
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index eb20669..c505bd5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -61,6 +61,8 @@
InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
public static final int CUJ_OPEN_SEARCH_RESULT =
InteractionJankMonitor.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
+ public static final int CUJ_LAUNCHER_UNFOLD_ANIM =
+ InteractionJankMonitor.CUJ_LAUNCHER_UNFOLD_ANIM;
@IntDef({
CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -77,6 +79,7 @@
CUJ_CLOSE_ALL_APPS_SWIPE,
CUJ_CLOSE_ALL_APPS_TO_HOME,
CUJ_OPEN_SEARCH_RESULT,
+ CUJ_LAUNCHER_UNFOLD_ANIM,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index 3360c96..aef8371 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -24,7 +24,7 @@
val knownFlags: Map<String, Flag<*>>
get() {
// We need to access Flags in order to initialize our map.
- assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+ assert(flagMap.contains(Flags.NULL_FLAG.name)) { "Where is the null flag?" }
return flagMap
}
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
index 75465c2..f4b4296 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
@@ -24,7 +24,7 @@
val knownFlags: Map<String, Flag<*>>
get() {
// We need to access Flags in order to initialize our map.
- assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+ assert(flagMap.contains(Flags.NULL_FLAG.name)) { "Where is the null flag?" }
return flagMap
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
index c4f1ce8..b186018 100644
--- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
@@ -33,11 +33,11 @@
import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS
import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD
import android.util.Log
-import com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.settings.SecureSettings
import java.io.PrintWriter
import javax.inject.Inject
@@ -50,6 +50,7 @@
@Main private val handler: Handler,
private val secureSettings: SecureSettings,
private val contentResolver: ContentResolver,
+ private val selectedUserInteractor: SelectedUserInteractor,
dumpManager: DumpManager
) : Dumpable {
@@ -134,7 +135,7 @@
)
)
- onChange(true, ArrayList(), 0, getCurrentUser())
+ onChange(true, ArrayList(), 0, selectedUserInteractor.getSelectedUserId())
}
private fun registerUri(uris: Collection<Uri>) {
@@ -153,29 +154,31 @@
flags: Int,
userId: Int
) {
- if (getCurrentUser() != userId) {
+ if (selectedUserInteractor.getSelectedUserId() != userId) {
return
}
if (selfChange || uris.contains(wakeUri)) {
requestActiveUnlockOnWakeup = secureSettings.getIntForUser(
- ACTIVE_UNLOCK_ON_WAKE, 0, getCurrentUser()) == 1
+ ACTIVE_UNLOCK_ON_WAKE, 0, selectedUserInteractor.getSelectedUserId()) == 1
}
if (selfChange || uris.contains(unlockIntentUri)) {
requestActiveUnlockOnUnlockIntent = secureSettings.getIntForUser(
- ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 0, getCurrentUser()) == 1
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 0,
+ selectedUserInteractor.getSelectedUserId()) == 1
}
if (selfChange || uris.contains(bioFailUri)) {
requestActiveUnlockOnBioFail = secureSettings.getIntForUser(
- ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0, getCurrentUser()) == 1
+ ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0,
+ selectedUserInteractor.getSelectedUserId()) == 1
}
if (selfChange || uris.contains(faceErrorsUri)) {
processStringArray(
secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ERRORS,
- getCurrentUser()),
+ selectedUserInteractor.getSelectedUserId()),
faceErrorsToTriggerBiometricFailOn,
setOf(FACE_ERROR_TIMEOUT))
}
@@ -183,7 +186,7 @@
if (selfChange || uris.contains(faceAcquireInfoUri)) {
processStringArray(
secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
- getCurrentUser()),
+ selectedUserInteractor.getSelectedUserId()),
faceAcquireInfoToTriggerBiometricFailOn,
emptySet())
}
@@ -192,7 +195,7 @@
processStringArray(
secureSettings.getStringForUser(
ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
- getCurrentUser()),
+ selectedUserInteractor.getSelectedUserId()),
onUnlockIntentWhenBiometricEnrolled,
setOf(BiometricType.NONE.intValue))
}
@@ -201,7 +204,7 @@
processStringArray(
secureSettings.getStringForUser(
ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
- getCurrentUser()),
+ selectedUserInteractor.getSelectedUserId()),
wakeupsConsideredUnlockIntents,
setOf(WAKE_REASON_UNFOLD_DEVICE))
}
@@ -210,7 +213,7 @@
processStringArray(
secureSettings.getStringForUser(
ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
- getCurrentUser()),
+ selectedUserInteractor.getSelectedUserId()),
wakeupsToForceDismissKeyguard,
setOf(WAKE_REASON_UNFOLD_DEVICE))
}
@@ -316,7 +319,8 @@
keyguardUpdateMonitor?.let {
val anyFaceEnrolled = it.isFaceEnrolled
val anyFingerprintEnrolled =
- it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())
+ it.getCachedIsUnlockWithFingerprintPossible(
+ selectedUserInteractor.getSelectedUserId())
val udfpsEnrolled = it.isUdfpsEnrolled
if (!anyFaceEnrolled && !anyFingerprintEnrolled) {
@@ -371,7 +375,8 @@
"${shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()}")
pw.println(" faceEnrolled=${it.isFaceEnrolled}")
pw.println(" fpEnrolled=${
- it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())}")
+ it.getCachedIsUnlockWithFingerprintPossible(
+ selectedUserInteractor.getSelectedUserId())}")
pw.println(" udfpsEnrolled=${it.isUdfpsEnrolled}")
} ?: pw.println(" keyguardUpdateMonitor is uninitialized")
}
diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
index 207f344..58bbdeb 100644
--- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
@@ -45,6 +45,7 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.keyguard.dagger.KeyguardBouncerScope;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import java.util.NoSuchElementException;
@@ -63,6 +64,7 @@
private Handler mHandler;
private IKeyguardClient mClient;
private KeyguardSecurityCallback mKeyguardCallback;
+ private SelectedUserInteractor mSelectedUserInteractor;
private final ServiceConnection mConnection = new ServiceConnection() {
@Override
@@ -76,7 +78,7 @@
} catch (RemoteException e) {
// Failed to link to death, just dismiss and unbind the service for now.
Log.e(TAG, "Lost connection to secondary lockscreen service", e);
- dismiss(KeyguardUpdateMonitor.getCurrentUser());
+ dismiss(mSelectedUserInteractor.getSelectedUserId());
}
}
}
@@ -110,7 +112,7 @@
mView.setChildSurfacePackage(surfacePackage);
} else {
mHandler.post(() -> {
- dismiss(KeyguardUpdateMonitor.getCurrentUser());
+ dismiss(mSelectedUserInteractor.getSelectedUserId());
});
}
}
@@ -131,7 +133,7 @@
protected SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
- final int userId = KeyguardUpdateMonitor.getCurrentUser();
+ final int userId = mSelectedUserInteractor.getSelectedUserId();
mUpdateMonitor.registerCallback(mUpdateCallback);
if (mClient != null) {
@@ -158,7 +160,7 @@
private AdminSecondaryLockScreenController(Context context, KeyguardSecurityContainer parent,
KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback,
- @Main Handler handler) {
+ @Main Handler handler, SelectedUserInteractor selectedUserInteractor) {
mContext = context;
mHandler = handler;
mParent = parent;
@@ -166,6 +168,7 @@
mKeyguardCallback = callback;
mView = new AdminSecurityView(mContext, mSurfaceHolderCallback);
mView.setId(View.generateViewId());
+ mSelectedUserInteractor = selectedUserInteractor;
}
/**
@@ -218,13 +221,13 @@
}
} catch (RemoteException e) {
Log.e(TAG, "Error in onCreateKeyguardSurface", e);
- dismiss(KeyguardUpdateMonitor.getCurrentUser());
+ dismiss(mSelectedUserInteractor.getSelectedUserId());
}
}
private void dismiss(int userId) {
mHandler.removeCallbacksAndMessages(null);
- if (mView.isAttachedToWindow() && userId == KeyguardUpdateMonitor.getCurrentUser()) {
+ if (mView.isAttachedToWindow() && userId == mSelectedUserInteractor.getSelectedUserId()) {
hide();
if (mKeyguardCallback != null) {
mKeyguardCallback.dismiss(/* securityVerified= */ true, userId,
@@ -265,19 +268,24 @@
private final KeyguardSecurityContainer mParent;
private final KeyguardUpdateMonitor mUpdateMonitor;
private final Handler mHandler;
+ private final SelectedUserInteractor mSelectedUserInteractor;
@Inject
- public Factory(Context context, KeyguardSecurityContainer parent,
- KeyguardUpdateMonitor updateMonitor, @Main Handler handler) {
+ public Factory(Context context,
+ KeyguardSecurityContainer parent,
+ KeyguardUpdateMonitor updateMonitor,
+ @Main Handler handler,
+ SelectedUserInteractor selectedUserInteractor) {
mContext = context;
mParent = parent;
mUpdateMonitor = updateMonitor;
mHandler = handler;
+ mSelectedUserInteractor = selectedUserInteractor;
}
public AdminSecondaryLockScreenController create(KeyguardSecurityCallback callback) {
return new AdminSecondaryLockScreenController(mContext, mParent, mUpdateMonitor,
- callback, mHandler);
+ callback, mHandler, mSelectedUserInteractor);
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 01a75d9..e47d36f 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -15,6 +15,8 @@
*/
package com.android.keyguard
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -37,7 +39,7 @@
import com.android.systemui.dagger.qualifiers.DisplaySpecific
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.DOZING_MIGRATION_1
+import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW
import com.android.systemui.flags.Flags.REGION_SAMPLING
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -61,6 +63,7 @@
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
import java.util.Locale
import java.util.TimeZone
@@ -297,7 +300,7 @@
object : KeyguardUpdateMonitorCallback() {
override fun onKeyguardVisibilityChanged(visible: Boolean) {
isKeyguardVisible = visible
- if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ if (!featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) {
if (!isKeyguardVisible) {
clock?.run {
smallClock.animations.doze(if (isDozing) 1f else 0f)
@@ -342,9 +345,9 @@
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
disposableHandle =
parent.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
listenForDozing(this)
- if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ if (featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) {
listenForDozeAmountTransition(this)
listenForAnyStateToAodTransition(this)
} else {
@@ -454,8 +457,9 @@
@VisibleForTesting
internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor.anyStateToAodTransition
- .filter { it.transitionState == TransitionState.FINISHED }
+ keyguardTransitionInteractor.transitionStepsToState(AOD)
+ .filter { it.transitionState == TransitionState.STARTED }
+ .filter { it.from != LOCKSCREEN }
.collect { handleDoze(1f) }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index f7e8eb4..5de370f 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -43,6 +43,7 @@
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.EmergencyDialerConstants;
import com.android.systemui.util.ViewController;
@@ -67,6 +68,7 @@
private LockPatternUtils mLockPatternUtils;
private Executor mMainExecutor;
private Executor mBackgroundExecutor;
+ private SelectedUserInteractor mSelectedUserInteractor;
private final KeyguardUpdateMonitorCallback mInfoCallback =
new KeyguardUpdateMonitorCallback() {
@@ -96,7 +98,8 @@
ShadeController shadeController,
@Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
LockPatternUtils lockPatternUtils,
- Executor mainExecutor, Executor backgroundExecutor) {
+ Executor mainExecutor, Executor backgroundExecutor,
+ SelectedUserInteractor selectedUserInteractor) {
super(view);
mConfigurationController = configurationController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -109,6 +112,7 @@
mLockPatternUtils = lockPatternUtils;
mMainExecutor = mainExecutor;
mBackgroundExecutor = backgroundExecutor;
+ mSelectedUserInteractor = selectedUserInteractor;
}
@Override
@@ -142,7 +146,7 @@
mBackgroundExecutor.execute(() -> {
boolean isInCall = mTelecomManager != null && mTelecomManager.isInCall();
boolean isSecure = mLockPatternUtils
- .isSecure(KeyguardUpdateMonitor.getCurrentUser());
+ .isSecure(mSelectedUserInteractor.getSelectedUserId());
mMainExecutor.execute(() -> mView.updateEmergencyCallButton(
/* isInCall= */ isInCall,
/* hasTelephonyRadio= */ getContext().getPackageManager()
@@ -192,7 +196,7 @@
getContext().startActivityAsUser(emergencyDialIntent,
ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
- new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
+ new UserHandle(mSelectedUserInteractor.getSelectedUserId()));
}
});
});
@@ -218,6 +222,7 @@
private final LockPatternUtils mLockPatternUtils;
private final Executor mMainExecutor;
private final Executor mBackgroundExecutor;
+ private final SelectedUserInteractor mSelectedUserInteractor;
@Inject
public Factory(ConfigurationController configurationController,
@@ -227,7 +232,8 @@
@Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
LockPatternUtils lockPatternUtils,
@Main Executor mainExecutor,
- @Background Executor backgroundExecutor) {
+ @Background Executor backgroundExecutor,
+ SelectedUserInteractor selectedUserInteractor) {
mConfigurationController = configurationController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -240,6 +246,7 @@
mLockPatternUtils = lockPatternUtils;
mMainExecutor = mainExecutor;
mBackgroundExecutor = backgroundExecutor;
+ mSelectedUserInteractor = selectedUserInteractor;
}
/** Construct an {@link com.android.keyguard.EmergencyButtonController}. */
@@ -247,7 +254,7 @@
return new EmergencyButtonController(view, mConfigurationController,
mKeyguardUpdateMonitor, mTelephonyManager, mPowerManager, mActivityTaskManager,
mShadeController, mTelecomManager, mMetricsLogger, mLockPatternUtils,
- mMainExecutor, mBackgroundExecutor);
+ mMainExecutor, mBackgroundExecutor, mSelectedUserInteractor);
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 167bd59..dad4400 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -34,10 +34,11 @@
import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
import com.android.systemui.classifier.FalsingClassifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import java.util.HashMap;
import java.util.Map;
@@ -80,9 +81,9 @@
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor) {
super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
- messageAreaControllerFactory, featureFlags);
+ messageAreaControllerFactory, featureFlags, selectedUserInteractor);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -104,7 +105,7 @@
mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
// if the user is currently locked out, enforce it.
long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
- KeyguardUpdateMonitor.getCurrentUser());
+ mSelectedUserInteractor.getSelectedUserId());
if (shouldLockout(deadline)) {
handleAttemptLockout(deadline);
}
@@ -175,7 +176,7 @@
}
void onPasswordChecked(int userId, boolean matched, int timeoutMs, boolean isValidPassword) {
- boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
+ boolean dismissKeyguard = mSelectedUserInteractor.getSelectedUserId() == userId;
if (matched) {
getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
if (dismissKeyguard) {
@@ -212,7 +213,7 @@
mPendingLockCheck.cancel(false);
}
- final int userId = KeyguardUpdateMonitor.getCurrentUser();
+ final int userId = mSelectedUserInteractor.getSelectedUserId();
if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
// to avoid accidental lockout, only count attempts that are long enough to be a
// real password. This may require some tweaking.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
index e6a2bfa..d26caa9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
@@ -29,6 +29,7 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.SessionTracker
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import java.io.PrintWriter
import javax.inject.Inject
@@ -42,7 +43,8 @@
class KeyguardBiometricLockoutLogger @Inject constructor(
private val uiEventLogger: UiEventLogger,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val sessionTracker: SessionTracker
+ private val sessionTracker: SessionTracker,
+ private val selectedUserInteractor: SelectedUserInteractor
) : CoreStartable {
private var fingerprintLockedOut = false
private var faceLockedOut = false
@@ -52,7 +54,7 @@
override fun start() {
mKeyguardUpdateMonitorCallback.onStrongAuthStateChanged(
- KeyguardUpdateMonitor.getCurrentUser())
+ selectedUserInteractor.getSelectedUserId())
keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback)
}
@@ -79,7 +81,7 @@
}
override fun onStrongAuthStateChanged(userId: Int) {
- if (userId != KeyguardUpdateMonitor.getCurrentUser()) {
+ if (userId != selectedUserInteractor.getSelectedUserId()) {
return
}
val strongAuthFlags = keyguardUpdateMonitor.strongAuthTracker
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 29ce18c..b309483 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -29,7 +29,6 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.bouncer.ui.BouncerMessageView;
import com.android.systemui.bouncer.ui.binder.BouncerMessageViewBinder;
@@ -38,7 +37,9 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.log.BouncerLogger;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -51,7 +52,6 @@
private final SecurityMode mSecurityMode;
private final KeyguardSecurityCallback mKeyguardSecurityCallback;
- private final EmergencyButton mEmergencyButton;
private final EmergencyButtonController mEmergencyButtonController;
private boolean mPaused;
protected KeyguardMessageAreaController<BouncerKeyguardMessageArea> mMessageAreaController;
@@ -61,18 +61,20 @@
// state for the current security method.
private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {};
private final FeatureFlags mFeatureFlags;
+ protected final SelectedUserInteractor mSelectedUserInteractor;
protected KeyguardInputViewController(T view, SecurityMode securityMode,
KeyguardSecurityCallback keyguardSecurityCallback,
EmergencyButtonController emergencyButtonController,
@Nullable KeyguardMessageAreaController.Factory messageAreaControllerFactory,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ SelectedUserInteractor selectedUserInteractor) {
super(view);
mSecurityMode = securityMode;
mKeyguardSecurityCallback = keyguardSecurityCallback;
- mEmergencyButton = view == null ? null : view.findViewById(R.id.emergency_call_button);
mEmergencyButtonController = emergencyButtonController;
mFeatureFlags = featureFlags;
+ mSelectedUserInteractor = selectedUserInteractor;
if (messageAreaControllerFactory != null) {
try {
BouncerKeyguardMessageArea kma = view.requireViewById(R.id.bouncer_message_area);
@@ -207,6 +209,7 @@
private final DevicePostureController mDevicePostureController;
private final KeyguardViewController mKeyguardViewController;
private final FeatureFlags mFeatureFlags;
+ private final SelectedUserInteractor mSelectedUserInteractor;
@Inject
public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -219,7 +222,7 @@
EmergencyButtonController.Factory emergencyButtonControllerFactory,
DevicePostureController devicePostureController,
KeyguardViewController keyguardViewController,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -234,6 +237,7 @@
mDevicePostureController = devicePostureController;
mKeyguardViewController = keyguardViewController;
mFeatureFlags = featureFlags;
+ mSelectedUserInteractor = selectedUserInteractor;
}
/** Create a new {@link KeyguardInputViewController}. */
@@ -248,32 +252,32 @@
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mLatencyTracker, mFalsingCollector,
emergencyButtonController, mMessageAreaControllerFactory,
- mDevicePostureController, mFeatureFlags);
+ mDevicePostureController, mFeatureFlags, mSelectedUserInteractor);
} else if (keyguardInputView instanceof KeyguardPasswordView) {
return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
mFalsingCollector, mKeyguardViewController,
- mDevicePostureController, mFeatureFlags);
+ mDevicePostureController, mFeatureFlags, mSelectedUserInteractor);
} else if (keyguardInputView instanceof KeyguardPINView) {
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
- mDevicePostureController, mFeatureFlags);
+ mDevicePostureController, mFeatureFlags, mSelectedUserInteractor);
} else if (keyguardInputView instanceof KeyguardSimPinView) {
return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
- emergencyButtonController, mFeatureFlags);
+ emergencyButtonController, mFeatureFlags, mSelectedUserInteractor);
} else if (keyguardInputView instanceof KeyguardSimPukView) {
return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
- emergencyButtonController, mFeatureFlags);
+ emergencyButtonController, mFeatureFlags, mSelectedUserInteractor);
}
throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 959cf6f..2e21255 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -39,11 +39,12 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.List;
@@ -51,7 +52,6 @@
public class KeyguardPasswordViewController
extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
- private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500; // 500ms
private final KeyguardSecurityCallback mKeyguardSecurityCallback;
private final DevicePostureController mPostureController;
private final DevicePostureController.Callback mPostureCallback = posture ->
@@ -112,10 +112,11 @@
FalsingCollector falsingCollector,
KeyguardViewController keyguardViewController,
DevicePostureController postureController,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ SelectedUserInteractor selectedUserInteractor) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
- emergencyButtonController, featureFlags);
+ emergencyButtonController, featureFlags, selectedUserInteractor);
mKeyguardSecurityCallback = keyguardSecurityCallback;
mInputMethodManager = inputMethodManager;
mPostureController = postureController;
@@ -132,7 +133,8 @@
@Override
protected void onViewAttached() {
super.onViewAttached();
- mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
+ mPasswordEntry.setTextOperationUser(
+ UserHandle.of(mSelectedUserInteractor.getSelectedUserId()));
mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_VARIATION_PASSWORD);
@@ -164,13 +166,6 @@
// If there's more than one IME, enable the IME switcher button
updateSwitchImeButton();
-
- // When we the current user is switching, InputMethodManagerService sometimes has not
- // switched internal state yet here. As a quick workaround, we check the keyboard state
- // again.
- // TODO: Remove this workaround by ensuring such a race condition never happens.
- mMainExecutor.executeDelayed(
- this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);
}
@Override
@@ -187,7 +182,8 @@
@Override
void resetState() {
- mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
+ mPasswordEntry.setTextOperationUser(
+ UserHandle.of(mSelectedUserInteractor.getSelectedUserId()));
mMessageAreaController.setMessage(getInitialMessageResId());
final boolean wasDisabled = mPasswordEntry.isEnabled();
mView.setPasswordEntryEnabled(true);
@@ -280,7 +276,7 @@
final boolean shouldIncludeAuxiliarySubtypes) {
final List<InputMethodInfo> enabledImis =
imm.getEnabledInputMethodListAsUser(
- UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
+ UserHandle.of(mSelectedUserInteractor.getSelectedUserId()));
// Number of the filtered IMEs
int filteredImisCount = 0;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 57151ae..db7ff88 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -36,11 +36,12 @@
import com.android.internal.widget.LockscreenCredential;
import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
import com.android.systemui.classifier.FalsingClassifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import java.util.HashMap;
import java.util.List;
@@ -110,7 +111,7 @@
mPendingLockCheck.cancel(false);
}
- final int userId = KeyguardUpdateMonitor.getCurrentUser();
+ final int userId = mSelectedUserInteractor.getSelectedUserId();
if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
// Treat single-sized patterns as erroneous taps.
if (pattern.size() == 1) {
@@ -163,7 +164,7 @@
private void onPatternChecked(int userId, boolean matched, int timeoutMs,
boolean isValidPattern) {
- boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
+ boolean dismissKeyguard = mSelectedUserInteractor.getSelectedUserId() == userId;
if (matched) {
getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
if (dismissKeyguard) {
@@ -198,9 +199,10 @@
FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
- DevicePostureController postureController, FeatureFlags featureFlags) {
+ DevicePostureController postureController, FeatureFlags featureFlags,
+ SelectedUserInteractor selectedUserInteractor) {
super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
- messageAreaControllerFactory, featureFlags);
+ messageAreaControllerFactory, featureFlags, selectedUserInteractor);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -223,7 +225,7 @@
mLockPatternView.setOnPatternListener(new UnlockPatternListener());
mLockPatternView.setSaveEnabled(false);
mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
- KeyguardUpdateMonitor.getCurrentUser()));
+ mSelectedUserInteractor.getSelectedUserId()));
mLockPatternView.setOnTouchListener((v, event) -> {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mFalsingCollector.avoidGesture();
@@ -243,7 +245,7 @@
mPostureController.addCallback(mPostureCallback);
// if the user is currently locked out, enforce it.
long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
- KeyguardUpdateMonitor.getCurrentUser());
+ mSelectedUserInteractor.getSelectedUserId());
if (deadline != 0) {
handleAttemptLockout(deadline);
}
@@ -266,7 +268,7 @@
public void reset() {
// reset lock pattern
mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
- KeyguardUpdateMonitor.getCurrentUser()));
+ mSelectedUserInteractor.getSelectedUserId()));
mLockPatternView.enableInput();
mLockPatternView.setEnabled(true);
mLockPatternView.clearPattern();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index aacf866..b7d1171 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -25,9 +25,10 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
extends KeyguardAbsKeyInputViewController<T> {
@@ -60,10 +61,11 @@
LiftToActivateListener liftToActivateListener,
EmergencyButtonController emergencyButtonController,
FalsingCollector falsingCollector,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ SelectedUserInteractor selectedUserInteractor) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
- emergencyButtonController, featureFlags);
+ emergencyButtonController, featureFlags, selectedUserInteractor);
mLiftToActivateListener = liftToActivateListener;
mFalsingCollector = falsingCollector;
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
@@ -74,7 +76,7 @@
super.onViewAttached();
boolean showAnimations = !mLockPatternUtils
- .isPinEnhancedPrivacyEnabled(KeyguardUpdateMonitor.getCurrentUser());
+ .isPinEnhancedPrivacyEnabled(mSelectedUserInteractor.getSelectedUserId());
mPasswordEntry.setShowPassword(showAnimations);
for (NumPadKey button : mView.getButtons()) {
button.setOnTouchListener((v, event) -> {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 9a78868..947d90f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -23,11 +23,12 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
public class KeyguardPinViewController
extends KeyguardPinBasedInputViewController<KeyguardPINView> {
@@ -55,17 +56,17 @@
EmergencyButtonController emergencyButtonController,
FalsingCollector falsingCollector,
DevicePostureController postureController,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- emergencyButtonController, falsingCollector, featureFlags);
+ emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mPostureController = postureController;
mLockPatternUtils = lockPatternUtils;
mFeatureFlags = featureFlags;
view.setIsLockScreenLandscapeEnabled(mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE));
mBackspaceKey = view.findViewById(R.id.delete_button);
- mPinLength = mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser());
+ mPinLength = mLockPatternUtils.getPinLength(selectedUserInteractor.getSelectedUserId());
}
@Override
@@ -124,7 +125,7 @@
private void updateAutoConfirmationState() {
mDisabledAutoConfirmation = mLockPatternUtils.getCurrentFailedPasswordAttempts(
- KeyguardUpdateMonitor.getCurrentUser()) >= MIN_FAILED_PIN_ATTEMPTS;
+ mSelectedUserInteractor.getSelectedUserId()) >= MIN_FAILED_PIN_ATTEMPTS;
updateOKButtonVisibility();
updateBackSpaceVisibility();
updatePinHinting();
@@ -179,7 +180,8 @@
*/
private boolean isAutoPinConfirmEnabledInSettings() {
//Checks if user has enabled the auto confirm in Settings
- return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser())
+ return mLockPatternUtils.isAutoPinConfirmEnabled(
+ mSelectedUserInteractor.getSelectedUserId())
&& mPinLength != LockPatternUtils.PIN_LENGTH_UNAVAILABLE;
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 51dafac..7101ed5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -91,7 +91,7 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.user.domain.interactor.UserInteractor;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
@@ -157,23 +157,25 @@
private int mCurrentUser = UserHandle.USER_NULL;
private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
new UserSwitcherController.UserSwitchCallback() {
- @Override
- public void onUserSwitched() {
- if (mCurrentUser == KeyguardUpdateMonitor.getCurrentUser()) {
- return;
- }
- mCurrentUser = KeyguardUpdateMonitor.getCurrentUser();
- showPrimarySecurityScreen(false);
- if (mCurrentSecurityMode != SecurityMode.SimPin
- && mCurrentSecurityMode != SecurityMode.SimPuk) {
- reinflateViewFlipper((l) -> {});
- }
- }
- };
+ @Override
+ public void onUserSwitched() {
+ if (mCurrentUser == mSelectedUserInteractor.getSelectedUserId()) {
+ return;
+ }
+ mCurrentUser = mSelectedUserInteractor.getSelectedUserId();
+ showPrimarySecurityScreen(false);
+ if (mCurrentSecurityMode != SecurityMode.SimPin
+ && mCurrentSecurityMode != SecurityMode.SimPuk) {
+ reinflateViewFlipper((l) -> {
+ });
+ }
+ }
+ };
@VisibleForTesting
final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() {
private MotionEvent mTouchDown;
+
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
@@ -267,7 +269,8 @@
ThreadUtils.postOnBackgroundThread(() -> {
try {
Thread.sleep(5000);
- } catch (InterruptedException ignored) { }
+ } catch (InterruptedException ignored) {
+ }
System.gc();
System.runFinalization();
System.gc();
@@ -281,7 +284,7 @@
mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
.setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE));
mUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS
- : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE, getSessionId());
+ : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE, getSessionId());
}
@Override
@@ -404,7 +407,7 @@
}
mKeyguardSecurityCallback.dismiss(
false /* authenticated */,
- KeyguardUpdateMonitor.getCurrentUser(),
+ mSelectedUserInteractor.getSelectedUserId(),
/* bypassSecondaryLockScreen */ false,
SecurityMode.Invalid
);
@@ -420,12 +423,13 @@
showPrimarySecurityScreen(false);
}
};
- private final UserInteractor mUserInteractor;
+ private final SelectedUserInteractor mSelectedUserInteractor;
private final Provider<DeviceEntryInteractor> mDeviceEntryInteractor;
private final Provider<JavaAdapter> mJavaAdapter;
private final DeviceProvisionedController mDeviceProvisionedController;
private final Lazy<PrimaryBouncerInteractor> mPrimaryBouncerInteractor;
- @Nullable private Job mSceneTransitionCollectionJob;
+ @Nullable
+ private Job mSceneTransitionCollectionJob;
@Inject
public KeyguardSecurityContainerController(KeyguardSecurityContainer view,
@@ -453,7 +457,7 @@
KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
BouncerMessageInteractor bouncerMessageInteractor,
Provider<JavaAdapter> javaAdapter,
- UserInteractor userInteractor,
+ SelectedUserInteractor selectedUserInteractor,
DeviceProvisionedController deviceProvisionedController,
FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
KeyguardTransitionInteractor keyguardTransitionInteractor,
@@ -487,7 +491,7 @@
mAudioManager = audioManager;
mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
mBouncerMessageInteractor = bouncerMessageInteractor;
- mUserInteractor = userInteractor;
+ mSelectedUserInteractor = selectedUserInteractor;
mDeviceEntryInteractor = deviceEntryInteractor;
mJavaAdapter = javaAdapter;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
@@ -520,10 +524,10 @@
// When the scene framework says that the lockscreen has been dismissed, dismiss the
// keyguard here, revealing the underlying app or launcher:
mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
- mDeviceEntryInteractor.get().isDeviceEntered(),
+ mDeviceEntryInteractor.get().isDeviceEntered(),
isDeviceEntered -> {
if (isDeviceEntered) {
- final int selectedUserId = mUserInteractor.getSelectedUserId();
+ final int selectedUserId = mSelectedUserInteractor.getSelectedUserId();
showNextSecurityScreenOrFinish(
/* authenticated= */ true,
selectedUserId,
@@ -548,7 +552,7 @@
}
}
- /** */
+ /** */
public void onPause() {
if (DEBUG) {
Log.d(TAG, String.format("screen off, instance %s at %s",
@@ -586,12 +590,13 @@
/**
* Shows the primary security screen for the user. This will be either the multi-selector
* or the user's security method.
+ *
* @param turningOff true if the device is being turned off
*/
public void showPrimarySecurityScreen(boolean turningOff) {
if (DEBUG) Log.d(TAG, "show()");
SecurityMode securityMode = whitelistIpcs(() -> mSecurityModel.getSecurityMode(
- KeyguardUpdateMonitor.getCurrentUser()));
+ mSelectedUserInteractor.getSelectedUserId()));
if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")");
showSecurityScreen(securityMode);
}
@@ -671,6 +676,7 @@
/**
* Dismisses the keyguard by going to the next screen or making it gone.
+ *
* @param targetUserId a user that needs to be the foreground user at the dismissal completion.
* @return True if the keyguard is done.
*/
@@ -716,7 +722,7 @@
}
/**
- * Resets the state of the views.
+ * Resets the state of the views.
*/
public void reset() {
mView.reset();
@@ -748,7 +754,7 @@
getCurrentSecurityController(controller -> controller.onResume(reason));
}
mView.onResume(
- mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()),
+ mSecurityModel.getSecurityMode(mSelectedUserInteractor.getSelectedUserId()),
mKeyguardStateController.isFaceAuthEnabled());
}
@@ -764,7 +770,6 @@
/**
* Show the bouncer and start appear animations.
- *
*/
public void appear() {
// We might still be collapsed and the view didn't have time to layout yet or still
@@ -823,13 +828,16 @@
/**
* Shows the next security screen if there is one.
- * @param authenticated true if the user entered the correct authentication
- * @param targetUserId a user that needs to be the foreground user at the finish (if called)
- * completion.
+ *
+ * @param authenticated true if the user entered the correct authentication
+ * @param targetUserId a user that needs to be the foreground user at the finish
+ * (if called)
+ * completion.
* @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary
- * secondary lock screen requirement, if any.
- * @param expectedSecurityMode SecurityMode that is invoking this request. SecurityMode.Invalid
- * indicates that no check should be done
+ * secondary lock screen requirement, if any.
+ * @param expectedSecurityMode SecurityMode that is invoking this request.
+ * SecurityMode.Invalid
+ * indicates that no check should be done
* @return true if keyguard is done
*/
public boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId,
@@ -879,7 +887,7 @@
// Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled(
- KeyguardUpdateMonitor.getCurrentUser())
+ mSelectedUserInteractor.getSelectedUserId())
|| !mDeviceProvisionedController.isUserSetup(targetUserId);
if (securityMode == SecurityMode.None && isLockscreenDisabled) {
@@ -955,6 +963,7 @@
* Allows the media keys to work when the keyguard is showing.
* The media keys should be of no interest to the actual keyguard view(s),
* so intercepting them here should not be of any harm.
+ *
* @param event The key event
* @return whether the event was consumed as a media key.
*/
@@ -1050,8 +1059,6 @@
/**
* Switches to the given security view unless it's already being shown, in which case
* this is a no-op.
- *
- * @param securityMode
*/
@VisibleForTesting
void showSecurityScreen(SecurityMode securityMode) {
@@ -1230,6 +1237,7 @@
* Fades and translates in/out the security screen.
* Fades in as expansion approaches 0.
* Animation duration is between 0.33f and 0.67f of panel expansion fraction.
+ *
* @param fraction amount of the screen that should show.
*/
public void setExpansion(float fraction) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index d2d0517..6e24208 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -43,6 +43,7 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
public class KeyguardSimPinViewController
extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
@@ -83,10 +84,11 @@
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
- EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) {
+ EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
+ SelectedUserInteractor selectedUserInteractor) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- emergencyButtonController, falsingCollector, featureFlags);
+ emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
@@ -168,7 +170,7 @@
mRemainingAttempts = -1;
mShowDefaultMessage = true;
getKeyguardSecurityCallback().dismiss(
- true, KeyguardUpdateMonitor.getCurrentUser(),
+ true, mSelectedUserInteractor.getSelectedUserId(),
SecurityMode.SimPin);
} else {
mShowDefaultMessage = false;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index b52a36b..13f9d3e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -40,6 +40,7 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
public class KeyguardSimPukViewController
extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
@@ -70,7 +71,8 @@
if (simState == TelephonyManager.SIM_STATE_READY) {
mRemainingAttempts = -1;
mShowDefaultMessage = true;
- getKeyguardSecurityCallback().dismiss(true, KeyguardUpdateMonitor.getCurrentUser(),
+ getKeyguardSecurityCallback().dismiss(
+ true, mSelectedUserInteractor.getSelectedUserId(),
SecurityMode.SimPuk);
} else {
resetState();
@@ -87,10 +89,11 @@
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
- EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) {
+ EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
+ SelectedUserInteractor selectedUserInteractor) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- emergencyButtonController, falsingCollector, featureFlags);
+ emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
@@ -284,7 +287,7 @@
mShowDefaultMessage = true;
getKeyguardSecurityCallback().dismiss(
- true, KeyguardUpdateMonitor.getCurrentUser(),
+ true, mSelectedUserInteractor.getSelectedUserId(),
SecurityMode.SimPuk);
} else {
mShowDefaultMessage = false;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 79642bd..758d1fe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -54,6 +54,9 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.shared.model.ScreenPowerState;
@@ -102,10 +105,11 @@
private final Rect mClipBounds = new Rect();
private final KeyguardInteractor mKeyguardInteractor;
private final PowerInteractor mPowerInteractor;
+ private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private Boolean mSplitShadeEnabled = false;
private Boolean mStatusViewCentered = true;
-
+ private boolean mGoneToAodTransitionRunning = false;
private DumpManager mDumpManager;
private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
@@ -135,6 +139,7 @@
FeatureFlags featureFlags,
InteractionJankMonitor interactionJankMonitor,
KeyguardInteractor keyguardInteractor,
+ KeyguardTransitionInteractor keyguardTransitionInteractor,
DumpManager dumpManager,
PowerInteractor powerInteractor) {
super(keyguardStatusView);
@@ -144,12 +149,13 @@
mConfigurationController = configurationController;
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
- logger.getBuffer());
+ featureFlags, logger.getBuffer());
mInteractionJankMonitor = interactionJankMonitor;
mFeatureFlags = featureFlags;
mDumpManager = dumpManager;
mKeyguardInteractor = keyguardInteractor;
mPowerInteractor = powerInteractor;
+ mKeyguardTransitionInteractor = keyguardTransitionInteractor;
}
@Override
@@ -199,6 +205,15 @@
dozeTimeTick();
}
}, context);
+
+ collectFlow(mView, mKeyguardTransitionInteractor.getGoneToAodTransition(),
+ (TransitionStep step) -> {
+ if (step.getTransitionState() == TransitionState.RUNNING) {
+ mGoneToAodTransitionRunning = true;
+ } else {
+ mGoneToAodTransitionRunning = false;
+ }
+ }, context);
}
public KeyguardStatusView getView() {
@@ -266,7 +281,7 @@
* Set keyguard status view alpha.
*/
public void setAlpha(float alpha) {
- if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
+ if (!mKeyguardVisibilityHelper.isVisibilityAnimating() && !mGoneToAodTransitionRunning) {
mView.setAlpha(alpha);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index b7bb35e..7d6240b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -153,7 +153,6 @@
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -177,6 +176,7 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.WeatherData;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -186,8 +186,8 @@
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.Assert;
-import com.android.systemui.util.settings.SecureSettings;
import dalvik.annotation.optimization.NeverCompile;
@@ -410,7 +410,6 @@
private final DevicePolicyManager mDevicePolicyManager;
private final DevicePostureController mPostureController;
private final BroadcastDispatcher mBroadcastDispatcher;
- private final SecureSettings mSecureSettings;
private final InteractionJankMonitor mInteractionJankMonitor;
private final LatencyTracker mLatencyTracker;
private final StatusBarStateController mStatusBarStateController;
@@ -429,6 +428,7 @@
private final IActivityTaskManager mActivityTaskManager;
private final WakefulnessLifecycle mWakefulness;
private final DisplayTracker mDisplayTracker;
+ private final SelectedUserInteractor mSelectedUserInteractor;
private final LockPatternUtils mLockPatternUtils;
@VisibleForTesting
@DevicePostureInt
@@ -537,13 +537,14 @@
private static int sCurrentUser;
+ @Deprecated
public synchronized static void setCurrentUser(int currentUser) {
sCurrentUser = currentUser;
}
/**
* @deprecated This can potentially return unexpected values in a multi user scenario
- * as this state is managed by another component. Consider using {@link UserTracker}.
+ * as this state is managed by another component. Consider using {@link SelectedUserInteractor}.
*/
@Deprecated
public synchronized static int getCurrentUser() {
@@ -577,7 +578,7 @@
if (enabled) {
String message = null;
- if (KeyguardUpdateMonitor.getCurrentUser() == userId
+ if (mSelectedUserInteractor.getSelectedUserId() == userId
&& trustGrantedMessages != null) {
// Show the first non-empty string provided by a trust agent OR intentionally pass
// an empty string through (to prevent the default trust agent string from showing)
@@ -590,7 +591,7 @@
}
mLogger.logTrustGrantedWithFlags(flags, newlyUnlocked, userId, message);
- if (userId == getCurrentUser()) {
+ if (userId == mSelectedUserInteractor.getSelectedUserId()) {
if (newlyUnlocked) {
// if this callback is ever removed, this should then be logged in
// TrustRepository
@@ -1038,7 +1039,7 @@
mHandler.removeCallbacks(mFpCancelNotReceived);
}
try {
- final int userId = mUserTracker.getUserId();
+ final int userId = mSelectedUserInteractor.getSelectedUserId(true);
if (userId != authUserId) {
mLogger.logFingerprintAuthForWrongUser(authUserId);
return;
@@ -1127,8 +1128,8 @@
lockedOutStateChanged = !mFingerprintLockedOutPermanent;
mFingerprintLockedOutPermanent = true;
mLogger.d("Fingerprint permanently locked out - requiring stronger auth");
- mLockPatternUtils.requireStrongAuth(
- STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, getCurrentUser());
+ mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
+ mSelectedUserInteractor.getSelectedUserId());
}
if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
@@ -1309,7 +1310,7 @@
mLogger.d("Aborted successful auth because device is going to sleep.");
return;
}
- final int userId = mUserTracker.getUserId();
+ final int userId = mSelectedUserInteractor.getSelectedUserId(true);
if (userId != authUserId) {
mLogger.logFaceAuthForWrongUser(authUserId);
return;
@@ -1565,7 +1566,8 @@
@Deprecated
public boolean getIsFaceAuthenticated() {
boolean faceAuthenticated = false;
- BiometricAuthenticated bioFaceAuthenticated = mUserFaceAuthenticated.get(getCurrentUser());
+ BiometricAuthenticated bioFaceAuthenticated =
+ mUserFaceAuthenticated.get(mSelectedUserInteractor.getSelectedUserId());
if (bioFaceAuthenticated != null) {
faceAuthenticated = bioFaceAuthenticated.mAuthenticated;
}
@@ -1754,9 +1756,10 @@
cb.onStrongAuthStateChanged(userId);
}
}
- if (userId == getCurrentUser()) {
+ if (userId == mSelectedUserInteractor.getSelectedUserId()) {
FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED.setExtraInfo(
- mStrongAuthTracker.getStrongAuthForUser(getCurrentUser()));
+ mStrongAuthTracker.getStrongAuthForUser(
+ mSelectedUserInteractor.getSelectedUserId()));
// Strong auth is only reset when primary auth is used to enter the device,
// so we only check whether to stop biometric listening states here
@@ -1783,10 +1786,10 @@
cb.onNonStrongBiometricAllowedChanged(userId);
}
}
- if (userId == getCurrentUser()) {
+ if (userId == mSelectedUserInteractor.getSelectedUserId()) {
FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED.setExtraInfo(
mStrongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(
- getCurrentUser()) ? -1 : 1);
+ mSelectedUserInteractor.getSelectedUserId()) ? -1 : 1);
// This is only reset when primary auth is used to enter the device, so we only check
// whether to stop biometric listening states here
@@ -1984,6 +1987,7 @@
@Override
public void onAuthenticationAcquired(int acquireInfo) {
Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationAcquired");
+ mLogger.logFingerprintAcquired(acquireInfo);
handleFingerprintAcquired(acquireInfo);
Trace.endSection();
}
@@ -2190,12 +2194,12 @@
}
public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
- int userId = getCurrentUser();
+ int userId = mSelectedUserInteractor.getSelectedUserId();
return isBiometricAllowedForUser(isStrongBiometric, userId);
}
public boolean hasUserAuthenticatedSinceBoot() {
- int userId = getCurrentUser();
+ int userId = mSelectedUserInteractor.getSelectedUserId();
return (getStrongAuthForUser(userId)
& STRONG_AUTH_REQUIRED_AFTER_BOOT) == 0;
}
@@ -2343,7 +2347,6 @@
UserTracker userTracker,
@Main Looper mainLooper,
BroadcastDispatcher broadcastDispatcher,
- SecureSettings secureSettings,
DumpManager dumpManager,
@Background Executor backgroundExecutor,
@Main Executor mainExecutor,
@@ -2375,7 +2378,8 @@
TaskStackChangeListeners taskStackChangeListeners,
IActivityTaskManager activityTaskManagerService,
DisplayTracker displayTracker,
- WakefulnessLifecycle wakefulness) {
+ WakefulnessLifecycle wakefulness,
+ SelectedUserInteractor selectedUserInteractor) {
mContext = context;
mSubscriptionManager = subscriptionManager;
mUserTracker = userTracker;
@@ -2391,7 +2395,6 @@
mStatusBarState = mStatusBarStateController.getState();
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
- mSecureSettings = secureSettings;
dumpManager.registerDumpable(this);
mSensorPrivacyManager = sensorPrivacyManager;
mActiveUnlockConfig = activeUnlockConfiguration;
@@ -2425,6 +2428,7 @@
mWakefulness = wakefulness;
mDisplayTracker = displayTracker;
mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
+ mSelectedUserInteractor = selectedUserInteractor;
mHandler = new Handler(mainLooper) {
@Override
@@ -2647,7 +2651,7 @@
mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
mIsSystemUser = mUserManager.isSystemUser();
- int user = mUserTracker.getUserId();
+ int user = mSelectedUserInteractor.getSelectedUserId(true);
mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
updateSecondaryLockscreenRequirement(user);
@@ -2719,7 +2723,7 @@
* @return true if there's at least one udfps enrolled for the current user.
*/
public boolean isUdfpsEnrolled() {
- return mAuthController.isUdfpsEnrolled(getCurrentUser());
+ return mAuthController.isUdfpsEnrolled(mSelectedUserInteractor.getSelectedUserId());
}
/**
@@ -2734,7 +2738,7 @@
* @return true if there's at least one sfps enrollment for the current user.
*/
public boolean isSfpsEnrolled() {
- return mAuthController.isSfpsEnrolled(getCurrentUser());
+ return mAuthController.isSfpsEnrolled(mSelectedUserInteractor.getSelectedUserId());
}
/**
@@ -2905,7 +2909,7 @@
if (shouldTriggerActiveUnlock()) {
mLogger.logActiveUnlockTriggered(reason);
- mTrustManager.reportUserMayRequestUnlock(KeyguardUpdateMonitor.getCurrentUser());
+ mTrustManager.reportUserMayRequestUnlock(mSelectedUserInteractor.getSelectedUserId());
}
}
@@ -2959,7 +2963,7 @@
if (allowRequest && shouldTriggerActiveUnlock()) {
mLogger.logUserRequestedUnlock(requestOrigin, reason, dismissKeyguard);
- mTrustManager.reportUserRequestedUnlock(KeyguardUpdateMonitor.getCurrentUser(),
+ mTrustManager.reportUserRequestedUnlock(mSelectedUserInteractor.getSelectedUserId(),
dismissKeyguard);
}
}
@@ -3030,7 +3034,7 @@
&& mStatusBarState != StatusBarState.SHADE_LOCKED);
// Gates:
- final int user = getCurrentUser();
+ final int user = mSelectedUserInteractor.getSelectedUserId();
// No need to trigger active unlock if we're already unlocked or don't have
// pin/pattern/password setup
@@ -3072,30 +3076,33 @@
}
private boolean shouldListenForFingerprintAssistant() {
- BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(getCurrentUser());
+ BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(
+ mSelectedUserInteractor.getSelectedUserId());
return mAssistantVisible && mKeyguardOccluded
&& !(fingerprint != null && fingerprint.mAuthenticated)
- && !mUserHasTrust.get(getCurrentUser(), false);
+ && !mUserHasTrust.get(
+ mSelectedUserInteractor.getSelectedUserId(), false);
}
private boolean shouldListenForFaceAssistant() {
- BiometricAuthenticated face = mUserFaceAuthenticated.get(getCurrentUser());
+ BiometricAuthenticated face = mUserFaceAuthenticated.get(
+ mSelectedUserInteractor.getSelectedUserId());
return mAssistantVisible
// There can be intermediate states where mKeyguardShowing is false but
// mKeyguardOccluded is true, we don't want to run face auth in such a scenario.
&& (mKeyguardShowing && mKeyguardOccluded)
&& !(face != null && face.mAuthenticated)
- && !mUserHasTrust.get(getCurrentUser(), false);
+ && !mUserHasTrust.get(mSelectedUserInteractor.getSelectedUserId(), false);
}
private boolean shouldTriggerActiveUnlockForAssistant() {
return mAssistantVisible && mKeyguardOccluded
- && !mUserHasTrust.get(getCurrentUser(), false);
+ && !mUserHasTrust.get(mSelectedUserInteractor.getSelectedUserId(), false);
}
@VisibleForTesting
protected boolean shouldListenForFingerprint(boolean isUdfps) {
- final int user = getCurrentUser();
+ final int user = mSelectedUserInteractor.getSelectedUserId();
final boolean userDoesNotHaveTrust = !getUserHasTrust(user);
final boolean shouldListenForFingerprintAssistant = shouldListenForFingerprintAssistant();
final boolean shouldListenKeyguardState =
@@ -3184,7 +3191,7 @@
final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED;
final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive
&& !statusBarShadeLocked;
- final int user = getCurrentUser();
+ final int user = mSelectedUserInteractor.getSelectedUserId();
final boolean faceAuthAllowed = isUnlockingWithBiometricAllowed(FACE);
final boolean canBypass = mKeyguardBypassController != null
&& mKeyguardBypassController.canBypass();
@@ -3284,7 +3291,7 @@
}
private void startListeningForFingerprint() {
- final int userId = getCurrentUser();
+ final int userId = mSelectedUserInteractor.getSelectedUserId();
final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
if (mFingerprintCancelSignal != null) {
mLogger.logUnexpectedFpCancellationSignalState(
@@ -3331,7 +3338,7 @@
}
private void startListeningForFace(@NonNull FaceAuthUiEvent faceAuthUiEvent) {
- final int userId = getCurrentUser();
+ final int userId = mSelectedUserInteractor.getSelectedUserId();
final boolean unlockPossible = isUnlockWithFacePossible(userId);
if (mFaceCancelSignal != null) {
mLogger.logUnexpectedFaceCancellationSignalState(mFaceRunningState, unlockPossible);
@@ -3462,7 +3469,8 @@
@Deprecated
private boolean isUnlockWithFacePossible(int userId) {
if (isFaceAuthInteractorEnabled()) {
- return getFaceAuthInteractor().canFaceAuthRun();
+ return getFaceAuthInteractor() != null
+ && getFaceAuthInteractor().isFaceAuthEnabledAndEnrolled();
}
return isFaceAuthEnabledForUser(userId) && !isFaceDisabled(userId);
}
@@ -3873,12 +3881,12 @@
}
private boolean resolveNeedsSlowUnlockTransition() {
- if (isUserUnlocked(getCurrentUser())) {
+ if (isUserUnlocked(mSelectedUserInteractor.getSelectedUserId())) {
return false;
}
Intent homeIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME);
ResolveInfo resolveInfo = mPackageManager.resolveActivityAsUser(homeIntent,
- 0 /* flags */, getCurrentUser());
+ 0 /* flags */, mSelectedUserInteractor.getSelectedUserId());
if (resolveInfo == null) {
mLogger.w("resolveNeedsSlowUnlockTransition: returning false since activity could "
@@ -4064,9 +4072,11 @@
@AnyThread
public void setSwitchingUser(boolean switching) {
if (switching) {
- mLogger.logUserSwitching(getCurrentUser(), "from setSwitchingUser");
+ mLogger.logUserSwitching(
+ mSelectedUserInteractor.getSelectedUserId(), "from setSwitchingUser");
} else {
- mLogger.logUserSwitchComplete(getCurrentUser(), "from setSwitchingUser");
+ mLogger.logUserSwitchComplete(
+ mSelectedUserInteractor.getSelectedUserId(), "from setSwitchingUser");
}
mSwitchingUser = switching;
// Since this comes in on a binder thread, we need to post it first
@@ -4441,9 +4451,10 @@
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("KeyguardUpdateMonitor state:");
- pw.println(" getUserHasTrust()=" + getUserHasTrust(getCurrentUser()));
+ pw.println(" getUserHasTrust()=" + getUserHasTrust(
+ mSelectedUserInteractor.getSelectedUserId()));
pw.println(" getUserUnlockedWithBiometric()="
- + getUserUnlockedWithBiometric(getCurrentUser()));
+ + getUserUnlockedWithBiometric(mSelectedUserInteractor.getSelectedUserId()));
pw.println(" isFaceAuthInteractorEnabled: " + isFaceAuthInteractorEnabled());
pw.println(" SIM States:");
for (SimData data : mSimDatas.values()) {
@@ -4461,7 +4472,7 @@
pw.println(" " + subId + "=" + mServiceStates.get(subId));
}
if (isFingerprintSupported()) {
- final int userId = mUserTracker.getUserId();
+ final int userId = mSelectedUserInteractor.getSelectedUserId(true);
final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
pw.println(" Fingerprint state (user=" + userId + ")");
@@ -4504,7 +4515,7 @@
mFingerprintListenBuffer.toList()
).printTableData(pw);
} else if (mFpm != null && mFingerprintSensorProperties.isEmpty()) {
- final int userId = mUserTracker.getUserId();
+ final int userId = mSelectedUserInteractor.getSelectedUserId(true);
pw.println(" Fingerprint state (user=" + userId + ")");
pw.println(" mFingerprintSensorProperties.isEmpty="
+ mFingerprintSensorProperties.isEmpty());
@@ -4518,7 +4529,7 @@
).printTableData(pw);
}
if (isFaceSupported()) {
- final int userId = mUserTracker.getUserId();
+ final int userId = mSelectedUserInteractor.getSelectedUserId(true);
final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
pw.println(" Face authentication state (user=" + userId + ")");
@@ -4548,7 +4559,7 @@
mFaceListenBuffer.toList()
).printTableData(pw);
} else if (mFaceManager != null && mFaceSensorProperties.isEmpty()) {
- final int userId = mUserTracker.getUserId();
+ final int userId = mSelectedUserInteractor.getSelectedUserId(true);
pw.println(" Face state (user=" + userId + ")");
pw.println(" mFaceSensorProperties.isEmpty="
+ mFaceSensorProperties.isEmpty());
@@ -4562,7 +4573,7 @@
).printTableData(pw);
}
pw.println("ActiveUnlockRunning="
- + mTrustManager.isActiveUnlockRunning(KeyguardUpdateMonitor.getCurrentUser()));
+ + mTrustManager.isActiveUnlockRunning(mSelectedUserInteractor.getSelectedUserId()));
new DumpsysTableLogger(
"KeyguardActiveUnlockTriggers",
KeyguardActiveUnlockModel.TABLE_HEADERS,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index c64ae01..d524e4a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -23,6 +23,8 @@
import android.view.View;
import com.android.app.animation.Interpolators;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.statusbar.StatusBarState;
@@ -53,6 +55,7 @@
private boolean mKeyguardViewVisibilityAnimating;
private boolean mLastOccludedState = false;
private final AnimationProperties mAnimationProperties = new AnimationProperties();
+ private final FeatureFlags mFeatureFlags;
private final LogBuffer mLogBuffer;
public KeyguardVisibilityHelper(View view,
@@ -60,12 +63,14 @@
DozeParameters dozeParameters,
ScreenOffAnimationController screenOffAnimationController,
boolean animateYPos,
+ FeatureFlags featureFlags,
LogBuffer logBuffer) {
mView = view;
mKeyguardStateController = keyguardStateController;
mDozeParameters = dozeParameters;
mScreenOffAnimationController = screenOffAnimationController;
mAnimateYPos = animateYPos;
+ mFeatureFlags = featureFlags;
mLogBuffer = logBuffer;
}
@@ -162,13 +167,17 @@
animProps,
true /* animate */);
} else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) {
- log("ScreenOff transition");
- mKeyguardViewVisibilityAnimating = true;
+ if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ log("Using GoneToAodTransition");
+ mKeyguardViewVisibilityAnimating = false;
+ } else {
+ log("ScreenOff transition");
+ mKeyguardViewVisibilityAnimating = true;
- // Ask the screen off animation controller to animate the keyguard visibility for us
- // since it may need to be cancelled due to keyguard lifecycle events.
- mScreenOffAnimationController.animateInKeyguard(
- mView, mSetVisibleEndRunnable);
+ // Ask the screen off animation controller to animate the keyguard visibility
+ // for us since it may need to be cancelled due to keyguard lifecycle events.
+ mScreenOffAnimationController.animateInKeyguard(mView, mSetVisibleEndRunnable);
+ }
} else {
log("Direct set Visibility to VISIBLE");
mView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 40d0be1..ff6a3d0 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -25,7 +25,6 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
@@ -105,18 +104,6 @@
mLockIcon.setImageTintList(ColorStateList.valueOf(mLockIconColor));
}
- void setImageDrawable(Drawable drawable) {
- mLockIcon.setImageDrawable(drawable);
-
- if (!mUseBackground) return;
-
- if (drawable == null) {
- mBgView.setVisibility(View.INVISIBLE);
- } else {
- mBgView.setVisibility(View.VISIBLE);
- }
- }
-
/**
* Whether or not to render the lock icon background. Mainly used for UDPFS.
*/
@@ -197,6 +184,7 @@
mLockIcon = new ImageView(context, attrs);
mLockIcon.setId(R.id.lock_icon);
mLockIcon.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ mLockIcon.setImageDrawable(context.getDrawable(R.drawable.super_lock_icon));
addView(mLockIcon);
LayoutParams lp = (LayoutParams) mLockIcon.getLayoutParams();
lp.height = MATCH_PARENT;
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 83da80f..611283f 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -35,7 +35,6 @@
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.drawable.AnimatedStateListDrawable;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricSourceType;
import android.os.Process;
@@ -120,9 +119,6 @@
private boolean mUdfpsEnrolled;
private Resources mResources;
private Context mContext;
-
- @NonNull private final AnimatedStateListDrawable mIcon;
-
@NonNull private CharSequence mUnlockedLabel;
@NonNull private CharSequence mLockedLabel;
@NonNull private final VibratorHelper mVibrator;
@@ -147,7 +143,6 @@
private boolean mCanDismissLockScreen;
private int mStatusBarState;
private boolean mIsKeyguardShowing;
- private Runnable mOnGestureDetectedRunnable;
private Runnable mLongPressCancelRunnable;
private boolean mUdfpsSupported;
@@ -232,9 +227,6 @@
mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
-
- mIcon = (AnimatedStateListDrawable)
- resources.getDrawable(R.drawable.super_lock_icon, context.getTheme());
mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button);
mLockedLabel = resources.getString(R.string.accessibility_lock_icon);
mLongPressTimeout = resources.getInteger(R.integer.config_lockIconLongPress);
@@ -270,7 +262,6 @@
@SuppressLint("ClickableViewAccessibility")
public void setLockIconView(LockIconView lockIconView) {
mView = lockIconView;
- mView.setImageDrawable(mIcon);
mView.setAccessibilityDelegate(mAccessibilityDelegate);
if (mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
@@ -492,10 +483,6 @@
pw.println("mUdfpsSupported: " + mUdfpsSupported);
pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled);
pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing);
- pw.println(" mIcon: ");
- for (int state : mIcon.getState()) {
- pw.print(" " + state);
- }
pw.println();
pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
pw.println(" mShowLockIcon: " + mShowLockIcon);
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index fe19616..fa07072 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -44,6 +44,7 @@
import javax.inject.Inject
private const val TAG = "KeyguardUpdateMonitorLog"
+private const val FP_LOG_TAG = "KeyguardFingerprintLog"
/** Helper class for logging for [com.android.keyguard.KeyguardUpdateMonitor] */
class KeyguardUpdateMonitorLogger
@@ -157,7 +158,7 @@
fun logFingerprintAuthForWrongUser(authUserId: Int) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{ int1 = authUserId },
{ "Fingerprint authenticated for wrong user: $int1" }
@@ -166,7 +167,7 @@
fun logFingerprintDisabledForUser(userId: Int) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{ int1 = userId },
{ "Fingerprint disabled by DPM for userId: $int1" }
@@ -174,12 +175,17 @@
}
fun logFingerprintLockoutReset(@LockoutMode mode: Int) {
- logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFingerprintLockoutReset: $int1" })
+ logBuffer.log(
+ FP_LOG_TAG,
+ DEBUG,
+ { int1 = mode },
+ { "handleFingerprintLockoutReset: $int1" }
+ )
}
fun logFingerprintRunningState(fingerprintRunningState: Int) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{ int1 = fingerprintRunningState },
{ "fingerprintRunningState: $int1" }
@@ -188,7 +194,7 @@
fun logFingerprintSuccess(userId: Int, isStrongBiometric: Boolean) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{
int1 = userId
@@ -212,7 +218,7 @@
fun logFingerprintDetected(userId: Int, isStrongBiometric: Boolean) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{
int1 = userId
@@ -224,7 +230,7 @@
fun logFingerprintError(msgId: Int, originalErrMsg: String) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{
str1 = originalErrMsg
@@ -751,4 +757,25 @@
{ "userSwitchComplete: $str1, userId: $int1" }
)
}
+
+ fun logFingerprintHelp(helpMsgId: Int, helpString: CharSequence) {
+ logBuffer.log(
+ FP_LOG_TAG,
+ DEBUG,
+ {
+ int1 = helpMsgId
+ str1 = "$helpString"
+ },
+ { "fingerprint help message: $int1, $str1" }
+ )
+ }
+
+ fun logFingerprintAcquired(acquireInfo: Int) {
+ logBuffer.log(
+ FP_LOG_TAG,
+ DEBUG,
+ { int1 = acquireInfo },
+ { "fingerprint acquire message: $int1" }
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index 2d2ebe9..d33d279 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -17,6 +17,7 @@
package com.android.systemui
import android.content.Context
+import android.content.res.Resources
import android.graphics.Path
import android.graphics.Rect
import android.graphics.RectF
@@ -33,37 +34,32 @@
*/
class CameraAvailabilityListener(
private val cameraManager: CameraManager,
- private val cutoutProtectionPath: Path,
- private val targetCameraId: String,
+ private val cameraProtectionInfoList: List<CameraProtectionInfo>,
excludedPackages: String,
private val executor: Executor
) {
- private var cutoutBounds = Rect()
private val excludedPackageIds: Set<String>
private val listeners = mutableListOf<CameraTransitionCallback>()
private val availabilityCallback: CameraManager.AvailabilityCallback =
object : CameraManager.AvailabilityCallback() {
override fun onCameraClosed(cameraId: String) {
- if (targetCameraId == cameraId) {
- notifyCameraInactive()
+ cameraProtectionInfoList.forEach {
+ if (cameraId == it.cameraId) {
+ notifyCameraInactive()
+ }
}
}
override fun onCameraOpened(cameraId: String, packageId: String) {
- if (targetCameraId == cameraId && !isExcluded(packageId)) {
- notifyCameraActive()
+ cameraProtectionInfoList.forEach {
+ if (cameraId == it.cameraId && !isExcluded(packageId)) {
+ notifyCameraActive(it)
+ }
}
}
}
init {
- val computed = RectF()
- cutoutProtectionPath.computeBounds(computed, false /* unused */)
- cutoutBounds.set(
- computed.left.roundToInt(),
- computed.top.roundToInt(),
- computed.right.roundToInt(),
- computed.bottom.roundToInt())
excludedPackageIds = excludedPackages.split(",").toSet()
}
@@ -100,8 +96,10 @@
cameraManager.unregisterAvailabilityCallback(availabilityCallback)
}
- private fun notifyCameraActive() {
- listeners.forEach { it.onApplyCameraProtection(cutoutProtectionPath, cutoutBounds) }
+ private fun notifyCameraActive(info: CameraProtectionInfo) {
+ listeners.forEach {
+ it.onApplyCameraProtection(info.cutoutProtectionPath, info.cutoutBounds)
+ }
}
private fun notifyCameraInactive() {
@@ -121,12 +119,11 @@
val manager = context
.getSystemService(Context.CAMERA_SERVICE) as CameraManager
val res = context.resources
- val pathString = res.getString(R.string.config_frontBuiltInDisplayCutoutProtection)
- val cameraId = res.getString(R.string.config_protectedCameraId)
+ val cameraProtectionInfoList = loadCameraProtectionInfoList(res)
val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages)
return CameraAvailabilityListener(
- manager, pathFromString(pathString), cameraId, excluded, executor)
+ manager, cameraProtectionInfoList, excluded, executor)
}
private fun pathFromString(pathString: String): Path {
@@ -140,5 +137,53 @@
return p
}
+
+ private fun loadCameraProtectionInfoList(res: Resources): List<CameraProtectionInfo> {
+ val list = mutableListOf<CameraProtectionInfo>()
+ val front = loadCameraProtectionInfo(
+ res,
+ R.string.config_protectedCameraId,
+ R.string.config_frontBuiltInDisplayCutoutProtection
+ )
+ if (front != null) {
+ list.add(front)
+ }
+ val inner = loadCameraProtectionInfo(
+ res,
+ R.string.config_protectedInnerCameraId,
+ R.string.config_innerBuiltInDisplayCutoutProtection
+ )
+ if (inner != null) {
+ list.add(inner)
+ }
+ return list
+ }
+
+ private fun loadCameraProtectionInfo(
+ res: Resources,
+ cameraIdRes: Int,
+ pathRes: Int
+ ): CameraProtectionInfo? {
+ val cameraId = res.getString(cameraIdRes)
+ if (cameraId == null || cameraId.isEmpty()) {
+ return null
+ }
+ val protectionPath = pathFromString(res.getString(pathRes))
+ val computed = RectF()
+ protectionPath.computeBounds(computed)
+ val protectionBounds = Rect(
+ computed.left.roundToInt(),
+ computed.top.roundToInt(),
+ computed.right.roundToInt(),
+ computed.bottom.roundToInt()
+ )
+ return CameraProtectionInfo(cameraId, protectionPath, protectionBounds)
+ }
}
+
+ data class CameraProtectionInfo (
+ val cameraId: String,
+ val cutoutProtectionPath: Path,
+ val cutoutBounds: Rect
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index b33d501..c860979 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -31,7 +31,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.phone.BiometricUnlockController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -53,25 +53,25 @@
private static final String
ACTION_FACE_WAKE =
"com.android.systemui.latency.ACTION_FACE_WAKE";
- private final BiometricUnlockController mBiometricUnlockController;
private final BroadcastDispatcher mBroadcastDispatcher;
private final DeviceConfigProxy mDeviceConfigProxy;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final SelectedUserInteractor mSelectedUserInteractor;
private boolean mEnabled;
@Inject
public LatencyTester(
- BiometricUnlockController biometricUnlockController,
BroadcastDispatcher broadcastDispatcher,
DeviceConfigProxy deviceConfigProxy,
@Main DelayableExecutor mainExecutor,
- KeyguardUpdateMonitor keyguardUpdateMonitor
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ SelectedUserInteractor selectedUserInteractor
) {
- mBiometricUnlockController = biometricUnlockController;
mBroadcastDispatcher = broadcastDispatcher;
mDeviceConfigProxy = deviceConfigProxy;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mSelectedUserInteractor = selectedUserInteractor;
updateEnabled();
mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
@@ -87,11 +87,11 @@
return;
}
if (type == BiometricSourceType.FACE) {
- mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(),
+ mKeyguardUpdateMonitor.onFaceAuthenticated(mSelectedUserInteractor.getSelectedUserId(),
true);
} else if (type == BiometricSourceType.FINGERPRINT) {
mKeyguardUpdateMonitor.onFingerprintAuthenticated(
- KeyguardUpdateMonitor.getCurrentUser(), true);
+ mSelectedUserInteractor.getSelectedUserId(), true);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 0e339dd..9305ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -28,17 +28,17 @@
import com.android.internal.app.IVoiceInteractionSessionListener;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.res.R;
import com.android.systemui.assist.ui.DefaultUiController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.settings.SecureSettings;
import dagger.Lazy;
@@ -144,6 +144,7 @@
private final UserTracker mUserTracker;
private final DisplayTracker mDisplayTracker;
private final SecureSettings mSecureSettings;
+ private final SelectedUserInteractor mSelectedUserInteractor;
private final DeviceProvisionedController mDeviceProvisionedController;
@@ -152,16 +153,16 @@
private final IVisualQueryDetectionAttentionListener mVisualQueryDetectionAttentionListener =
new IVisualQueryDetectionAttentionListener.Stub() {
- @Override
- public void onAttentionGained() {
- handleVisualAttentionChanged(true);
- }
+ @Override
+ public void onAttentionGained() {
+ handleVisualAttentionChanged(true);
+ }
- @Override
- public void onAttentionLost() {
- handleVisualAttentionChanged(false);
- }
- };
+ @Override
+ public void onAttentionLost() {
+ handleVisualAttentionChanged(false);
+ }
+ };
private final CommandQueue mCommandQueue;
protected final AssistUtils mAssistUtils;
@@ -183,7 +184,8 @@
@Main Handler uiHandler,
UserTracker userTracker,
DisplayTracker displayTracker,
- SecureSettings secureSettings) {
+ SecureSettings secureSettings,
+ SelectedUserInteractor selectedUserInteractor) {
mContext = context;
mDeviceProvisionedController = controller;
mCommandQueue = commandQueue;
@@ -195,6 +197,7 @@
mUserTracker = userTracker;
mDisplayTracker = displayTracker;
mSecureSettings = secureSettings;
+ mSelectedUserInteractor = selectedUserInteractor;
registerVoiceInteractionSessionListener();
registerVisualQueryRecognitionStatusListener();
@@ -316,12 +319,13 @@
public boolean shouldOverrideAssist(int invocationType) {
return mAssistOverrideInvocationTypes != null
&& Arrays.stream(mAssistOverrideInvocationTypes).anyMatch(
- override -> override == invocationType);
+ override -> override == invocationType);
}
/**
* @param invocationTypes The invocation types that will henceforth be handled via
- * OverviewProxy (Launcher); other invocation types should be handled by this class.
+ * OverviewProxy (Launcher); other invocation types should be handled by
+ * this class.
*/
public void setAssistantOverridesRequested(int[] invocationTypes) {
mAssistOverrideInvocationTypes = invocationTypes;
@@ -478,7 +482,7 @@
@Nullable
private ComponentName getAssistInfo() {
- return getAssistInfoForUser(KeyguardUpdateMonitor.getCurrentUser());
+ return getAssistInfoForUser(mSelectedUserInteractor.getSelectedUserId());
}
public void showDisclosure() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
deleted file mode 100644
index 3f2da5e..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.biometrics
-
-import android.content.Context
-import android.graphics.drawable.Drawable
-import android.util.Log
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-
-private const val TAG = "AuthBiometricFaceIconController"
-
-/** Face only icon animator for BiometricPrompt. */
-class AuthBiometricFaceIconController(
- context: Context,
- iconView: LottieAnimationView
-) : AuthIconController(context, iconView) {
-
- // false = dark to light, true = light to dark
- private var lastPulseLightToDark = false
-
- private var state: BiometricState = BiometricState.STATE_IDLE
-
- init {
- val size = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
- iconView.layoutParams.width = size
- iconView.layoutParams.height = size
- showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
- }
-
- private fun startPulsing() {
- lastPulseLightToDark = false
- animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true)
- }
-
- private fun pulseInNextDirection() {
- val iconRes = if (lastPulseLightToDark) {
- R.drawable.face_dialog_pulse_dark_to_light
- } else {
- R.drawable.face_dialog_pulse_light_to_dark
- }
- animateIcon(iconRes, true /* repeat */)
- lastPulseLightToDark = !lastPulseLightToDark
- }
-
- override fun handleAnimationEnd(drawable: Drawable) {
- if (state == BiometricState.STATE_AUTHENTICATING || state == BiometricState.STATE_HELP) {
- pulseInNextDirection()
- }
- }
-
- override fun updateIcon(oldState: BiometricState, newState: BiometricState) {
- val lastStateIsErrorIcon = (oldState == BiometricState.STATE_ERROR || oldState == BiometricState.STATE_HELP)
- if (newState == BiometricState.STATE_AUTHENTICATING_ANIMATING_IN) {
- showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticating
- )
- } else if (newState == BiometricState.STATE_AUTHENTICATING) {
- startPulsing()
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticating
- )
- } else if (oldState == BiometricState.STATE_PENDING_CONFIRMATION && newState == BiometricState.STATE_AUTHENTICATED) {
- animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_confirmed
- )
- } else if (lastStateIsErrorIcon && newState == BiometricState.STATE_IDLE) {
- animateIconOnce(R.drawable.face_dialog_error_to_idle)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_idle
- )
- } else if (lastStateIsErrorIcon && newState == BiometricState.STATE_AUTHENTICATED) {
- animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticated
- )
- } else if (newState == BiometricState.STATE_ERROR && oldState != BiometricState.STATE_ERROR) {
- animateIconOnce(R.drawable.face_dialog_dark_to_error)
- iconView.contentDescription = context.getString(
- R.string.keyguard_face_failed
- )
- } else if (oldState == BiometricState.STATE_AUTHENTICATING && newState == BiometricState.STATE_AUTHENTICATED) {
- animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticated
- )
- } else if (newState == BiometricState.STATE_PENDING_CONFIRMATION) {
- animateIconOnce(R.drawable.face_dialog_wink_from_dark)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticated
- )
- } else if (newState == BiometricState.STATE_IDLE) {
- showStaticDrawable(R.drawable.face_dialog_idle_static)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_idle
- )
- } else {
- Log.w(TAG, "Unhandled state: $newState")
- }
- state = newState
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
deleted file mode 100644
index 09eabf2..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.annotation.RawRes
-import android.content.Context
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-
-/** Face/Fingerprint combined icon animator for BiometricPrompt. */
-open class AuthBiometricFingerprintAndFaceIconController(
- context: Context,
- iconView: LottieAnimationView,
- iconViewOverlay: LottieAnimationView,
-) : AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay) {
-
- override val actsAsConfirmButton: Boolean = true
-
- override fun shouldAnimateIconViewForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ): Boolean = when (newState) {
- STATE_PENDING_CONFIRMATION -> true
- else -> super.shouldAnimateIconViewForTransition(oldState, newState)
- }
-
- @RawRes
- override fun getAnimationForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ): Int? = when (newState) {
- STATE_AUTHENTICATED -> {
- if (oldState == STATE_PENDING_CONFIRMATION) {
- R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
- } else {
- super.getAnimationForTransition(oldState, newState)
- }
- }
- STATE_PENDING_CONFIRMATION -> {
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- R.raw.fingerprint_dialogue_error_to_unlock_lottie
- } else {
- R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
- }
- }
- else -> super.getAnimationForTransition(oldState, newState)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
deleted file mode 100644
index 0ad3848..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.annotation.RawRes
-import android.content.Context
-import android.content.Context.FINGERPRINT_SERVICE
-import android.hardware.fingerprint.FingerprintManager
-import android.view.DisplayInfo
-import android.view.Surface
-import android.view.View
-import androidx.annotation.VisibleForTesting
-import com.airbnb.lottie.LottieAnimationView
-import com.android.settingslib.widget.LottieColorUtils
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING_ANIMATING_IN
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_IDLE
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-
-
-/** Fingerprint only icon animator for BiometricPrompt. */
-open class AuthBiometricFingerprintIconController(
- context: Context,
- iconView: LottieAnimationView,
- protected val iconViewOverlay: LottieAnimationView
-) : AuthIconController(context, iconView) {
-
- private val isSideFps: Boolean
- private val isReverseDefaultRotation =
- context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
-
- var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
- set(value) {
- if (field == value) {
- return
- }
- iconViewOverlay.layoutParams.width = value.first
- iconViewOverlay.layoutParams.height = value.second
- iconView.layoutParams.width = value.first
- iconView.layoutParams.height = value.second
- field = value
- }
-
- init {
- iconLayoutParamSize = Pair(context.resources.getDimensionPixelSize(
- R.dimen.biometric_dialog_fingerprint_icon_width),
- context.resources.getDimensionPixelSize(
- R.dimen.biometric_dialog_fingerprint_icon_height))
- isSideFps =
- (context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager?)?.let { fpm ->
- fpm.sensorPropertiesInternal.any { it.isAnySidefpsType }
- } ?: false
- preloadAssets(context)
- val displayInfo = DisplayInfo()
- context.display?.getDisplayInfo(displayInfo)
- if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) {
- iconView.rotation = 180f
- }
- }
-
- private fun updateIconSideFps(lastState: BiometricState, newState: BiometricState) {
- val displayInfo = DisplayInfo()
- context.display?.getDisplayInfo(displayInfo)
- val rotation = getRotationFromDefault(displayInfo.rotation)
- val iconViewOverlayAnimation =
- getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return
-
- if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
- iconViewOverlay.setAnimation(iconViewOverlayAnimation)
- }
-
- val iconContentDescription = getIconContentDescription(newState)
- if (iconContentDescription != null) {
- iconView.contentDescription = iconContentDescription
- }
-
- iconView.frame = 0
- iconViewOverlay.frame = 0
- if (shouldAnimateSfpsIconViewForTransition(lastState, newState)) {
- iconView.playAnimation()
- }
-
- if (shouldAnimateIconViewOverlayForTransition(lastState, newState)) {
- iconViewOverlay.playAnimation()
- }
-
- LottieColorUtils.applyDynamicColors(context, iconView)
- LottieColorUtils.applyDynamicColors(context, iconViewOverlay)
- }
-
- private fun updateIconNormal(lastState: BiometricState, newState: BiometricState) {
- val icon = getAnimationForTransition(lastState, newState) ?: return
-
- if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
- iconView.setAnimation(icon)
- }
-
- val iconContentDescription = getIconContentDescription(newState)
- if (iconContentDescription != null) {
- iconView.contentDescription = iconContentDescription
- }
-
- iconView.frame = 0
- if (shouldAnimateIconViewForTransition(lastState, newState)) {
- iconView.playAnimation()
- }
- LottieColorUtils.applyDynamicColors(context, iconView)
- }
-
- override fun updateIcon(lastState: BiometricState, newState: BiometricState) {
- if (isSideFps) {
- updateIconSideFps(lastState, newState)
- } else {
- iconViewOverlay.visibility = View.GONE
- updateIconNormal(lastState, newState)
- }
- }
-
- @VisibleForTesting
- fun getIconContentDescription(newState: BiometricState): CharSequence? {
- val id = when (newState) {
- STATE_IDLE,
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING,
- STATE_AUTHENTICATED ->
- if (isSideFps) {
- R.string.security_settings_sfps_enroll_find_sensor_message
- } else {
- R.string.fingerprint_dialog_touch_sensor
- }
- STATE_PENDING_CONFIRMATION ->
- if (isSideFps) {
- R.string.security_settings_sfps_enroll_find_sensor_message
- } else {
- R.string.fingerprint_dialog_authenticated_confirmation
- }
- STATE_ERROR,
- STATE_HELP -> R.string.biometric_dialog_try_again
- else -> null
- }
- return if (id != null) context.getString(id) else null
- }
-
- protected open fun shouldAnimateIconViewForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ) = when (newState) {
- STATE_HELP,
- STATE_ERROR -> true
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
- STATE_AUTHENTICATED -> true
- else -> false
- }
-
- private fun shouldAnimateSfpsIconViewForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ) = when (newState) {
- STATE_HELP,
- STATE_ERROR -> true
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING ->
- oldState == STATE_ERROR || oldState == STATE_HELP || oldState == STATE_IDLE
- STATE_AUTHENTICATED -> true
- else -> false
- }
-
- protected open fun shouldAnimateIconViewOverlayForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ) = when (newState) {
- STATE_HELP,
- STATE_ERROR -> true
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
- STATE_AUTHENTICATED -> true
- else -> false
- }
-
- @RawRes
- protected open fun getAnimationForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ): Int? {
- val id = when (newState) {
- STATE_HELP,
- STATE_ERROR -> {
- R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
- }
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING -> {
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
- } else {
- R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
- }
- }
- STATE_AUTHENTICATED -> {
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- R.raw.fingerprint_dialogue_error_to_success_lottie
- } else {
- R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
- }
- }
- else -> return null
- }
- return if (id != null) return id else null
- }
-
- private fun getRotationFromDefault(rotation: Int): Int =
- if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
-
- @RawRes
- private fun getSideFpsOverlayAnimationForTransition(
- oldState: BiometricState,
- newState: BiometricState,
- rotation: Int
- ): Int? = when (newState) {
- STATE_HELP,
- STATE_ERROR -> {
- when (rotation) {
- Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
- Surface.ROTATION_90 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
- Surface.ROTATION_180 ->
- R.raw.biometricprompt_fingerprint_to_error_landscape
- Surface.ROTATION_270 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
- else -> R.raw.biometricprompt_fingerprint_to_error_landscape
- }
- }
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING -> {
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- when (rotation) {
- Surface.ROTATION_0 ->
- R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
- Surface.ROTATION_90 ->
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
- Surface.ROTATION_180 ->
- R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
- Surface.ROTATION_270 ->
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
- else -> R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
- }
- } else {
- when (rotation) {
- Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
- Surface.ROTATION_90 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
- Surface.ROTATION_180 ->
- R.raw.biometricprompt_fingerprint_to_error_landscape
- Surface.ROTATION_270 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
- else -> R.raw.biometricprompt_fingerprint_to_error_landscape
- }
- }
- }
- STATE_AUTHENTICATED -> {
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- when (rotation) {
- Surface.ROTATION_0 ->
- R.raw.biometricprompt_symbol_error_to_success_landscape
- Surface.ROTATION_90 ->
- R.raw.biometricprompt_symbol_error_to_success_portrait_topleft
- Surface.ROTATION_180 ->
- R.raw.biometricprompt_symbol_error_to_success_landscape
- Surface.ROTATION_270 ->
- R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright
- else -> R.raw.biometricprompt_symbol_error_to_success_landscape
- }
- } else {
- when (rotation) {
- Surface.ROTATION_0 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
- Surface.ROTATION_90 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
- Surface.ROTATION_180 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
- Surface.ROTATION_270 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
- else -> R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
- }
- }
- }
- else -> null
- }
-
- private fun preloadAssets(context: Context) {
- if (isSideFps) {
- cacheLottieAssetsInContext(
- context,
- R.raw.biometricprompt_fingerprint_to_error_landscape,
- R.raw.biometricprompt_folded_base_bottomright,
- R.raw.biometricprompt_folded_base_default,
- R.raw.biometricprompt_folded_base_topleft,
- R.raw.biometricprompt_landscape_base,
- R.raw.biometricprompt_portrait_base_bottomright,
- R.raw.biometricprompt_portrait_base_topleft,
- R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
- R.raw.biometricprompt_symbol_error_to_success_landscape,
- R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
- R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
- R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
- )
- } else {
- cacheLottieAssetsInContext(
- context,
- R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
- R.raw.fingerprint_dialogue_error_to_success_lottie,
- R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
- R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
- )
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index 054bd08..8d1d905 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.biometrics
import android.annotation.MainThread
@@ -25,7 +41,7 @@
shadeExpansionCollectorJob =
scope.launch {
// wait for it to emit true once
- shadeInteractorLazy.get().isAnyExpanding.first { it }
+ shadeInteractorLazy.get().isUserInteracting.first { it }
onShadeInteraction.run()
}
shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
deleted file mode 100644
index 958213a..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.annotation.DrawableRes
-import android.content.Context
-import android.graphics.drawable.Animatable2
-import android.graphics.drawable.AnimatedVectorDrawable
-import android.graphics.drawable.Drawable
-import android.util.Log
-import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieCompositionFactory
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-
-private const val TAG = "AuthIconController"
-
-/** Controller for animating the BiometricPrompt icon/affordance. */
-abstract class AuthIconController(
- protected val context: Context,
- protected val iconView: LottieAnimationView
-) : Animatable2.AnimationCallback() {
-
- /** If this controller should ignore events and pause. */
- var deactivated: Boolean = false
-
- /** If the icon view should be treated as an alternate "confirm" button. */
- open val actsAsConfirmButton: Boolean = false
-
- final override fun onAnimationStart(drawable: Drawable) {
- super.onAnimationStart(drawable)
- }
-
- final override fun onAnimationEnd(drawable: Drawable) {
- super.onAnimationEnd(drawable)
-
- if (!deactivated) {
- handleAnimationEnd(drawable)
- }
- }
-
- /** Set the icon to a static image. */
- protected fun showStaticDrawable(@DrawableRes iconRes: Int) {
- iconView.setImageDrawable(context.getDrawable(iconRes))
- }
-
- /** Animate a resource. */
- protected fun animateIconOnce(@DrawableRes iconRes: Int) {
- animateIcon(iconRes, false)
- }
-
- /** Animate a resource. */
- protected fun animateIcon(@DrawableRes iconRes: Int, repeat: Boolean) {
- if (!deactivated) {
- val icon = context.getDrawable(iconRes) as AnimatedVectorDrawable
- iconView.setImageDrawable(icon)
- icon.forceAnimationOnUI()
- if (repeat) {
- icon.registerAnimationCallback(this)
- }
- icon.start()
- }
- }
-
- /** Update the icon to reflect the [newState]. */
- fun updateState(lastState: BiometricState, newState: BiometricState) {
- if (deactivated) {
- Log.w(TAG, "Ignoring updateState when deactivated: $newState")
- } else {
- updateIcon(lastState, newState)
- }
- }
-
- /** Call during [updateState] if the controller is not [deactivated]. */
- abstract fun updateIcon(lastState: BiometricState, newState: BiometricState)
-
- /** Called during [onAnimationEnd] if the controller is not [deactivated]. */
- open fun handleAnimationEnd(drawable: Drawable) {}
-
- // TODO(b/251476085): Migrate this to an extension at the appropriate level?
- /** Load the given [rawResources] immediately so they are cached for use in the [context]. */
- protected fun cacheLottieAssetsInContext(context: Context, vararg rawResources: Int) {
- for (res in rawResources) {
- LottieCompositionFactory.fromRawRes(context, res)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index c1f6259..40f229b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -41,6 +41,8 @@
import android.view.Surface
import android.view.View
import android.view.View.AccessibilityDelegate
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
import android.view.ViewPropertyAnimator
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
@@ -54,13 +56,13 @@
import com.android.internal.annotations.VisibleForTesting
import com.android.keyguard.KeyguardPINView
import com.android.systemui.Dumpable
-import com.android.systemui.res.R
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.res.R
import com.android.systemui.util.boundsOnScreen
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.traceSection
@@ -229,6 +231,20 @@
}
}
+ /** Hide the arrow indicator. */
+ fun hideIndicator() {
+ val lottieAnimationView =
+ overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView?
+ lottieAnimationView?.visibility = INVISIBLE
+ }
+
+ /** Show the arrow indicator. */
+ fun showIndicator() {
+ val lottieAnimationView =
+ overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView?
+ lottieAnimationView?.visibility = VISIBLE
+ }
+
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println("requests:")
for (requestSource in requests) {
@@ -247,6 +263,10 @@
pw.println(" displayId=${displayInfo.uniqueId}")
pw.println(" sensorType=${sensorProps?.sensorType}")
pw.println(" location=${sensorProps?.getLocation(displayInfo.uniqueId)}")
+ pw.println("lottieAnimationView:")
+ pw.println(
+ " visibility=${overlayView?.findViewById<View>(R.id.sidefps_animation)?.visibility}"
+ )
pw.println("overlayOffsets=$overlayOffsets")
pw.println("isReverseDefaultRotation=$isReverseDefaultRotation")
@@ -498,5 +518,5 @@
AUTO_SHOW,
/** Pin, pattern or password bouncer */
PRIMARY_BOUNCER,
- ALTERNATE_BOUNCER
+ ALTERNATE_BOUNCER,
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index c9e4cbe..92eacf1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -97,6 +97,7 @@
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.time.SystemClock;
@@ -167,6 +168,7 @@
@NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor;
@NonNull private final InputManager mInputManager;
@NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
+ @NonNull private final SelectedUserInteractor mSelectedUserInteractor;
private final boolean mIgnoreRefreshRate;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
@@ -281,7 +283,8 @@
mPrimaryBouncerInteractor,
mAlternateBouncerInteractor,
mUdfpsKeyguardAccessibilityDelegate,
- mUdfpsKeyguardViewModels
+ mUdfpsKeyguardViewModels,
+ mSelectedUserInteractor
)));
}
@@ -644,7 +647,8 @@
@NonNull InputManager inputManager,
@NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
@NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
- @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider) {
+ @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider,
+ @NonNull SelectedUserInteractor selectedUserInteractor) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -687,6 +691,7 @@
mAlternateBouncerInteractor = alternateBouncerInteractor;
mInputManager = inputManager;
mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
+ mSelectedUserInteractor = selectedUserInteractor;
mTouchProcessor = singlePointerTouchProcessor;
mSessionTracker = sessionTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 7130bfb..272e0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -44,7 +44,6 @@
import androidx.annotation.LayoutRes
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.res.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController
@@ -56,12 +55,14 @@
import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import kotlinx.coroutines.ExperimentalCoroutinesApi
import javax.inject.Provider
@@ -78,31 +79,32 @@
@ExperimentalCoroutinesApi
@UiThread
class UdfpsControllerOverlay @JvmOverloads constructor(
- private val context: Context,
- private val inflater: LayoutInflater,
- private val windowManager: WindowManager,
- private val accessibilityManager: AccessibilityManager,
- private val statusBarStateController: StatusBarStateController,
- private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val dialogManager: SystemUIDialogManager,
- private val dumpManager: DumpManager,
- private val transitionController: LockscreenShadeTransitionController,
- private val configurationController: ConfigurationController,
- private val keyguardStateController: KeyguardStateController,
- private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
- private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
- val requestId: Long,
- @ShowReason val requestReason: Int,
- private val controllerCallback: IUdfpsOverlayControllerCallback,
- private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
- private val activityLaunchAnimator: ActivityLaunchAnimator,
- private val featureFlags: FeatureFlags,
- private val primaryBouncerInteractor: PrimaryBouncerInteractor,
- private val alternateBouncerInteractor: AlternateBouncerInteractor,
- private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
- private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
- private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>,
+ private val context: Context,
+ private val inflater: LayoutInflater,
+ private val windowManager: WindowManager,
+ private val accessibilityManager: AccessibilityManager,
+ private val statusBarStateController: StatusBarStateController,
+ private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val dialogManager: SystemUIDialogManager,
+ private val dumpManager: DumpManager,
+ private val transitionController: LockscreenShadeTransitionController,
+ private val configurationController: ConfigurationController,
+ private val keyguardStateController: KeyguardStateController,
+ private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+ private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
+ val requestId: Long,
+ @ShowReason val requestReason: Int,
+ private val controllerCallback: IUdfpsOverlayControllerCallback,
+ private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
+ private val activityLaunchAnimator: ActivityLaunchAnimator,
+ private val featureFlags: FeatureFlags,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
+ private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
+ private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>,
+ private val selectedUserInteractor: SelectedUserInteractor,
) {
/** The view, when [isShowing], or null. */
var overlayView: UdfpsView? = null
@@ -268,6 +270,7 @@
primaryBouncerInteractor,
alternateBouncerInteractor,
udfpsKeyguardAccessibilityDelegate,
+ selectedUserInteractor,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 3d5be6f..d7df0e5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -45,6 +45,7 @@
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import java.io.PrintWriter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -68,6 +69,7 @@
primaryBouncerInteractor: PrimaryBouncerInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
+ private val selectedUserInteractor: SelectedUserInteractor,
) :
UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
view,
@@ -384,7 +386,7 @@
}
if (
keyguardUpdateMonitor.getUserUnlockedWithBiometric(
- KeyguardUpdateMonitor.getCurrentUser()
+ selectedUserInteractor.getSelectedUserId()
)
) {
// If the device was unlocked by a biometric, immediately hide the UDFPS icon to avoid
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index c4c52e8b..050b399 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -42,8 +42,11 @@
/** Repository for the current state of the display */
interface DisplayStateRepository {
/**
- * Whether or not the direction rotation is applied to get to an application's requested
- * orientation is reversed.
+ * If true, the direction rotation is applied to get to an application's requested orientation
+ * is reversed. Normally, the model is that landscape is clockwise from portrait; thus on a
+ * portrait device an app requesting landscape will cause a clockwise rotation, and on a
+ * landscape device an app requesting portrait will cause a counter-clockwise rotation. Setting
+ * true here reverses that logic. See go/natural-orientation for context.
*/
val isReverseDefaultRotation: Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
index a590dccd..b9b2fd8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
@@ -23,8 +23,6 @@
import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
-import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
-import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
import com.android.systemui.dagger.SysUISingleton
import dagger.Binds
import dagger.Module
@@ -49,10 +47,4 @@
@Binds
@SysUISingleton
fun bindsLogContextInteractor(impl: LogContextInteractorImpl): LogContextInteractor
-
- @Binds
- @SysUISingleton
- fun providesSideFpsOverlayInteractor(
- impl: SideFpsOverlayInteractorImpl
- ): SideFpsOverlayInteractor
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index f36a3ec..427361d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -54,6 +54,18 @@
/** Current rotation of the display */
val currentRotation: StateFlow<DisplayRotation>
+ /** Display change event indicating a change to the given displayId has occurred. */
+ val displayChanges: Flow<Int>
+
+ /**
+ * If true, the direction rotation is applied to get to an application's requested orientation
+ * is reversed. Normally, the model is that landscape is clockwise from portrait; thus on a
+ * portrait device an app requesting landscape will cause a clockwise rotation, and on a
+ * landscape device an app requesting portrait will cause a counter-clockwise rotation. Setting
+ * true here reverses that logic. See go/natural-orientation for context.
+ */
+ val isReverseDefaultRotation: Boolean
+
/** Called on configuration changes, used to keep the display state in sync */
fun onConfigurationChanged(newConfig: Configuration)
}
@@ -74,6 +86,8 @@
screenSizeFoldProvider = foldProvider
}
+ override val displayChanges = displayRepository.displayChangeEvent
+
override val isFolded: Flow<Boolean> =
conflatedCallbackFlow {
val sendFoldStateUpdate = { state: Boolean ->
@@ -107,6 +121,8 @@
override val currentRotation: StateFlow<DisplayRotation> =
displayStateRepository.currentRotation
+ override val isReverseDefaultRotation: Boolean = displayStateRepository.isReverseDefaultRotation
+
override fun onConfigurationChanged(newConfig: Configuration) {
screenSizeFoldProvider.onConfigurationChange(newConfig)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
deleted file mode 100644
index 75ae061..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
+++ /dev/null
@@ -1,62 +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.systemui.biometrics.domain.interactor
-
-import android.hardware.biometrics.SensorLocationInternal
-import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.combine
-
-/** Business logic for SideFps overlay offsets. */
-interface SideFpsOverlayInteractor {
-
- /** The displayId of the current display. */
- val displayId: Flow<String>
-
- /** Overlay offsets corresponding to given displayId. */
- val overlayOffsets: Flow<SensorLocationInternal>
-
- /** Called on display changes, used to keep the display state in sync */
- fun onDisplayChanged(displayId: String)
-}
-
-@SysUISingleton
-class SideFpsOverlayInteractorImpl
-@Inject
-constructor(fingerprintPropertyRepository: FingerprintPropertyRepository) :
- SideFpsOverlayInteractor {
-
- private val _displayId: MutableStateFlow<String> = MutableStateFlow("")
- override val displayId: Flow<String> = _displayId.asStateFlow()
-
- override val overlayOffsets: Flow<SensorLocationInternal> =
- combine(displayId, fingerprintPropertyRepository.sensorLocations) { displayId, offsets ->
- offsets[displayId] ?: SensorLocationInternal.DEFAULT
- }
-
- override fun onDisplayChanged(displayId: String) {
- _displayId.value = displayId
- }
-
- companion object {
- private const val TAG = "SideFpsOverlayInteractorImpl"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
new file mode 100644
index 0000000..f85203e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
@@ -0,0 +1,136 @@
+/*
+ * 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.biometrics.domain.interactor
+
+import android.content.Context
+import android.hardware.biometrics.SensorLocationInternal
+import android.view.WindowManager
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.model.SideFpsSensorLocation
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.isDefaultOrientation
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class SideFpsSensorInteractor
+@Inject
+constructor(
+ private val context: Context,
+ fingerprintPropertyRepository: FingerprintPropertyRepository,
+ windowManager: WindowManager,
+ displayStateInteractor: DisplayStateInteractor,
+ featureFlags: FeatureFlagsClassic,
+) {
+
+ private val sensorForCurrentDisplay =
+ combine(
+ displayStateInteractor.displayChanges,
+ fingerprintPropertyRepository.sensorLocations,
+ ::Pair
+ )
+ .map { (_, locations) -> locations[context.display?.uniqueId] }
+ .filterNotNull()
+
+ val isAvailable: Flow<Boolean> =
+ fingerprintPropertyRepository.sensorType.map { it == FingerprintSensorType.POWER_BUTTON }
+
+ val authenticationDuration: Flow<Long> =
+ flowOf(context.resources?.getInteger(R.integer.config_restToUnlockDuration)?.toLong() ?: 0L)
+
+ val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
+ isAvailable.flatMapLatest { sfpsAvailable ->
+ if (sfpsAvailable) {
+ // todo (b/305236201) also add the settings check here.
+ flowOf(featureFlags.isEnabled(Flags.REST_TO_UNLOCK))
+ } else {
+ flowOf(false)
+ }
+ }
+
+ val sensorLocation: Flow<SideFpsSensorLocation> =
+ combine(displayStateInteractor.currentRotation, sensorForCurrentDisplay, ::Pair).map {
+ (rotation, sensorLocation: SensorLocationInternal) ->
+ val isSensorVerticalInDefaultOrientation = sensorLocation.sensorLocationY != 0
+ // device dimensions in the current rotation
+ val size = windowManager.maximumWindowMetrics.bounds
+ val isDefaultOrientation = rotation.isDefaultOrientation()
+ // Width and height are flipped is device is not in rotation_0 or rotation_180
+ // Flipping it to the width and height of the device in default orientation.
+ val displayWidth = if (isDefaultOrientation) size.width() else size.height()
+ val displayHeight = if (isDefaultOrientation) size.height() else size.width()
+ val sensorWidth = context.resources?.getInteger(R.integer.config_sfpsSensorWidth) ?: 0
+
+ val (sensorLeft, sensorTop) =
+ if (isSensorVerticalInDefaultOrientation) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 -> {
+ Pair(displayWidth, sensorLocation.sensorLocationY)
+ }
+ DisplayRotation.ROTATION_90 -> {
+ Pair(sensorLocation.sensorLocationY, 0)
+ }
+ DisplayRotation.ROTATION_180 -> {
+ Pair(0, displayHeight - sensorLocation.sensorLocationY - sensorWidth)
+ }
+ DisplayRotation.ROTATION_270 -> {
+ Pair(
+ displayHeight - sensorLocation.sensorLocationY - sensorWidth,
+ displayWidth
+ )
+ }
+ }
+ } else {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 -> {
+ Pair(sensorLocation.sensorLocationX, 0)
+ }
+ DisplayRotation.ROTATION_90 -> {
+ Pair(0, displayWidth - sensorLocation.sensorLocationX - sensorWidth)
+ }
+ DisplayRotation.ROTATION_180 -> {
+ Pair(
+ displayWidth - sensorLocation.sensorLocationX - sensorWidth,
+ displayHeight
+ )
+ }
+ DisplayRotation.ROTATION_270 -> {
+ Pair(displayHeight, sensorLocation.sensorLocationX)
+ }
+ }
+ }
+
+ SideFpsSensorLocation(
+ left = sensorLeft,
+ top = sensorTop,
+ width = sensorWidth,
+ isSensorVerticalInDefaultOrientation = isSensorVerticalInDefaultOrientation
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 2a1047a..38043b4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -17,13 +17,13 @@
package com.android.systemui.biometrics.domain.interactor
import android.view.MotionEvent
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
@@ -35,11 +35,16 @@
@SysUISingleton
class UdfpsOverlayInteractor
@Inject
-constructor(private val authController: AuthController, @Application scope: CoroutineScope) {
+constructor(
+ private val authController: AuthController,
+ private val selectedUserInteractor: SelectedUserInteractor,
+ @Application scope: CoroutineScope
+) {
/** Whether a touch is within the under-display fingerprint sensor area */
fun isTouchWithinUdfpsArea(ev: MotionEvent): Boolean {
- val isUdfpsEnrolled = authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())
+ val isUdfpsEnrolled =
+ authController.isUdfpsEnrolled(selectedUserInteractor.getSelectedUserId())
val isWithinOverlayBounds =
udfpsOverlayParams.value.overlayBounds.contains(ev.rawX.toInt(), ev.rawY.toInt())
return isUdfpsEnrolled && isWithinOverlayBounds
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt
new file mode 100644
index 0000000..35f8e3b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.biometrics.domain.model
+
+data class SideFpsSensorLocation(
+ /** Pixel offset from the left of the screen */
+ val left: Int,
+ /** Pixel offset from the top of the screen */
+ val top: Int,
+ /** Width in pixels of the SFPS sensor */
+ val width: Int,
+ /**
+ * Whether the sensor is vertical when the device is in its default orientation (Rotation_0 or
+ * Rotation_180)
+ */
+ val isSensorVerticalInDefaultOrientation: Boolean
+)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt
index 10a3e91..336404c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt
@@ -10,6 +10,9 @@
ROTATION_270,
}
+fun DisplayRotation.isDefaultOrientation() =
+ this == DisplayRotation.ROTATION_0 || this == DisplayRotation.ROTATION_180
+
/** Converts [Surface.Rotation] to corresponding [DisplayRotation] */
fun Int.toDisplayRotation(): DisplayRotation =
when (this) {
@@ -19,3 +22,12 @@
Surface.ROTATION_270 -> DisplayRotation.ROTATION_270
else -> throw IllegalArgumentException("Invalid DisplayRotation value: $this")
}
+
+/** Converts [DisplayRotation] to corresponding [Surface.Rotation] */
+fun DisplayRotation.toRotation(): Int =
+ when (this) {
+ DisplayRotation.ROTATION_0 -> Surface.ROTATION_0
+ DisplayRotation.ROTATION_90 -> Surface.ROTATION_90
+ DisplayRotation.ROTATION_180 -> Surface.ROTATION_180
+ DisplayRotation.ROTATION_270 -> Surface.ROTATION_270
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
index cef0be0..0d72b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
@@ -29,11 +29,10 @@
import android.widget.LinearLayout;
import android.widget.TextView;
-import com.android.systemui.res.R;
-import com.android.systemui.biometrics.AuthBiometricFingerprintIconController;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.AuthDialog;
import com.android.systemui.biometrics.UdfpsDialogMeasureAdapter;
+import com.android.systemui.res.R;
import kotlin.Pair;
@@ -85,13 +84,13 @@
}
@Deprecated
- public void updateFingerprintAffordanceSize(
- @NonNull AuthBiometricFingerprintIconController iconController) {
+ public Pair<Integer, Integer> getUpdatedFingerprintAffordanceSize() {
if (mUdfpsAdapter != null) {
final int sensorDiameter = mUdfpsAdapter.getSensorDiameter(
mScaleFactorProvider.provide());
- iconController.setIconLayoutParamSize(new Pair(sensorDiameter, sensorDiameter));
+ return new Pair(sensorDiameter, sensorDiameter);
}
+ return null;
}
@NonNull
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index c29efc0..ac48b6a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -38,11 +38,7 @@
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.AuthBiometricFaceIconController
-import com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceIconController
-import com.android.systemui.biometrics.AuthBiometricFingerprintIconController
-import com.android.systemui.biometrics.AuthIconController
+import com.airbnb.lottie.LottieCompositionFactory
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
@@ -56,6 +52,7 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
@@ -101,10 +98,15 @@
!accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
descriptionView.movementMethod = ScrollingMovementMethod()
- val iconViewOverlay = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
+ val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
- PromptFingerprintIconViewBinder.bind(iconView, viewModel.fingerprintIconViewModel)
+ PromptIconViewBinder.bind(
+ iconView,
+ iconOverlayView,
+ view.getUpdatedFingerprintAffordanceSize(),
+ viewModel.iconViewModel
+ )
val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
@@ -128,9 +130,21 @@
// bind to prompt
var boundSize = false
+
view.repeatWhenAttached {
// these do not change and need to be set before any size transitions
val modalities = viewModel.modalities.first()
+ if (modalities.hasFingerprint) {
+ /**
+ * Load the given [rawResources] immediately so they are cached for use in the
+ * [context].
+ */
+ val rawResources = viewModel.iconViewModel.getRawAssets(modalities.hasSfps)
+ for (res in rawResources) {
+ LottieCompositionFactory.fromRawRes(view.context, res)
+ }
+ }
+
titleView.text = viewModel.title.first()
descriptionView.text = viewModel.description.first()
subtitleView.text = viewModel.subtitle.first()
@@ -148,27 +162,8 @@
legacyCallback.onButtonTryAgain()
}
- // TODO(b/251476085): migrate legacy icon controllers and remove
- var legacyState = viewModel.legacyState.value
- val iconController =
- modalities.asIconController(
- view.context,
- iconView,
- iconViewOverlay,
- )
- adapter.attach(this, iconController, modalities, legacyCallback)
- if (iconController is AuthBiometricFingerprintIconController) {
- view.updateFingerprintAffordanceSize(iconController)
- }
- if (iconController is HackyCoexIconController) {
- iconController.faceMode = !viewModel.isConfirmationRequired.first()
- }
+ adapter.attach(this, modalities, legacyCallback)
- // the icon controller must be created before this happens for the legacy
- // sizing code in BiometricPromptLayout to work correctly. Simplify this
- // when those are also migrated. (otherwise the icon size may not be set to
- // a pixel value before the view is measured and WRAP_CONTENT will be incorrectly
- // used as part of the measure spec)
if (!boundSize) {
boundSize = true
BiometricViewSizeBinder.bind(
@@ -212,14 +207,6 @@
) {
legacyCallback.onStartDelayedFingerprintSensor()
}
-
- if (newMode.isStarted) {
- // do wonky switch from implicit to explicit flow
- (iconController as? HackyCoexIconController)?.faceMode = false
- viewModel.showAuthenticating(
- modalities.asDefaultHelpMessage(view.context),
- )
- }
}
}
@@ -312,7 +299,7 @@
viewModel.isIconConfirmButton
.map { isPending ->
when {
- isPending && iconController.actsAsConfirmButton ->
+ isPending && modalities.hasFaceAndFingerprint ->
View.OnTouchListener { _: View, event: MotionEvent ->
viewModel.onOverlayTouch(event)
}
@@ -320,22 +307,11 @@
}
}
.collect { onTouch ->
- iconViewOverlay.setOnTouchListener(onTouch)
+ iconOverlayView.setOnTouchListener(onTouch)
iconView.setOnTouchListener(onTouch)
}
}
- // TODO(b/251476085): remove w/ legacy icon controllers
- // set icon affordance using legacy states
- // like the old code, this causes animations to repeat on config changes :(
- // but keep behavior for now as no one has complained...
- launch {
- viewModel.legacyState.collect { newState ->
- iconController.updateState(legacyState, newState)
- legacyState = newState
- }
- }
-
// dismiss prompt when authenticated and confirmed
launch {
viewModel.isAuthenticated.collect { authState ->
@@ -350,7 +326,7 @@
// Allow icon to be used as confirmation button with a11y enabled
if (accessibilityManager.isTouchExplorationEnabled) {
- iconViewOverlay.setOnClickListener {
+ iconOverlayView.setOnClickListener {
viewModel.confirmAuthenticated()
}
iconView.setOnClickListener { viewModel.confirmAuthenticated() }
@@ -377,7 +353,6 @@
launch {
viewModel.message.collect { promptMessage ->
val isError = promptMessage is PromptMessage.Error
-
indicatorMessageView.text = promptMessage.message
indicatorMessageView.setTextColor(
if (isError) textColorError else textColorHint
@@ -472,9 +447,6 @@
private var modalities: BiometricModalities = BiometricModalities()
private var legacyCallback: Callback? = null
- var legacyIconController: AuthIconController? = null
- private set
-
// hacky way to suppress lockout errors
private val lockoutErrorStrings =
listOf(
@@ -485,24 +457,20 @@
fun attach(
lifecycleOwner: LifecycleOwner,
- iconController: AuthIconController,
activeModalities: BiometricModalities,
callback: Callback,
) {
modalities = activeModalities
- legacyIconController = iconController
legacyCallback = callback
lifecycleOwner.lifecycle.addObserver(
object : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
lifecycleScope = owner.lifecycleScope
- iconController.deactivated = false
}
override fun onDestroy(owner: LifecycleOwner) {
lifecycleScope = null
- iconController.deactivated = true
}
}
)
@@ -626,61 +594,9 @@
else -> ""
}
-private fun BiometricModalities.asIconController(
- context: Context,
- iconView: LottieAnimationView,
- iconViewOverlay: LottieAnimationView,
-): AuthIconController =
- when {
- hasFaceAndFingerprint -> HackyCoexIconController(context, iconView, iconViewOverlay)
- hasFingerprint -> AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
- hasFace -> AuthBiometricFaceIconController(context, iconView)
- else -> throw IllegalStateException("unexpected view type :$this")
- }
-
private fun Boolean.asVisibleOrGone(): Int = if (this) View.VISIBLE else View.GONE
private fun Boolean.asVisibleOrHidden(): Int = if (this) View.VISIBLE else View.INVISIBLE
// TODO(b/251476085): proper type?
typealias BiometricJankListener = Animator.AnimatorListener
-
-// TODO(b/251476085): delete - temporary until the legacy icon controllers are replaced
-private class HackyCoexIconController(
- context: Context,
- iconView: LottieAnimationView,
- iconViewOverlay: LottieAnimationView,
-) : AuthBiometricFingerprintAndFaceIconController(context, iconView, iconViewOverlay) {
-
- private var state: Spaghetti.BiometricState? = null
- private val faceController = AuthBiometricFaceIconController(context, iconView)
-
- var faceMode: Boolean = true
- set(value) {
- if (field != value) {
- field = value
-
- faceController.deactivated = !value
- iconView.setImageIcon(null)
- iconViewOverlay.setImageIcon(null)
- state?.let { updateIcon(Spaghetti.BiometricState.STATE_IDLE, it) }
- }
- }
-
- override fun updateIcon(
- lastState: Spaghetti.BiometricState,
- newState: Spaghetti.BiometricState,
- ) {
- if (deactivated) {
- return
- }
-
- if (faceMode) {
- faceController.updateIcon(lastState, newState)
- } else {
- super.updateIcon(lastState, newState)
- }
-
- state = newState
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt
deleted file mode 100644
index d28f1dc..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt
+++ /dev/null
@@ -1,49 +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.systemui.biometrics.ui.binder
-
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.biometrics.ui.viewmodel.PromptFingerprintIconViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.launch
-
-/** Sub-binder for [BiometricPromptLayout.iconView]. */
-object PromptFingerprintIconViewBinder {
-
- /** Binds [BiometricPromptLayout.iconView] to [PromptFingerprintIconViewModel]. */
- @JvmStatic
- fun bind(view: LottieAnimationView, viewModel: PromptFingerprintIconViewModel) {
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.onConfigurationChanged(view.context.resources.configuration)
- launch {
- viewModel.iconAsset.collect { iconAsset ->
- if (iconAsset != -1) {
- view.setAnimation(iconAsset)
- // TODO: must replace call below once non-sfps asset logic and
- // shouldAnimateIconView logic is migrated to this ViewModel.
- view.playAnimation()
- }
- }
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
new file mode 100644
index 0000000..475ef18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -0,0 +1,200 @@
+/*
+ * 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.biometrics.ui.binder
+
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.android.settingslib.widget.LottieColorUtils
+import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
+import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.util.kotlin.Utils.Companion.toQuad
+import com.android.systemui.util.kotlin.Utils.Companion.toQuint
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
+import com.android.systemui.util.kotlin.sample
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+/** Sub-binder for [BiometricPromptLayout.iconView]. */
+object PromptIconViewBinder {
+ /**
+ * Binds [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay] to
+ * [PromptIconViewModel].
+ */
+ @JvmStatic
+ fun bind(
+ iconView: LottieAnimationView,
+ iconOverlayView: LottieAnimationView,
+ iconViewLayoutParamSizeOverride: Pair<Int, Int>?,
+ viewModel: PromptIconViewModel
+ ) {
+ iconView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.onConfigurationChanged(iconView.context.resources.configuration)
+ if (iconViewLayoutParamSizeOverride != null) {
+ iconView.layoutParams.width = iconViewLayoutParamSizeOverride.first
+ iconView.layoutParams.height = iconViewLayoutParamSizeOverride.second
+
+ iconOverlayView.layoutParams.width = iconViewLayoutParamSizeOverride.first
+ iconOverlayView.layoutParams.height = iconViewLayoutParamSizeOverride.second
+ }
+
+ var faceIcon: AnimatedVectorDrawable? = null
+ val faceIconCallback =
+ object : Animatable2.AnimationCallback() {
+ override fun onAnimationStart(drawable: Drawable) {
+ viewModel.onAnimationStart()
+ }
+
+ override fun onAnimationEnd(drawable: Drawable) {
+ viewModel.onAnimationEnd()
+ }
+ }
+
+ launch {
+ viewModel.activeAuthType.collect { activeAuthType ->
+ if (iconViewLayoutParamSizeOverride == null) {
+ val width: Int
+ val height: Int
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex -> {
+ width = viewModel.fingerprintIconWidth
+ height = viewModel.fingerprintIconHeight
+ }
+ AuthType.Face -> {
+ width = viewModel.faceIconWidth
+ height = viewModel.faceIconHeight
+ }
+ }
+
+ iconView.layoutParams.width = width
+ iconView.layoutParams.height = height
+
+ iconOverlayView.layoutParams.width = width
+ iconOverlayView.layoutParams.height = height
+ }
+ }
+ }
+
+ launch {
+ viewModel.iconAsset
+ .sample(
+ combine(
+ viewModel.activeAuthType,
+ viewModel.shouldAnimateIconView,
+ viewModel.shouldRepeatAnimation,
+ viewModel.showingError,
+ ::toQuad
+ ),
+ ::toQuint
+ )
+ .collect {
+ (
+ iconAsset,
+ activeAuthType,
+ shouldAnimateIconView,
+ shouldRepeatAnimation,
+ showingError) ->
+ if (iconAsset != -1) {
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex -> {
+ iconView.setAnimation(iconAsset)
+ iconView.frame = 0
+
+ if (shouldAnimateIconView) {
+ iconView.playAnimation()
+ }
+ }
+ AuthType.Face -> {
+ faceIcon?.apply {
+ unregisterAnimationCallback(faceIconCallback)
+ stop()
+ }
+ faceIcon =
+ iconView.context.getDrawable(iconAsset)
+ as AnimatedVectorDrawable
+ faceIcon?.apply {
+ iconView.setImageDrawable(this)
+ if (shouldAnimateIconView) {
+ forceAnimationOnUI()
+ if (shouldRepeatAnimation) {
+ registerAnimationCallback(faceIconCallback)
+ }
+ start()
+ }
+ }
+ }
+ }
+ LottieColorUtils.applyDynamicColors(iconView.context, iconView)
+ viewModel.setPreviousIconWasError(showingError)
+ }
+ }
+ }
+
+ launch {
+ viewModel.iconOverlayAsset
+ .sample(
+ combine(
+ viewModel.shouldAnimateIconOverlay,
+ viewModel.showingError,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (iconOverlayAsset, shouldAnimateIconOverlay, showingError) ->
+ if (iconOverlayAsset != -1) {
+ iconOverlayView.setAnimation(iconOverlayAsset)
+ iconOverlayView.frame = 0
+ LottieColorUtils.applyDynamicColors(
+ iconOverlayView.context,
+ iconOverlayView
+ )
+
+ if (shouldAnimateIconOverlay) {
+ iconOverlayView.playAnimation()
+ }
+ viewModel.setPreviousIconOverlayWasError(showingError)
+ }
+ }
+ }
+
+ launch {
+ viewModel.shouldFlipIconView.collect { shouldFlipIconView ->
+ if (shouldFlipIconView) {
+ iconView.rotation = 180f
+ }
+ }
+ }
+
+ launch {
+ viewModel.contentDescriptionId.collect { id ->
+ if (id != -1) {
+ iconView.contentDescription = iconView.context.getString(id)
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
deleted file mode 100644
index dfd3a9b..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
+++ /dev/null
@@ -1,95 +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.systemui.biometrics.ui.viewmodel
-
-import android.annotation.RawRes
-import android.content.res.Configuration
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
-import com.android.systemui.biometrics.shared.model.DisplayRotation
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-
-/** Models UI of [BiometricPromptLayout.iconView] */
-class PromptFingerprintIconViewModel
-@Inject
-constructor(
- private val displayStateInteractor: DisplayStateInteractor,
- promptSelectorInteractor: PromptSelectorInteractor,
-) {
- /** Current BiometricPromptLayout.iconView asset. */
- val iconAsset: Flow<Int> =
- combine(
- displayStateInteractor.currentRotation,
- displayStateInteractor.isFolded,
- displayStateInteractor.isInRearDisplayMode,
- promptSelectorInteractor.sensorType,
- ) {
- rotation: DisplayRotation,
- isFolded: Boolean,
- isInRearDisplayMode: Boolean,
- sensorType: FingerprintSensorType ->
- when (sensorType) {
- FingerprintSensorType.POWER_BUTTON ->
- getSideFpsAnimationAsset(rotation, isFolded, isInRearDisplayMode)
- // Replace below when non-SFPS iconAsset logic is migrated to this ViewModel
- else -> -1
- }
- }
-
- @RawRes
- private fun getSideFpsAnimationAsset(
- rotation: DisplayRotation,
- isDeviceFolded: Boolean,
- isInRearDisplayMode: Boolean,
- ): Int =
- when (rotation) {
- DisplayRotation.ROTATION_90 ->
- if (isInRearDisplayMode) {
- R.raw.biometricprompt_rear_portrait_reverse_base
- } else if (isDeviceFolded) {
- R.raw.biometricprompt_folded_base_topleft
- } else {
- R.raw.biometricprompt_portrait_base_topleft
- }
- DisplayRotation.ROTATION_270 ->
- if (isInRearDisplayMode) {
- R.raw.biometricprompt_rear_portrait_base
- } else if (isDeviceFolded) {
- R.raw.biometricprompt_folded_base_bottomright
- } else {
- R.raw.biometricprompt_portrait_base_bottomright
- }
- else ->
- if (isInRearDisplayMode) {
- R.raw.biometricprompt_rear_landscape_base
- } else if (isDeviceFolded) {
- R.raw.biometricprompt_folded_base_default
- } else {
- R.raw.biometricprompt_landscape_base
- }
- }
-
- /** Called on configuration changes */
- fun onConfigurationChanged(newConfig: Configuration) {
- displayStateInteractor.onConfigurationChanged(newConfig)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
new file mode 100644
index 0000000..11a5d8b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -0,0 +1,721 @@
+/*
+ * 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.biometrics.ui.viewmodel
+
+import android.annotation.DrawableRes
+import android.annotation.RawRes
+import android.content.res.Configuration
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.combine
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/**
+ * Models UI of [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay]
+ */
+class PromptIconViewModel
+constructor(
+ promptViewModel: PromptViewModel,
+ private val displayStateInteractor: DisplayStateInteractor,
+ promptSelectorInteractor: PromptSelectorInteractor
+) {
+
+ /** Auth types for the UI to display. */
+ enum class AuthType {
+ Fingerprint,
+ Face,
+ Coex
+ }
+
+ /**
+ * Indicates what auth type the UI currently displays.
+ * Fingerprint-only auth -> Fingerprint
+ * Face-only auth -> Face
+ * Co-ex auth, implicit flow -> Face
+ * Co-ex auth, explicit flow -> Coex
+ */
+ val activeAuthType: Flow<AuthType> =
+ combine(
+ promptViewModel.modalities.distinctUntilChanged(),
+ promptViewModel.faceMode.distinctUntilChanged()
+ ) { modalities, faceMode ->
+ if (modalities.hasFaceAndFingerprint && !faceMode) {
+ AuthType.Coex
+ } else if (modalities.hasFaceOnly || faceMode) {
+ AuthType.Face
+ } else if (modalities.hasFingerprintOnly) {
+ AuthType.Fingerprint
+ } else {
+ throw IllegalStateException("unexpected modality: $modalities")
+ }
+ }
+
+ /** Whether an error message is currently being shown. */
+ val showingError = promptViewModel.showingError
+
+ /** Whether the previous icon shown displayed an error. */
+ private val _previousIconWasError: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /** Whether the previous icon overlay shown displayed an error. */
+ private val _previousIconOverlayWasError: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ fun setPreviousIconWasError(previousIconWasError: Boolean) {
+ _previousIconWasError.value = previousIconWasError
+ }
+
+ fun setPreviousIconOverlayWasError(previousIconOverlayWasError: Boolean) {
+ _previousIconOverlayWasError.value = previousIconOverlayWasError
+ }
+
+ /** Called when iconView begins animating. */
+ fun onAnimationStart() {
+ _animationEnded.value = false
+ }
+
+ /** Called when iconView ends animating. */
+ fun onAnimationEnd() {
+ _animationEnded.value = true
+ }
+
+ private val _animationEnded: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /**
+ * Whether a face iconView should pulse (i.e. while isAuthenticating and previous animation
+ * ended).
+ */
+ val shouldPulseAnimation: Flow<Boolean> =
+ combine(_animationEnded, promptViewModel.isAuthenticating) {
+ animationEnded,
+ isAuthenticating ->
+ animationEnded && isAuthenticating
+ }
+ .distinctUntilChanged()
+
+ private val _lastPulseLightToDark: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /** Tracks whether a face iconView last pulsed light to dark (vs. dark to light) */
+ val lastPulseLightToDark: Flow<Boolean> = _lastPulseLightToDark.asStateFlow()
+
+ /** Layout params for fingerprint iconView */
+ val fingerprintIconWidth: Int = promptViewModel.fingerprintIconWidth
+ val fingerprintIconHeight: Int = promptViewModel.fingerprintIconHeight
+
+ /** Layout params for face iconView */
+ val faceIconWidth: Int = promptViewModel.faceIconWidth
+ val faceIconHeight: Int = promptViewModel.faceIconHeight
+
+ /** Current BiometricPromptLayout.iconView asset. */
+ val iconAsset: Flow<Int> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint ->
+ combine(
+ displayStateInteractor.currentRotation,
+ displayStateInteractor.isFolded,
+ displayStateInteractor.isInRearDisplayMode,
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError
+ ) {
+ rotation: DisplayRotation,
+ isFolded: Boolean,
+ isInRearDisplayMode: Boolean,
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode)
+ else ->
+ getFingerprintIconViewAsset(
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ }
+ }
+ AuthType.Face ->
+ shouldPulseAnimation.flatMapLatest { shouldPulseAnimation: Boolean ->
+ if (shouldPulseAnimation) {
+ val iconAsset =
+ if (_lastPulseLightToDark.value) {
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else {
+ R.drawable.face_dialog_pulse_light_to_dark
+ }
+ _lastPulseLightToDark.value = !_lastPulseLightToDark.value
+ flowOf(iconAsset)
+ } else {
+ combine(
+ promptViewModel.isAuthenticated.distinctUntilChanged(),
+ promptViewModel.isAuthenticating.distinctUntilChanged(),
+ promptViewModel.isPendingConfirmation.distinctUntilChanged(),
+ promptViewModel.showingError.distinctUntilChanged()
+ ) {
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean ->
+ getFaceIconViewAsset(
+ authState,
+ isAuthenticating,
+ isPendingConfirmation,
+ showingError
+ )
+ }
+ }
+ }
+ AuthType.Coex ->
+ combine(
+ displayStateInteractor.currentRotation,
+ displayStateInteractor.isFolded,
+ displayStateInteractor.isInRearDisplayMode,
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.isPendingConfirmation,
+ promptViewModel.showingError,
+ ) {
+ rotation: DisplayRotation,
+ isFolded: Boolean,
+ isInRearDisplayMode: Boolean,
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode)
+ else ->
+ getCoexIconViewAsset(
+ authState,
+ isAuthenticating,
+ isPendingConfirmation,
+ showingError
+ )
+ }
+ }
+ }
+ }
+
+ private fun getFingerprintIconViewAsset(
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (isAuthenticated) {
+ if (_previousIconWasError.value) {
+ R.raw.fingerprint_dialogue_error_to_success_lottie
+ } else {
+ R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+ }
+ } else if (isAuthenticating) {
+ if (_previousIconWasError.value) {
+ R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
+ } else {
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+ }
+ } else if (showingError) {
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+ } else {
+ -1
+ }
+
+ @RawRes
+ private fun getSfpsIconViewAsset(
+ rotation: DisplayRotation,
+ isDeviceFolded: Boolean,
+ isInRearDisplayMode: Boolean,
+ ): Int =
+ when (rotation) {
+ DisplayRotation.ROTATION_90 ->
+ if (isInRearDisplayMode) {
+ R.raw.biometricprompt_rear_portrait_reverse_base
+ } else if (isDeviceFolded) {
+ R.raw.biometricprompt_folded_base_topleft
+ } else {
+ R.raw.biometricprompt_portrait_base_topleft
+ }
+ DisplayRotation.ROTATION_270 ->
+ if (isInRearDisplayMode) {
+ R.raw.biometricprompt_rear_portrait_base
+ } else if (isDeviceFolded) {
+ R.raw.biometricprompt_folded_base_bottomright
+ } else {
+ R.raw.biometricprompt_portrait_base_bottomright
+ }
+ else ->
+ if (isInRearDisplayMode) {
+ R.raw.biometricprompt_rear_landscape_base
+ } else if (isDeviceFolded) {
+ R.raw.biometricprompt_folded_base_default
+ } else {
+ R.raw.biometricprompt_landscape_base
+ }
+ }
+
+ @DrawableRes
+ private fun getFaceIconViewAsset(
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (authState.isAuthenticated && isPendingConfirmation) {
+ R.drawable.face_dialog_wink_from_dark
+ } else if (authState.isAuthenticated) {
+ R.drawable.face_dialog_dark_to_checkmark
+ } else if (isAuthenticating) {
+ _lastPulseLightToDark.value = false
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else if (showingError) {
+ R.drawable.face_dialog_dark_to_error
+ } else if (_previousIconWasError.value) {
+ R.drawable.face_dialog_error_to_idle
+ } else {
+ R.drawable.face_dialog_idle_static
+ }
+
+ @RawRes
+ private fun getCoexIconViewAsset(
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+ R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
+ } else if (isPendingConfirmation) {
+ if (_previousIconWasError.value) {
+ R.raw.fingerprint_dialogue_error_to_unlock_lottie
+ } else {
+ R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
+ }
+ } else if (authState.isAuthenticated) {
+ if (_previousIconWasError.value) {
+ R.raw.fingerprint_dialogue_error_to_success_lottie
+ } else {
+ R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+ }
+ } else if (isAuthenticating) {
+ if (_previousIconWasError.value) {
+ R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
+ } else {
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+ }
+ } else if (showingError) {
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+ } else {
+ -1
+ }
+
+ /** Current BiometricPromptLayout.biometric_icon_overlay asset. */
+ var iconOverlayAsset: Flow<Int> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex ->
+ combine(
+ displayStateInteractor.currentRotation,
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError
+ ) {
+ rotation: DisplayRotation,
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ getSfpsIconOverlayAsset(
+ rotation,
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ else -> -1
+ }
+ }
+ AuthType.Face -> flowOf(-1)
+ }
+ }
+
+ @RawRes
+ private fun getSfpsIconOverlayAsset(
+ rotation: DisplayRotation,
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (isAuthenticated) {
+ if (_previousIconOverlayWasError.value) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_symbol_error_to_success_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_error_to_success_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_symbol_error_to_success_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright
+ }
+ } else {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
+ }
+ }
+ } else if (isAuthenticating) {
+ if (_previousIconOverlayWasError.value) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
+ }
+ } else {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+ }
+ }
+ } else if (showingError) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+ DisplayRotation.ROTATION_180 -> R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+ }
+ } else {
+ -1
+ }
+
+ /** Content description for iconView */
+ val contentDescriptionId: Flow<Int> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex ->
+ combine(
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.isPendingConfirmation,
+ promptViewModel.showingError
+ ) {
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean ->
+ getFingerprintIconContentDescriptionId(
+ sensorType,
+ authState.isAuthenticated,
+ isAuthenticating,
+ isPendingConfirmation,
+ showingError
+ )
+ }
+ AuthType.Face ->
+ combine(
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError,
+ ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean
+ ->
+ getFaceIconContentDescriptionId(authState, isAuthenticating, showingError)
+ }
+ }
+ }
+
+ private fun getFingerprintIconContentDescriptionId(
+ sensorType: FingerprintSensorType,
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (isPendingConfirmation) {
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ R.string.security_settings_sfps_enroll_find_sensor_message
+ else -> R.string.fingerprint_dialog_authenticated_confirmation
+ }
+ } else if (isAuthenticating || isAuthenticated) {
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ R.string.security_settings_sfps_enroll_find_sensor_message
+ else -> R.string.fingerprint_dialog_touch_sensor
+ }
+ } else if (showingError) {
+ R.string.biometric_dialog_try_again
+ } else {
+ -1
+ }
+
+ private fun getFaceIconContentDescriptionId(
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+ R.string.biometric_dialog_face_icon_description_confirmed
+ } else if (authState.isAuthenticated) {
+ R.string.biometric_dialog_face_icon_description_authenticated
+ } else if (isAuthenticating) {
+ R.string.biometric_dialog_face_icon_description_authenticating
+ } else if (showingError) {
+ R.string.keyguard_face_failed
+ } else {
+ R.string.biometric_dialog_face_icon_description_idle
+ }
+
+ /** Whether the current BiometricPromptLayout.iconView asset animation should be playing. */
+ val shouldAnimateIconView: Flow<Boolean> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint ->
+ combine(
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError
+ ) {
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ shouldAnimateSfpsIconView(
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ else ->
+ shouldAnimateFingerprintIconView(
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ }
+ }
+ AuthType.Face ->
+ combine(
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError
+ ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean
+ ->
+ isAuthenticating ||
+ authState.isAuthenticated ||
+ showingError ||
+ _previousIconWasError.value
+ }
+ AuthType.Coex ->
+ combine(
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.isPendingConfirmation,
+ promptViewModel.showingError,
+ ) {
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ shouldAnimateSfpsIconView(
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ else ->
+ shouldAnimateCoexIconView(
+ authState.isAuthenticated,
+ isAuthenticating,
+ isPendingConfirmation,
+ showingError
+ )
+ }
+ }
+ }
+ }
+
+ private fun shouldAnimateFingerprintIconView(
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ) = (isAuthenticating && _previousIconWasError.value) || isAuthenticated || showingError
+
+ private fun shouldAnimateSfpsIconView(
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ) = isAuthenticated || isAuthenticating || showingError
+
+ private fun shouldAnimateCoexIconView(
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean
+ ) =
+ (isAuthenticating && _previousIconWasError.value) ||
+ isPendingConfirmation ||
+ isAuthenticated ||
+ showingError
+
+ /** Whether the current iconOverlayAsset animation should be playing. */
+ val shouldAnimateIconOverlay: Flow<Boolean> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex ->
+ combine(
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError
+ ) {
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ shouldAnimateSfpsIconOverlay(
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ else -> false
+ }
+ }
+ AuthType.Face -> flowOf(false)
+ }
+ }
+
+ private fun shouldAnimateSfpsIconOverlay(
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ) = (isAuthenticating && _previousIconOverlayWasError.value) || isAuthenticated || showingError
+
+ /** Whether the iconView should be flipped due to a device using reverse default rotation . */
+ val shouldFlipIconView: Flow<Boolean> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex ->
+ combine(
+ promptSelectorInteractor.sensorType,
+ displayStateInteractor.currentRotation
+ ) { sensorType: FingerprintSensorType, rotation: DisplayRotation ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ (rotation == DisplayRotation.ROTATION_180)
+ else -> false
+ }
+ }
+ AuthType.Face -> flowOf(false)
+ }
+ }
+
+ /** Whether the current BiometricPromptLayout.iconView asset animation should be repeated. */
+ val shouldRepeatAnimation: Flow<Boolean> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex -> flowOf(false)
+ AuthType.Face -> promptViewModel.isAuthenticating.map { it }
+ }
+ }
+
+ /** Called on configuration changes */
+ fun onConfigurationChanged(newConfig: Configuration) {
+ displayStateInteractor.onConfigurationChanged(newConfig)
+ }
+
+ /** iconView assets for caching */
+ fun getRawAssets(hasSfps: Boolean): List<Int> {
+ return if (hasSfps) {
+ listOf(
+ R.raw.biometricprompt_fingerprint_to_error_landscape,
+ R.raw.biometricprompt_folded_base_bottomright,
+ R.raw.biometricprompt_folded_base_default,
+ R.raw.biometricprompt_folded_base_topleft,
+ R.raw.biometricprompt_landscape_base,
+ R.raw.biometricprompt_portrait_base_bottomright,
+ R.raw.biometricprompt_portrait_base_topleft,
+ R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
+ R.raw.biometricprompt_symbol_error_to_success_landscape,
+ R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
+ R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+ )
+ } else {
+ listOf(
+ R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
+ R.raw.fingerprint_dialogue_error_to_success_lottie,
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
+ R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 267afae..e49b4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -28,10 +28,10 @@
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.PromptKind
-import com.android.systemui.biometrics.ui.binder.Spaghetti
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
+import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
import kotlinx.coroutines.Job
@@ -39,7 +39,6 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -51,25 +50,29 @@
class PromptViewModel
@Inject
constructor(
- private val displayStateInteractor: DisplayStateInteractor,
- private val promptSelectorInteractor: PromptSelectorInteractor,
+ displayStateInteractor: DisplayStateInteractor,
+ promptSelectorInteractor: PromptSelectorInteractor,
private val vibrator: VibratorHelper,
@Application context: Context,
private val featureFlags: FeatureFlags,
) {
- /** Models UI of [BiometricPromptLayout.iconView] */
- val fingerprintIconViewModel: PromptFingerprintIconViewModel =
- PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor)
-
/** The set of modalities available for this prompt */
val modalities: Flow<BiometricModalities> =
promptSelectorInteractor.prompt
.map { it?.modalities ?: BiometricModalities() }
.distinctUntilChanged()
- // TODO(b/251476085): remove after icon controllers are migrated - do not keep this state
- private var _legacyState = MutableStateFlow(Spaghetti.BiometricState.STATE_IDLE)
- val legacyState: StateFlow<Spaghetti.BiometricState> = _legacyState.asStateFlow()
+ /** Layout params for fingerprint iconView */
+ val fingerprintIconWidth: Int =
+ context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_fingerprint_icon_width)
+ val fingerprintIconHeight: Int =
+ context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_fingerprint_icon_height)
+
+ /** Layout params for face iconView */
+ val faceIconWidth: Int =
+ context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
+ val faceIconHeight: Int =
+ context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)
@@ -82,6 +85,12 @@
/** If the user has successfully authenticated and confirmed (when explicitly required). */
val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()
+ /** If the auth is pending confirmation. */
+ val isPendingConfirmation: Flow<Boolean> =
+ isAuthenticated.map { authState ->
+ authState.isAuthenticated && authState.needsUserConfirmation
+ }
+
private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)
/** The kind of credential the user has. */
@@ -96,6 +105,9 @@
/** A message to show the user, if there is an error, hint, or help to show. */
val message: Flow<PromptMessage> = _message.asStateFlow()
+ /** Whether an error message is currently being shown. */
+ val showingError: Flow<Boolean> = message.map { it.isError }.distinctUntilChanged()
+
private val isRetrySupported: Flow<Boolean> = modalities.map { it.hasFace }
private val _fingerprintStartMode = MutableStateFlow(FingerprintStartMode.Pending)
@@ -141,6 +153,38 @@
!isOverlayTouched && size.isNotSmall
}
+ /**
+ * When fingerprint and face modalities are enrolled, indicates whether only face auth has
+ * started.
+ *
+ * True when fingerprint and face modalities are enrolled and implicit flow is active. This
+ * occurs in co-ex auth when confirmation is not required and only face auth is started, then
+ * becomes false when device transitions to explicit flow after a first error, when the
+ * fingerprint sensor is started.
+ *
+ * False when the dialog opens in explicit flow (fingerprint and face modalities enrolled but
+ * confirmation is required), or if user has only fingerprint enrolled, or only face enrolled.
+ */
+ val faceMode: Flow<Boolean> =
+ combine(modalities, isConfirmationRequired, fingerprintStartMode) {
+ modalities: BiometricModalities,
+ isConfirmationRequired: Boolean,
+ fingerprintStartMode: FingerprintStartMode ->
+ if (modalities.hasFaceAndFingerprint) {
+ if (isConfirmationRequired) {
+ false
+ } else {
+ !fingerprintStartMode.isStarted
+ }
+ } else {
+ false
+ }
+ }
+ .distinctUntilChanged()
+
+ val iconViewModel: PromptIconViewModel =
+ PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
+
/** Padding for prompt UI elements */
val promptPadding: Flow<Rect> =
combine(size, displayStateInteractor.currentRotation) { size, rotation ->
@@ -184,9 +228,9 @@
val isConfirmButtonVisible: Flow<Boolean> =
combine(
size,
- isAuthenticated,
- ) { size, authState ->
- size.isNotSmall && authState.isAuthenticated && authState.needsUserConfirmation
+ isPendingConfirmation,
+ ) { size, isPendingConfirmation ->
+ size.isNotSmall && isPendingConfirmation
}
.distinctUntilChanged()
@@ -293,7 +337,6 @@
_isAuthenticated.value = PromptAuthState(false)
_forceMediumSize.value = true
_message.value = PromptMessage.Error(message)
- _legacyState.value = Spaghetti.BiometricState.STATE_ERROR
if (hapticFeedback) {
vibrator.error(failedModality)
@@ -305,7 +348,7 @@
if (authenticateAfterError) {
showAuthenticating(messageAfterError)
} else {
- showInfo(messageAfterError)
+ showHelp(messageAfterError)
}
}
}
@@ -325,15 +368,12 @@
private fun supportsRetry(failedModality: BiometricModality) =
failedModality == BiometricModality.Face
- suspend fun showHelp(message: String) = showHelp(message, clearIconError = false)
- suspend fun showInfo(message: String) = showHelp(message, clearIconError = true)
-
/**
* Show a persistent help message.
*
* Will be show even if the user has already authenticated.
*/
- private suspend fun showHelp(message: String, clearIconError: Boolean) {
+ suspend fun showHelp(message: String) {
val alreadyAuthenticated = _isAuthenticated.value.isAuthenticated
if (!alreadyAuthenticated) {
_isAuthenticating.value = false
@@ -343,16 +383,6 @@
_message.value =
if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
_forceMediumSize.value = true
- _legacyState.value =
- if (alreadyAuthenticated && isConfirmationRequired.first()) {
- Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
- } else if (alreadyAuthenticated && !isConfirmationRequired.first()) {
- Spaghetti.BiometricState.STATE_AUTHENTICATED
- } else if (clearIconError) {
- Spaghetti.BiometricState.STATE_IDLE
- } else {
- Spaghetti.BiometricState.STATE_HELP
- }
messageJob?.cancel()
messageJob = null
@@ -376,7 +406,6 @@
_message.value =
if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
_forceMediumSize.value = true
- _legacyState.value = Spaghetti.BiometricState.STATE_HELP
messageJob?.cancel()
messageJob = launch {
@@ -396,7 +425,6 @@
_isAuthenticating.value = true
_isAuthenticated.value = PromptAuthState(false)
_message.value = if (message.isBlank()) PromptMessage.Empty else PromptMessage.Help(message)
- _legacyState.value = Spaghetti.BiometricState.STATE_AUTHENTICATING
// reset the try again button(s) after the user attempts a retry
if (isRetry) {
@@ -427,12 +455,6 @@
_isAuthenticated.value =
PromptAuthState(true, modality, needsUserConfirmation, dismissAfterDelay)
_message.value = PromptMessage.Empty
- _legacyState.value =
- if (needsUserConfirmation) {
- Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
- } else {
- Spaghetti.BiometricState.STATE_AUTHENTICATED
- }
if (!needsUserConfirmation) {
vibrator.success(modality)
@@ -472,7 +494,6 @@
_isAuthenticated.value = authState.asExplicitlyConfirmed()
_message.value = PromptMessage.Empty
- _legacyState.value = Spaghetti.BiometricState.STATE_AUTHENTICATED
vibrator.success(authState.authenticatedModality)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 6e26fe9..21578f4 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -28,7 +28,6 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.DejankUtils
-import com.android.systemui.res.R
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
@@ -41,8 +40,10 @@
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -73,6 +74,7 @@
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val trustRepository: TrustRepository,
@Application private val applicationScope: CoroutineScope,
+ private val selectedUserInteractor: SelectedUserInteractor,
) {
private val passiveAuthBouncerDelay =
context.resources.getInteger(R.integer.primary_bouncer_passive_auth_delay).toLong()
@@ -144,6 +146,16 @@
/** Show the bouncer if necessary and set the relevant states. */
@JvmOverloads
fun show(isScrimmed: Boolean) {
+ if (primaryBouncerView.delegate == null) {
+ Log.d(
+ TAG,
+ "PrimaryBouncerInteractor#show is being called before the " +
+ "primaryBouncerDelegate is set. Let's exit early so we don't set the wrong " +
+ "primaryBouncer state."
+ )
+ return
+ }
+
// Reset some states as we show the bouncer.
repository.setKeyguardAuthenticatedBiometrics(null)
repository.setPrimaryStartingToHide(false)
@@ -384,7 +396,7 @@
/** Returns whether the bouncer should be full screen. */
private fun needsFullscreenBouncer(): Boolean {
val mode: KeyguardSecurityModel.SecurityMode =
- keyguardSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser())
+ keyguardSecurityModel.getSecurityMode(selectedUserInteractor.getSelectedUserId())
return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
mode == KeyguardSecurityModel.SecurityMode.SimPuk
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
index 36e5db4..ac3d4b6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -26,7 +26,6 @@
import com.android.keyguard.KeyguardSecurityContainerController
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityView
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
@@ -37,6 +36,7 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.BouncerLogger
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@@ -53,6 +53,7 @@
bouncerMessageInteractor: BouncerMessageInteractor,
bouncerLogger: BouncerLogger,
featureFlags: FeatureFlags,
+ selectedUserInteractor: SelectedUserInteractor,
) {
// Builds the KeyguardSecurityContainerController from bouncer view group.
val securityContainerController: KeyguardSecurityContainerController =
@@ -84,7 +85,7 @@
override fun showNextSecurityScreenOrFinish(): Boolean {
return securityContainerController.dismiss(
- KeyguardUpdateMonitor.getCurrentUser()
+ selectedUserInteractor.getSelectedUserId()
)
}
@@ -220,7 +221,7 @@
launch {
viewModel.keyguardAuthenticated.collect {
securityContainerController.finish(
- KeyguardUpdateMonitor.getCurrentUser()
+ selectedUserInteractor.getSelectedUserId()
)
viewModel.notifyKeyguardAuthenticated()
}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index b268095..11c7a31 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -27,17 +27,16 @@
import android.os.RemoteException
import android.util.Log
import android.view.WindowManager
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -59,7 +58,7 @@
private val cameraIntents: CameraIntentsWrapper,
private val contentResolver: ContentResolver,
@Main private val uiExecutor: Executor,
- private val userTracker: UserTracker
+ private val selectedUserInteractor: SelectedUserInteractor,
) {
/**
* Whether the camera application can be launched for the camera launch gesture.
@@ -72,12 +71,12 @@
val resolveInfo: ResolveInfo? = packageManager.resolveActivityAsUser(
getStartCameraIntent(),
PackageManager.MATCH_DEFAULT_ONLY,
- KeyguardUpdateMonitor.getCurrentUser()
+ selectedUserInteractor.getSelectedUserId()
)
val resolvedPackage = resolveInfo?.activityInfo?.packageName
return (resolvedPackage != null &&
(statusBarState != StatusBarState.SHADE ||
- !activityManager.isInForeground(resolvedPackage)))
+ !activityManager.isInForeground(resolvedPackage)))
}
/**
@@ -89,7 +88,7 @@
val intent: Intent = getStartCameraIntent()
intent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source)
val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
- intent, KeyguardUpdateMonitor.getCurrentUser()
+ intent, selectedUserInteractor.getSelectedUserId()
)
if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
uiExecutor.execute {
@@ -102,7 +101,7 @@
val activityOptions = ActivityOptions.makeBasic()
activityOptions.setDisallowEnterPictureInPictureWhileLaunching(true)
activityOptions.rotationAnimationHint =
- WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+ WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
try {
activityTaskManager.startActivityAsUser(
null,
@@ -116,7 +115,7 @@
Intent.FLAG_ACTIVITY_NEW_TASK,
null,
activityOptions.toBundle(),
- userTracker.userId,
+ selectedUserInteractor.getSelectedUserId(true),
)
} catch (e: RemoteException) {
Log.w(
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 39c01f7..a6b073d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -37,12 +37,15 @@
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.sensors.ThresholdSensor;
import com.android.systemui.util.sensors.ThresholdSensorEvent;
import com.android.systemui.util.time.SystemClock;
+import dagger.Lazy;
+
import java.util.Collections;
import javax.inject.Inject;
@@ -66,6 +69,7 @@
private final DockManager mDockManager;
private final DelayableExecutor mMainExecutor;
private final SystemClock mSystemClock;
+ private final Lazy<SelectedUserInteractor> mUserInteractor;
private int mState;
private boolean mShowingAod;
@@ -93,7 +97,7 @@
public void onBiometricAuthenticated(int userId,
BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
- if (userId == KeyguardUpdateMonitor.getCurrentUser()
+ if (userId == mUserInteractor.get().getSelectedUserId()
&& biometricSourceType == BiometricSourceType.FACE) {
mFalsingDataProvider.setJustUnlockedWithFace(true);
}
@@ -136,7 +140,8 @@
BatteryController batteryController,
DockManager dockManager,
@Main DelayableExecutor mainExecutor,
- SystemClock systemClock) {
+ SystemClock systemClock,
+ Lazy<SelectedUserInteractor> userInteractor) {
mFalsingDataProvider = falsingDataProvider;
mFalsingManager = falsingManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -148,6 +153,7 @@
mDockManager = dockManager;
mMainExecutor = mainExecutor;
mSystemClock = systemClock;
+ mUserInteractor = userInteractor;
mProximitySensor.setTag(PROXIMITY_SENSOR_TAG);
mProximitySensor.setDelay(SensorManager.SENSOR_DELAY_GAME);
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
index bf4fba8..0bf5069 100644
--- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
@@ -26,11 +26,13 @@
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.types.ExtractionType;
import com.android.internal.colorextraction.types.Tonal;
-import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+
+import dagger.Lazy;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -48,19 +50,22 @@
private boolean mHasMediaArtwork;
private final GradientColors mNeutralColorsLock;
private final GradientColors mBackdropColors;
+ private Lazy<SelectedUserInteractor> mUserInteractor;
@Inject
public SysuiColorExtractor(
Context context,
ConfigurationController configurationController,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ Lazy<SelectedUserInteractor> userInteractor) {
this(
context,
new Tonal(context),
configurationController,
context.getSystemService(WallpaperManager.class),
dumpManager,
- false /* immediately */);
+ false /* immediately */,
+ userInteractor);
}
@VisibleForTesting
@@ -70,7 +75,8 @@
ConfigurationController configurationController,
WallpaperManager wallpaperManager,
DumpManager dumpManager,
- boolean immediately) {
+ boolean immediately,
+ Lazy<SelectedUserInteractor> userInteractor) {
super(context, type, immediately, wallpaperManager);
mTonal = type instanceof Tonal ? (Tonal) type : new Tonal(context);
mNeutralColorsLock = new GradientColors();
@@ -79,6 +85,7 @@
mBackdropColors = new GradientColors();
mBackdropColors.setMainColor(Color.BLACK);
+ mUserInteractor = userInteractor;
// Listen to all users instead of only the current one.
if (wallpaperManager.isWallpaperSupported()) {
@@ -100,7 +107,7 @@
@Override
public void onColorsChanged(WallpaperColors colors, int which, int userId) {
- if (userId != KeyguardUpdateMonitor.getCurrentUser()) {
+ if (userId != mUserInteractor.get().getSelectedUserId()) {
// Colors do not belong to current user, ignoring.
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
new file mode 100644
index 0000000..b8e2de4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.communal.dagger
+
+import com.android.systemui.communal.data.repository.CommunalRepositoryModule
+import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
+import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
+import dagger.Module
+
+@Module(
+ includes =
+ [
+ CommunalRepositoryModule::class,
+ CommunalTutorialRepositoryModule::class,
+ CommunalWidgetRepositoryModule::class,
+ ]
+)
+class CommunalModule
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
new file mode 100644
index 0000000..1a214ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.communal.data.model
+
+import com.android.systemui.communal.shared.model.CommunalContentSize
+
+/** Metadata for the default widgets */
+data class CommunalWidgetMetadata(
+ /* Widget provider component name */
+ val componentName: String,
+
+ /* Defines the order in which the widget will be rendered in the grid. */
+ val priority: Int,
+
+ /* Supported sizes */
+ val sizes: List<CommunalContentSize>
+)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index e2a7d07..77025dc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -20,6 +20,7 @@
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.BroadcastReceiver
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
@@ -27,95 +28,125 @@
import android.os.UserManager
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.data.model.CommunalWidgetMetadata
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
/** Encapsulates the state of widgets for communal mode. */
interface CommunalWidgetRepository {
/** A flow of provider info for the stopwatch widget, or null if widget is unavailable. */
val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?>
+
+ /** Widgets that are allowed to render in the glanceable hub */
+ val communalWidgetAllowlist: List<CommunalWidgetMetadata>
+
+ /** A flow of information about all the communal widgets to show. */
+ val communalWidgets: Flow<List<CommunalWidgetContentModel>>
}
@SysUISingleton
class CommunalWidgetRepositoryImpl
@Inject
constructor(
+ @Application private val applicationContext: Context,
private val appWidgetManager: AppWidgetManager,
private val appWidgetHost: AppWidgetHost,
broadcastDispatcher: BroadcastDispatcher,
+ communalRepository: CommunalRepository,
private val packageManager: PackageManager,
private val userManager: UserManager,
private val userTracker: UserTracker,
@CommunalLog logBuffer: LogBuffer,
- featureFlags: FeatureFlags,
+ featureFlags: FeatureFlagsClassic,
) : CommunalWidgetRepository {
companion object {
const val TAG = "CommunalWidgetRepository"
const val WIDGET_LABEL = "Stopwatch"
}
+ override val communalWidgetAllowlist: List<CommunalWidgetMetadata>
private val logger = Logger(logBuffer, TAG)
// Whether the [AppWidgetHost] is listening for updates.
private var isHostListening = false
+ init {
+ communalWidgetAllowlist =
+ if (communalRepository.isCommunalEnabled) getWidgetAllowlist() else emptyList()
+ }
+
// Widgets that should be rendered in communal mode.
private val widgets: HashMap<Int, CommunalAppWidgetInfo> = hashMapOf()
- private val isUserUnlocked: Flow<Boolean> = callbackFlow {
- if (!featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) {
- awaitClose()
- }
-
- fun isUserUnlockingOrUnlocked(): Boolean {
- return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle)
- }
-
- fun send() {
- trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG)
- }
-
- if (isUserUnlockingOrUnlocked()) {
- send()
- awaitClose()
- } else {
- val receiver =
- object : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- send()
- }
+ private val isUserUnlocked: Flow<Boolean> =
+ callbackFlow {
+ if (!communalRepository.isCommunalEnabled) {
+ awaitClose()
}
- broadcastDispatcher.registerReceiver(
- receiver,
- IntentFilter(Intent.ACTION_USER_UNLOCKED),
- )
+ fun isUserUnlockingOrUnlocked(): Boolean {
+ return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle)
+ }
- awaitClose { broadcastDispatcher.unregisterReceiver(receiver) }
+ fun send() {
+ trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG)
+ }
+
+ if (isUserUnlockingOrUnlocked()) {
+ send()
+ awaitClose()
+ } else {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ send()
+ }
+ }
+
+ broadcastDispatcher.registerReceiver(
+ receiver,
+ IntentFilter(Intent.ACTION_USER_UNLOCKED),
+ )
+
+ awaitClose { broadcastDispatcher.unregisterReceiver(receiver) }
+ }
+ }
+ .distinctUntilChanged()
+
+ private val isHostActive: Flow<Boolean> =
+ isUserUnlocked.map {
+ if (it) {
+ startListening()
+ true
+ } else {
+ stopListening()
+ clearWidgets()
+ false
+ }
}
- }
override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> =
- isUserUnlocked.map { isUserUnlocked ->
- if (!isUserUnlocked) {
- clearWidgets()
- stopListening()
+ isHostActive.map { isHostActive ->
+ if (!isHostActive || !featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) {
return@map null
}
- startListening()
-
val providerInfo =
appWidgetManager.installedProviders.find {
it.loadLabel(packageManager).equals(WIDGET_LABEL)
@@ -129,6 +160,54 @@
return@map addWidget(providerInfo)
}
+ override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
+ isHostActive.map { isHostActive ->
+ if (!isHostActive) {
+ return@map emptyList()
+ }
+
+ // The allowlist should be fetched from the local database with all the metadata tied to
+ // a widget, including an appWidgetId if it has been bound. Before the database is set
+ // up, we are going to use the app widget host as the source of truth for bound widgets,
+ // and rebind each time on boot.
+
+ // Remove all previously bound widgets.
+ appWidgetHost.appWidgetIds.forEach { appWidgetHost.deleteAppWidgetId(it) }
+
+ val inventory = mutableListOf<CommunalWidgetContentModel>()
+
+ // Bind all widgets from the allowlist.
+ communalWidgetAllowlist.forEach {
+ val id = appWidgetHost.allocateAppWidgetId()
+ appWidgetManager.bindAppWidgetId(
+ id,
+ ComponentName.unflattenFromString(it.componentName),
+ )
+
+ inventory.add(
+ CommunalWidgetContentModel(
+ appWidgetId = id,
+ providerInfo = appWidgetManager.getAppWidgetInfo(id),
+ priority = it.priority,
+ )
+ )
+ }
+
+ return@map inventory.toList()
+ }
+
+ private fun getWidgetAllowlist(): List<CommunalWidgetMetadata> {
+ val componentNames =
+ applicationContext.resources.getStringArray(R.array.config_communalWidgetAllowlist)
+ return componentNames.mapIndexed { index, name ->
+ CommunalWidgetMetadata(
+ componentName = name,
+ priority = componentNames.size - index,
+ sizes = listOf(CommunalContentSize.HALF),
+ )
+ }
+ }
+
private fun startListening() {
if (isHostListening) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 04bb6ae..6238707 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -18,7 +18,8 @@
import com.android.systemui.communal.data.repository.CommunalRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -38,4 +39,12 @@
/** A flow of info about the widget to be displayed, or null if widget is unavailable. */
val appWidgetInfo: Flow<CommunalAppWidgetInfo?> = widgetRepository.stopwatchAppWidgetInfo
+
+ /**
+ * A flow of information about widgets to be shown in communal hub.
+ *
+ * Currently only showing persistent widgets that have been bound to the app widget service
+ * (have an allocated id).
+ */
+ val widgetContent: Flow<List<CommunalWidgetContentModel>> = widgetRepository.communalWidgets
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
rename to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
index 0803a01..109ed2d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.communal.shared
+package com.android.systemui.communal.shared.model
import android.appwidget.AppWidgetProviderInfo
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
similarity index 67%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
index 0803a01..7f05b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
@@ -12,15 +12,14 @@
* 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.communal.shared
+package com.android.systemui.communal.shared.model
-import android.appwidget.AppWidgetProviderInfo
+enum class CommunalContentCategory {
+ /** The content persists in the communal hub until removed by the user. */
+ PERSISTENT,
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
- val providerInfo: AppWidgetProviderInfo,
- val appWidgetId: Int,
-)
+ /** The content temporarily shows up in the communal hub when certain conditions are met. */
+ TRANSIENT,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
index 0803a01..39a6476 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
@@ -12,15 +12,18 @@
* 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.communal.shared
+package com.android.systemui.communal.shared.model
-import android.appwidget.AppWidgetProviderInfo
+/** Supported sizes for communal content in the layout grid. */
+enum class CommunalContentSize {
+ /** Content takes the full height of the column. */
+ FULL,
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
- val providerInfo: AppWidgetProviderInfo,
- val appWidgetId: Int,
-)
+ /** Content takes half of the height of the column. */
+ HALF,
+
+ /** Content takes a third of the height of the column. */
+ THIRD,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
similarity index 80%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index 0803a01..e141dc4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -12,15 +12,15 @@
* 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.communal.shared
+package com.android.systemui.communal.shared.model
import android.appwidget.AppWidgetProviderInfo
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
- val providerInfo: AppWidgetProviderInfo,
+/** Encapsulates data for a communal widget. */
+data class CommunalWidgetContentModel(
val appWidgetId: Int,
+ val providerInfo: AppWidgetProviderInfo,
+ val priority: Int,
)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
index 2a08d7f..0daf7b5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
@@ -20,7 +20,7 @@
import android.appwidget.AppWidgetManager
import android.content.Context
import android.util.SizeF
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
import com.android.systemui.communal.ui.view.CommunalWidgetWrapper
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
new file mode 100644
index 0000000..98060dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.communal.ui.model
+
+import android.view.View
+import com.android.systemui.communal.shared.model.CommunalContentSize
+
+/**
+ * Encapsulates data for a communal content that holds a view.
+ *
+ * This model stays in the UI layer.
+ */
+data class CommunalContentUiModel(
+ val view: View,
+ val size: CommunalContentSize,
+ val priority: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt
index 932dbfb..ad02f62 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt
@@ -2,6 +2,7 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.layout.sections.removeView
@@ -9,14 +10,20 @@
import javax.inject.Inject
/** A keyguard section that hosts the communal hub. */
-class DefaultCommunalHubSection @Inject constructor() : KeyguardSection() {
+class DefaultCommunalHubSection
+@Inject
+constructor(
+ private val viewModel: CommunalViewModel,
+) : KeyguardSection() {
private val communalHubViewId = R.id.communal_hub
override fun addViews(constraintLayout: ConstraintLayout) {
constraintLayout.addView(
- ComposeFacade.createCommunalView(constraintLayout.context).apply {
- id = communalHubViewId
- },
+ ComposeFacade.createCommunalView(
+ context = constraintLayout.context,
+ viewModel = viewModel,
+ )
+ .apply { id = communalHubViewId },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
new file mode 100644
index 0000000..25c64ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.communal.ui.viewmodel
+
+import android.appwidget.AppWidgetHost
+import android.content.Context
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.model.CommunalContentUiModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class CommunalViewModel
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val appWidgetHost: AppWidgetHost,
+ communalInteractor: CommunalInteractor,
+ tutorialInteractor: CommunalTutorialInteractor,
+) {
+ /** Whether communal hub should show tutorial content. */
+ val showTutorialContent: Flow<Boolean> = tutorialInteractor.isTutorialAvailable
+
+ /** List of widgets to be displayed in the communal hub. */
+ val widgetContent: Flow<List<CommunalContentUiModel>> =
+ communalInteractor.widgetContent.map {
+ it.map {
+ // TODO(b/306406256): As adding and removing widgets functionalities are
+ // supported, cache the host views so they're not recreated each time.
+ val hostView = appWidgetHost.createView(context, it.appWidgetId, it.providerInfo)
+ return@map CommunalContentUiModel(
+ view = hostView,
+ priority = it.priority,
+ // All widgets have HALF size.
+ size = CommunalContentSize.HALF,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
index 8fba342..d7bbea6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
@@ -17,7 +17,7 @@
package com.android.systemui.communal.ui.viewmodel
import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 5c1539a..4bdea75 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -22,6 +22,7 @@
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.scene.shared.model.Scene
@@ -73,8 +74,12 @@
sceneByKey: Map<SceneKey, Scene>,
): View
- /** Create a [View] that represents the communal hub. */
+ /** Create a [View] to represent [viewModel] on screen. */
fun createCommunalView(
context: Context,
+ viewModel: CommunalViewModel,
): View
+
+ /** Creates a container that hosts the communal UI and handles gesture transitions. */
+ fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 04a9cae..d57f31f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -67,6 +67,7 @@
import android.media.AudioManager;
import android.media.IAudioService;
import android.media.MediaRouter2Manager;
+import android.media.projection.IMediaProjectionManager;
import android.media.projection.MediaProjectionManager;
import android.media.session.MediaSessionManager;
import android.net.ConnectivityManager;
@@ -414,6 +415,13 @@
}
@Provides
+ @Singleton
+ static IMediaProjectionManager provideIMediaProjectionManager() {
+ return IMediaProjectionManager.Stub.asInterface(
+ ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE));
+ }
+
+ @Provides
static MediaRouter2Manager provideMediaRouter2Manager(Context context) {
return MediaRouter2Manager.getInstance(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 7d4e1a1..f0d7592 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -42,6 +42,7 @@
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
import com.android.systemui.common.ui.data.repository.CommonRepositoryModule;
+import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.dagger.qualifiers.Main;
@@ -170,6 +171,7 @@
ClipboardOverlayModule.class,
ClockRegistryModule.class,
CommonRepositoryModule.class,
+ CommunalModule.class,
ConnectivityModule.class,
ControlsModule.class,
CoroutinesModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
index e7f835f..c3aaef7 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -1,5 +1,6 @@
package com.android.systemui.deviceentry
+import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepositoryModule
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
import dagger.Module
@@ -7,6 +8,7 @@
includes =
[
DeviceEntryRepositoryModule::class,
+ DeviceEntryHapticsRepositoryModule::class,
],
)
object DeviceEntryModule
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt
new file mode 100644
index 0000000..1458404
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.deviceentry.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Interface for classes that can access device-entry haptics application state. */
+interface DeviceEntryHapticsRepository {
+ /**
+ * Whether a successful biometric haptic has been requested. Has not yet been handled if true.
+ */
+ val successHapticRequest: Flow<Boolean>
+
+ /** Whether an error biometric haptic has been requested. Has not yet been handled if true. */
+ val errorHapticRequest: Flow<Boolean>
+
+ fun requestSuccessHaptic()
+ fun handleSuccessHaptic()
+ fun requestErrorHaptic()
+ fun handleErrorHaptic()
+}
+
+/** Encapsulates application state for device entry haptics. */
+@SysUISingleton
+class DeviceEntryHapticsRepositoryImpl @Inject constructor() : DeviceEntryHapticsRepository {
+ private val _successHapticRequest = MutableStateFlow(false)
+ override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow()
+
+ private val _errorHapticRequest = MutableStateFlow(false)
+ override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow()
+
+ override fun requestSuccessHaptic() {
+ _successHapticRequest.value = true
+ }
+
+ override fun handleSuccessHaptic() {
+ _successHapticRequest.value = false
+ }
+
+ override fun requestErrorHaptic() {
+ _errorHapticRequest.value = true
+ }
+
+ override fun handleErrorHaptic() {
+ _errorHapticRequest.value = false
+ }
+}
+
+@Module
+interface DeviceEntryHapticsRepositoryModule {
+ @Binds fun repository(impl: DeviceEntryHapticsRepositoryImpl): DeviceEntryHapticsRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
new file mode 100644
index 0000000..53d6f73
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import com.android.keyguard.logging.BiometricUnlockLogger
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepository
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/**
+ * Business logic for device entry haptic events. Determines whether the haptic should play. In
+ * particular, there are extra guards for whether device entry error and successes hatpics should
+ * play when the physical fingerprint sensor is located on the power button.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class DeviceEntryHapticsInteractor
+@Inject
+constructor(
+ private val repository: DeviceEntryHapticsRepository,
+ fingerprintPropertyRepository: FingerprintPropertyRepository,
+ biometricSettingsRepository: BiometricSettingsRepository,
+ keyEventInteractor: KeyEventInteractor,
+ powerInteractor: PowerInteractor,
+ private val systemClock: SystemClock,
+ private val logger: BiometricUnlockLogger,
+) {
+ private val powerButtonSideFpsEnrolled =
+ combineTransform(
+ fingerprintPropertyRepository.sensorType,
+ biometricSettingsRepository.isFingerprintEnrolledAndEnabled,
+ ) { sensorType, enrolledAndEnabled ->
+ if (sensorType == FingerprintSensorType.POWER_BUTTON) {
+ emit(enrolledAndEnabled)
+ } else {
+ emit(false)
+ }
+ }
+ .distinctUntilChanged()
+ private val powerButtonDown: Flow<Boolean> = keyEventInteractor.isPowerButtonDown
+ private val lastPowerButtonWakeup: Flow<Long> =
+ powerInteractor.detailedWakefulness
+ .filter { it.isAwakeFrom(WakeSleepReason.POWER_BUTTON) }
+ .map { systemClock.uptimeMillis() }
+ .onStart {
+ // If the power button hasn't been pressed, we still want this to evaluate to true:
+ // `uptimeMillis() - lastPowerButtonWakeup > recentPowerButtonPressThresholdMs`
+ emit(recentPowerButtonPressThresholdMs * -1L - 1L)
+ }
+
+ val playSuccessHaptic: Flow<Boolean> =
+ repository.successHapticRequest
+ .filter { it }
+ .sample(
+ combine(
+ powerButtonSideFpsEnrolled,
+ powerButtonDown,
+ lastPowerButtonWakeup,
+ ::Triple
+ )
+ )
+ .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
+ val sideFpsAllowsHaptic =
+ !powerButtonDown &&
+ systemClock.uptimeMillis() - lastPowerButtonWakeup >
+ recentPowerButtonPressThresholdMs
+ val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic
+ if (!allowHaptic) {
+ logger.d("Skip success haptic. Recent power button press or button is down.")
+ handleSuccessHaptic() // immediately handle, don't vibrate
+ }
+ allowHaptic
+ }
+ val playErrorHaptic: Flow<Boolean> =
+ repository.errorHapticRequest
+ .filter { it }
+ .sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair))
+ .map { (sideFpsEnrolled, powerButtonDown) ->
+ val allowHaptic = !sideFpsEnrolled || !powerButtonDown
+ if (!allowHaptic) {
+ logger.d("Skip error haptic. Power button is down.")
+ handleErrorHaptic() // immediately handle, don't vibrate
+ }
+ allowHaptic
+ }
+
+ fun vibrateSuccess() {
+ repository.requestSuccessHaptic()
+ }
+
+ fun vibrateError() {
+ repository.requestErrorHaptic()
+ }
+
+ fun handleSuccessHaptic() {
+ repository.handleSuccessHaptic()
+ }
+
+ fun handleErrorHaptic() {
+ repository.handleErrorHaptic()
+ }
+
+ private val recentPowerButtonPressThresholdMs = 400L
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index 7510cf6c..d19efbd 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -15,14 +15,12 @@
*/
package com.android.systemui.display.ui.view
-import android.app.Dialog
import android.content.Context
import android.os.Bundle
-import android.view.Gravity
import android.view.View
-import android.view.WindowManager
import android.widget.TextView
import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog
/**
* Dialog used to decide what to do with a connected display.
@@ -35,7 +33,7 @@
private val onStartMirroringClickListener: View.OnClickListener,
private val onCancelMirroring: View.OnClickListener,
theme: Int = R.style.Theme_SystemUI_Dialog,
-) : Dialog(context, theme) {
+) : SystemUIBottomSheetDialog(context, theme) {
private lateinit var mirrorButton: TextView
private lateinit var dismissButton: TextView
@@ -43,13 +41,8 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- window?.apply {
- setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
- addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
- setGravity(Gravity.BOTTOM)
- }
setContentView(R.layout.connected_display_dialog)
- setCanceledOnTouchOutside(true)
+
mirrorButton =
requireViewById<TextView>(R.id.enable_display).apply {
setOnClickListener(onStartMirroringClickListener)
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
index 5eb9808..9c13a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
@@ -18,6 +18,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.doze.dagger.DozeScope;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import javax.inject.Inject;
@@ -28,16 +29,19 @@
public class DozeAuthRemover implements DozeMachine.Part {
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final SelectedUserInteractor mSelectedUserInteractor;
@Inject
- public DozeAuthRemover(KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ public DozeAuthRemover(KeyguardUpdateMonitor keyguardUpdateMonitor,
+ SelectedUserInteractor selectedUserInteractor) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mSelectedUserInteractor = selectedUserInteractor;
}
@Override
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
if (newState == DozeMachine.State.DOZE || newState == DozeMachine.State.DOZE_AOD) {
- int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+ int currentUser = mSelectedUserInteractor.getSelectedUserId();
if (mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(currentUser)) {
mKeyguardUpdateMonitor.clearBiometricRecognized();
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 7da2cf1..ba57918 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -31,13 +31,13 @@
import androidx.annotation.Nullable;
-import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.doze.dagger.WrappedService;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.wakelock.SettableWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
@@ -81,6 +81,7 @@
@Nullable private UdfpsController mUdfpsController;
private final DozeLog mDozeLog;
private final DozeScreenBrightness mDozeScreenBrightness;
+ private final SelectedUserInteractor mSelectedUserInteractor;
private int mPendingScreenState = Display.STATE_UNKNOWN;
private SettableWakeLock mWakeLock;
@@ -95,7 +96,8 @@
AuthController authController,
Provider<UdfpsController> udfpsControllerProvider,
DozeLog dozeLog,
- DozeScreenBrightness dozeScreenBrightness) {
+ DozeScreenBrightness dozeScreenBrightness,
+ SelectedUserInteractor selectedUserInteractor) {
mDozeService = service;
mHandler = handler;
mParameters = parameters;
@@ -105,6 +107,7 @@
mUdfpsControllerProvider = udfpsControllerProvider;
mDozeLog = dozeLog;
mDozeScreenBrightness = dozeScreenBrightness;
+ mSelectedUserInteractor = selectedUserInteractor;
updateUdfpsController();
if (mUdfpsController == null) {
@@ -113,7 +116,7 @@
}
private void updateUdfpsController() {
- if (mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
+ if (mAuthController.isUdfpsEnrolled(mSelectedUserInteractor.getSelectedUserId())) {
mUdfpsController = mUdfpsControllerProvider.get();
} else {
mUdfpsController = null;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 07efbfe..3194942 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -48,12 +48,11 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.settings.SecureSettings;
@@ -101,7 +100,7 @@
private final SecureSettings mSecureSettings;
private final DevicePostureController mDevicePostureController;
private final AuthController mAuthController;
- private final UserTracker mUserTracker;
+ private final SelectedUserInteractor mSelectedUserInteractor;
private final boolean mScreenOffUdfpsEnabled;
// Sensors
@@ -158,7 +157,7 @@
SecureSettings secureSettings,
AuthController authController,
DevicePostureController devicePostureController,
- UserTracker userTracker
+ SelectedUserInteractor selectedUserInteractor
) {
mSensorManager = sensorManager;
mConfig = config;
@@ -171,15 +170,15 @@
mProximitySensor.setTag(TAG);
mSelectivelyRegisterProxSensors = dozeParameters.getSelectivelyRegisterSensorsUsingProx();
mListeningProxSensors = !mSelectivelyRegisterProxSensors;
+ mSelectedUserInteractor = selectedUserInteractor;
mScreenOffUdfpsEnabled =
- config.screenOffUdfpsEnabled(KeyguardUpdateMonitor.getCurrentUser());
+ config.screenOffUdfpsEnabled(mSelectedUserInteractor.getSelectedUserId());
mDevicePostureController = devicePostureController;
mDevicePosture = mDevicePostureController.getDevicePosture();
mAuthController = authController;
- mUserTracker = userTracker;
mUdfpsEnrolled =
- mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser());
+ mAuthController.isUdfpsEnrolled(mSelectedUserInteractor.getSelectedUserId());
mAuthController.addCallback(mAuthControllerCallback);
mTriggerSensors = new TriggerSensor[] {
new TriggerSensor(
@@ -255,7 +254,8 @@
new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY),
Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
mConfig.wakeScreenGestureAvailable()
- && mConfig.alwaysOnEnabled(mUserTracker.getUserId()),
+ && mConfig.alwaysOnEnabled(
+ mSelectedUserInteractor.getSelectedUserId(true)),
DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE,
false /* reports touch coordinates */,
false /* touchscreen */
@@ -296,12 +296,13 @@
private boolean udfpsLongPressConfigured() {
return mUdfpsEnrolled
- && (mConfig.alwaysOnEnabled(mUserTracker.getUserId()) || mScreenOffUdfpsEnabled);
+ && (mConfig.alwaysOnEnabled(mSelectedUserInteractor.getSelectedUserId(true))
+ || mScreenOffUdfpsEnabled);
}
private boolean quickPickUpConfigured() {
return mUdfpsEnrolled
- && mConfig.quickPickupSensorEnabled(KeyguardUpdateMonitor.getCurrentUser());
+ && mConfig.quickPickupSensorEnabled(mSelectedUserInteractor.getSelectedUserId());
}
/**
@@ -471,7 +472,7 @@
private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) {
- if (userId != mUserTracker.getUserId()) {
+ if (userId != mSelectedUserInteractor.getSelectedUserId(true)) {
return;
}
for (TriggerSensor s : mTriggerSensors) {
@@ -697,13 +698,13 @@
}
protected boolean enabledBySetting() {
- if (!mConfig.enabled(mUserTracker.getUserId())) {
+ if (!mConfig.enabled(mSelectedUserInteractor.getSelectedUserId(true))) {
return false;
} else if (TextUtils.isEmpty(mSetting)) {
return true;
}
return mSecureSettings.getIntForUser(mSetting, mSettingDefault ? 1 : 0,
- mUserTracker.getUserId()) != 0;
+ mSelectedUserInteractor.getSelectedUserId(true)) != 0;
}
@Override
@@ -873,7 +874,7 @@
private void updateUdfpsEnrolled() {
mUdfpsEnrolled = mAuthController.isUdfpsEnrolled(
- KeyguardUpdateMonitor.getCurrentUser());
+ mSelectedUserInteractor.getSelectedUserId());
for (TriggerSensor sensor : mTriggerSensors) {
if (REASON_SENSOR_QUICK_PICKUP == sensor.mPulseReason) {
sensor.setConfigured(quickPickUpConfigured());
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 85272a6..795c3d4 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -50,6 +50,7 @@
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.Assert;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.ProximityCheck;
@@ -102,6 +103,7 @@
private final AuthController mAuthController;
private final KeyguardStateController mKeyguardStateController;
private final UserTracker mUserTracker;
+ private final SelectedUserInteractor mSelectedUserInteractor;
private final UiEventLogger mUiEventLogger;
private long mNotificationPulseTime;
@@ -201,7 +203,8 @@
SessionTracker sessionTracker,
KeyguardStateController keyguardStateController,
DevicePostureController devicePostureController,
- UserTracker userTracker) {
+ UserTracker userTracker,
+ SelectedUserInteractor selectedUserInteractor) {
mContext = context;
mDozeHost = dozeHost;
mConfig = config;
@@ -213,7 +216,7 @@
mDozeSensors = new DozeSensors(mContext.getResources(), mSensorManager, dozeParameters,
config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
- secureSettings, authController, devicePostureController, userTracker);
+ secureSettings, authController, devicePostureController, selectedUserInteractor);
mDockManager = dockManager;
mProxCheck = proxCheck;
mDozeLog = dozeLog;
@@ -222,6 +225,7 @@
mUiEventLogger = uiEventLogger;
mKeyguardStateController = keyguardStateController;
mUserTracker = userTracker;
+ mSelectedUserInteractor = selectedUserInteractor;
}
@Override
@@ -246,7 +250,7 @@
return;
}
mNotificationPulseTime = SystemClock.elapsedRealtime();
- if (!mConfig.pulseOnNotificationEnabled(mUserTracker.getUserId())) {
+ if (!mConfig.pulseOnNotificationEnabled(mSelectedUserInteractor.getSelectedUserId(true))) {
runIfNotNull(onPulseSuppressedListener);
mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled");
return;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
index 6946950..1c2ff4b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
@@ -36,6 +36,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.systemui.FeatureFlags;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.util.settings.GlobalSettings;
@@ -80,6 +81,7 @@
private final Map<String, Boolean> mBooleanFlagCache = new ConcurrentHashMap<>();
private final Map<String, String> mStringFlagCache = new ConcurrentHashMap<>();
private final Map<String, Integer> mIntFlagCache = new ConcurrentHashMap<>();
+ private final FeatureFlags mGantryFlags;
private final Restarter mRestarter;
private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
@@ -124,6 +126,7 @@
@Main Resources resources,
ServerFlagReader serverFlagReader,
@Named(ALL_FLAGS) Map<String, Flag<?>> allFlags,
+ FeatureFlags gantryFlags,
Restarter restarter) {
mFlagManager = flagManager;
mContext = context;
@@ -132,6 +135,7 @@
mSystemProperties = systemProperties;
mServerFlagReader = serverFlagReader;
mAllFlags = allFlags;
+ mGantryFlags = gantryFlags;
mRestarter = restarter;
}
@@ -259,9 +263,8 @@
if (!hasServerOverride
&& !defaultValue
&& result == null
- && !flag.getName().equals(Flags.TEAMFOOD.getName())
&& flag.getTeamfood()) {
- return isEnabled(Flags.TEAMFOOD);
+ return mGantryFlags.sysuiTeamfood();
}
return result == null ? mServerFlagReader.readServerOverride(
@@ -534,7 +537,7 @@
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("can override: true");
-
+ pw.println("teamfood: " + mGantryFlags.sysuiTeamfood());
pw.println("booleans: " + mBooleanFlagCache.size());
// Sort our flags for dumping
TreeMap<String, Boolean> dumpBooleanMap = new TreeMap<>(mBooleanFlagCache);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 11ac39f..c91c9ac 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -36,7 +36,10 @@
* See [FeatureFlagsClassicDebug] for instructions on flipping the flags via adb.
*/
object Flags {
- @JvmField val TEAMFOOD = unreleasedFlag("teamfood")
+ // IGNORE ME!
+ // Because flags are static, we need an ever-present flag to reference in some of the internal
+ // code that ensure that other flags are referenced and available.
+ @JvmField val NULL_FLAG = unreleasedFlag("null_flag")
// 100 - notification
// TODO(b/297792660): Tracking Bug
@@ -177,6 +180,10 @@
@JvmField
val NEW_AOD_TRANSITION = unreleasedFlag("new_aod_transition", teamfood = true)
+ // TODO(b/305984787):
+ @JvmField
+ val REFACTOR_GETCURRENTUSER = unreleasedFlag("refactor_getcurrentuser", teamfood = true)
+
/** Flag to control the migration of face auth to modern architecture. */
// TODO(b/262838215): Tracking bug
@JvmField val FACE_AUTH_REFACTOR = releasedFlag("face_auth_refactor")
@@ -401,6 +408,9 @@
@JvmField val SIGNAL_CALLBACK_DEPRECATION =
unreleasedFlag("signal_callback_deprecation", teamfood = true)
+ // TODO(b/301610137): Tracking bug
+ @JvmField val NEW_NETWORK_SLICE_UI = unreleasedFlag("new_network_slice_ui", teamfood = true)
+
// TODO(b/265892345): Tracking Bug
val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip")
@@ -413,7 +423,7 @@
releasedFlag("incompatible_charging_battery_icon")
// TODO(b/293585143): Tracking Bug
- val INSTANT_TETHER = unreleasedFlag("instant_tether")
+ val INSTANT_TETHER = unreleasedFlag("instant_tether", teamfood = true)
// TODO(b/294588085): Tracking Bug
val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks")
@@ -497,10 +507,9 @@
@Keep
@JvmField
val WM_ENABLE_PARTIAL_SCREEN_SHARING =
- unreleasedFlag(
- name = "record_task_content",
+ releasedFlag(
+ name = "enable_record_task_content",
namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- teamfood = true
)
// TODO(b/254512674): Tracking Bug
@@ -620,7 +629,7 @@
/** TODO(b/295143676): Tracking bug. When enable, captures a screenshot for each display. */
@JvmField
- val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot")
+ val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot", teamfood = true)
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
@@ -764,11 +773,10 @@
// TODO(b/283740863): Tracking Bug
@JvmField
- val ENABLE_NEW_PRIVACY_DIALOG =
- unreleasedFlag("enable_new_privacy_dialog", teamfood = true)
+ val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog")
// TODO(b/289573946): Tracking Bug
- @JvmField val PRECOMPUTED_TEXT = unreleasedFlag("precomputed_text", teamfood = true)
+ @JvmField val PRECOMPUTED_TEXT = releasedFlag("precomputed_text")
// TODO(b/302087895): Tracking Bug
@JvmField val CALL_LAYOUT_ASYNC_SET_DATA = unreleasedFlag("call_layout_async_set_data")
@@ -791,8 +799,7 @@
/** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */
@JvmField
- val ENABLE_CLOCK_KEYGUARD_PRESENTATION =
- unreleasedFlag("enable_clock_keyguard_presentation", teamfood = true)
+ val ENABLE_CLOCK_KEYGUARD_PRESENTATION = releasedFlag("enable_clock_keyguard_presentation")
/** Enable the Compose implementation of the PeopleSpaceActivity. */
@JvmField
@@ -812,8 +819,7 @@
// TODO(b/287205379): Tracking bug
@JvmField
- val QS_CONTAINER_GRAPH_OPTIMIZER = unreleasedFlag( "qs_container_graph_optimizer",
- teamfood = true)
+ val QS_CONTAINER_GRAPH_OPTIMIZER = releasedFlag( "qs_container_graph_optimizer")
/** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
index 3fe6806..7ccc26c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
@@ -22,11 +22,11 @@
/**
* This class promotes best practices for flag guarding System UI view refactors.
* * [isEnabled] allows changing an implementation.
- * * [assertDisabled] allows authors to flag code as being "dead" when the flag gets enabled and
+ * * [assertInLegacyMode] allows authors to flag code as being "dead" when the flag gets enabled and
* ensure that it is not being invoked accidentally in the post-flag refactor.
- * * [expectEnabled] allows authors to guard new code with a "safe" alternative when invoked on
- * flag-disabled builds, but with a check that should crash eng builds or tests when the
- * expectation is violated.
+ * * [isUnexpectedlyInLegacyMode] allows authors to guard new code with a "safe" alternative when
+ * invoked on flag-disabled builds, but with a check that should crash eng builds or tests when
+ * the expectation is violated.
*
* The constructors require that you provide a [FeatureFlags] instance. If you're using this in a
* View class, it's acceptable to ue the [forView] constructor methods, which do not require one,
@@ -60,13 +60,13 @@
* Example usage:
* ```
* public void setController(NotificationShelfController notificationShelfController) {
- * mShelfRefactor.assertDisabled();
+ * mShelfRefactor.assertInLegacyMode();
* mController = notificationShelfController;
* }
* ````
*/
- fun assertDisabled() =
- check(!isEnabled) { "Code path not supported when $flagName is enabled." }
+ fun assertInLegacyMode() =
+ check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." }
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
@@ -76,18 +76,17 @@
* Example usage:
* ```
* public void setShelfIcons(NotificationIconContainer icons) {
- * if (mShelfRefactor.expectEnabled()) {
- * mShelfIcons = icons;
- * }
+ * if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
+ * mShelfIcons = icons;
* }
* ```
*/
- fun expectEnabled(): Boolean {
+ fun isUnexpectedlyInLegacyMode(): Boolean {
if (!isEnabled) {
- val message = "Code path not supported when $flagName is disabled."
+ val message = "New code path expects $flagName to be enabled."
Log.wtf(TAG, message, Exception(message))
}
- return isEnabled
+ return !isEnabled
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index c6c1f79..5cc2e0a 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -134,6 +134,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.EmergencyDialerConstants;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.settings.GlobalSettings;
@@ -195,6 +196,7 @@
private final IDreamManager mDreamManager;
private final DevicePolicyManager mDevicePolicyManager;
private final LockPatternUtils mLockPatternUtils;
+ private final SelectedUserInteractor mSelectedUserInteractor;
private final TelephonyListenerManager mTelephonyListenerManager;
private final KeyguardStateController mKeyguardStateController;
private final BroadcastDispatcher mBroadcastDispatcher;
@@ -364,7 +366,8 @@
PackageManager packageManager,
ShadeController shadeController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- DialogLaunchAnimator dialogLaunchAnimator) {
+ DialogLaunchAnimator dialogLaunchAnimator,
+ SelectedUserInteractor selectedUserInteractor) {
mContext = context;
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
@@ -399,6 +402,7 @@
mShadeController = shadeController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mDialogLaunchAnimator = dialogLaunchAnimator;
+ mSelectedUserInteractor = selectedUserInteractor;
// receive broadcasts
IntentFilter filter = new IntentFilter();
@@ -713,7 +717,8 @@
mUiEventLogger,
mShadeController,
mKeyguardUpdateMonitor,
- mLockPatternUtils);
+ mLockPatternUtils,
+ mSelectedUserInteractor);
dialog.setOnDismissListener(this);
dialog.setOnShowListener(this);
@@ -2222,6 +2227,7 @@
private GestureDetector mGestureDetector;
private final ShadeController mShadeController;
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private SelectedUserInteractor mSelectedUserInteractor;
private LockPatternUtils mLockPatternUtils;
private float mWindowDimAmount;
@@ -2300,7 +2306,8 @@
UiEventLogger uiEventLogger,
ShadeController shadeController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- LockPatternUtils lockPatternUtils) {
+ LockPatternUtils lockPatternUtils,
+ SelectedUserInteractor selectedUserInteractor) {
// We set dismissOnDeviceLock to false because we have a custom broadcast receiver to
// dismiss this dialog when the device is locked.
super(context, themeRes, false /* dismissOnDeviceLock */);
@@ -2321,6 +2328,7 @@
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mGestureDetector = new GestureDetector(mContext, mGestureListener);
+ mSelectedUserInteractor = selectedUserInteractor;
}
@Override
@@ -2453,10 +2461,10 @@
}
// If user entered from the lock screen and smart lock was enabled, disable it
- int user = KeyguardUpdateMonitor.getCurrentUser();
+ int user = mSelectedUserInteractor.getSelectedUserId();
boolean userHasTrust = mKeyguardUpdateMonitor.getUserHasTrust(user);
if (mKeyguardShowing && userHasTrust) {
- mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
+ mLockPatternUtils.requireCredentialEntry(user);
showSmartLockDisabledMessage();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
index 20d99d1..7b33e11 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
@@ -32,6 +32,8 @@
@FloatRange(from = 0.0, to = 1.0) val additionalVelocityMaxBump: Float = 0.15f,
/** Additional time delta to wait between drag texture vibrations */
@FloatRange(from = 0.0) val deltaMillisForDragInterval: Float = 0f,
+ /** Progress threshold beyond which a new drag texture is delivered */
+ @FloatRange(from = 0.0, to = 1.0) val deltaProgressForDragThreshold: Float = 0.015f,
/** Number of low ticks in a drag texture composition. This is not expected to change */
val numberOfLowTicks: Int = 5,
/** Maximum velocity allowed for vibration scaling. This is not expected to change. */
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
index e6de156..f313fb3 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
@@ -46,6 +46,8 @@
private val positionAccelerateInterpolator =
AccelerateInterpolator(config.progressInterpolatorFactor)
private var dragTextureLastTime = clock.elapsedRealtime()
+ var dragTextureLastProgress = -1f
+ private set
private val lowTickDurationMs =
vibratorHelper.getPrimitiveDurations(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)[0]
private var hasVibratedAtLowerBookend = false
@@ -91,6 +93,9 @@
val elapsedSinceLastDrag = currentTime - dragTextureLastTime
if (elapsedSinceLastDrag < thresholdUntilNextDragCallMillis) return
+ val deltaProgress = abs(normalizedSliderProgress - dragTextureLastProgress)
+ if (deltaProgress < config.deltaProgressForDragThreshold) return
+
val velocityInterpolated =
velocityAccelerateInterpolator.getInterpolation(
min(absoluteVelocity / config.maxVelocityToScale, 1f)
@@ -116,11 +121,14 @@
}
vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
dragTextureLastTime = currentTime
+ dragTextureLastProgress = normalizedSliderProgress
}
override fun onHandleAcquiredByTouch() {}
- override fun onHandleReleasedFromTouch() {}
+ override fun onHandleReleasedFromTouch() {
+ dragTextureLastProgress = -1f
+ }
override fun onLowerBookend() {
if (!hasVibratedAtLowerBookend) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index b45613e..4779895 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -35,6 +35,8 @@
import static android.view.WindowManager.TransitionOldType;
import static android.view.WindowManager.TransitionType;
+import static com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER;
+
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -76,13 +78,13 @@
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.power.shared.model.ScreenPowerState;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel;
import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel;
import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.power.shared.model.ScreenPowerState;
import com.android.systemui.settings.DisplayTracker;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -599,11 +601,18 @@
mKeyguardViewMediator.setSwitchingUser(switching);
}
+ /**
+ * @deprecated This binder call is not listened to anymore. Instead the current user is
+ * tracked in SelectedUserInteractor.getSelectedUserId()
+ */
@Override // Binder interface
+ @Deprecated
public void setCurrentUser(int userId) {
- trace("setCurrentUser userId=" + userId);
+ trace("Deprecated/NOT USED: setCurrentUser userId=" + userId);
checkPermission();
- mKeyguardViewMediator.setCurrentUser(userId);
+ if (!mFlags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+ mKeyguardViewMediator.setCurrentUser(userId);
+ }
}
@Override // Binder interface
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 86bf368..119ade4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.view.LayoutInflater
import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
import com.android.keyguard.KeyguardStatusView
import com.android.keyguard.KeyguardStatusViewController
import com.android.keyguard.LockIconView
@@ -27,6 +28,7 @@
import com.android.keyguard.dagger.KeyguardStatusViewComponent
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
@@ -43,6 +45,7 @@
import com.android.systemui.shade.NotificationShadeWindowView
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import javax.inject.Inject
@@ -71,6 +74,9 @@
private val keyguardIndicationController: KeyguardIndicationController,
private val lockIconViewController: LockIconViewController,
private val shadeInteractor: ShadeInteractor,
+ private val interactionJankMonitor: InteractionJankMonitor,
+ private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
+ private val vibratorHelper: VibratorHelper,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -140,6 +146,9 @@
keyguardStateController,
shadeInteractor,
{ keyguardStatusViewController!!.getClockController() },
+ interactionJankMonitor,
+ deviceEntryHapticsInteractor,
+ vibratorHelper,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 39742a0..e893c63 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -38,6 +38,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER;
import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
@@ -163,6 +164,7 @@
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.SecureSettings;
@@ -617,6 +619,9 @@
public void onUserSwitching(int userId) {
if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
synchronized (KeyguardViewMediator.this) {
+ if (mFeatureFlags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+ notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
+ }
resetKeyguardDonePendingLocked();
dismiss(null /* callback */, null /* message */);
adjustStatusBarLocked();
@@ -742,7 +747,7 @@
@Override
public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
- final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+ final int currentUser = mSelectedUserInteractor.getSelectedUserId();
if (mLockPatternUtils.isSecure(currentUser)) {
mLockPatternUtils.getDevicePolicyManager().reportFailedBiometricAttempt(
currentUser);
@@ -760,7 +765,7 @@
@Override
public void onTrustChanged(int userId) {
- if (userId == KeyguardUpdateMonitor.getCurrentUser()) {
+ if (userId == mSelectedUserInteractor.getSelectedUserId()) {
synchronized (KeyguardViewMediator.this) {
notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
}
@@ -769,7 +774,7 @@
@Override
public void onStrongAuthStateChanged(int userId) {
- if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
+ if (mLockPatternUtils.isUserInLockdown(mSelectedUserInteractor.getSelectedUserId())) {
doKeyguardLocked(null);
}
}
@@ -784,7 +789,7 @@
@Override
public void keyguardDone(int targetUserId) {
- if (targetUserId != KeyguardUpdateMonitor.getCurrentUser()) {
+ if (targetUserId != mSelectedUserInteractor.getSelectedUserId()) {
return;
}
if (DEBUG) Log.d(TAG, "keyguardDone");
@@ -807,7 +812,7 @@
public void keyguardDonePending(int targetUserId) {
Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending");
if (DEBUG) Log.d(TAG, "keyguardDonePending");
- if (targetUserId != KeyguardUpdateMonitor.getCurrentUser()) {
+ if (targetUserId != mSelectedUserInteractor.getSelectedUserId()) {
Trace.endSection();
return;
}
@@ -888,7 +893,7 @@
@Override
public int getBouncerPromptReason() {
- int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+ int currentUser = mSelectedUserInteractor.getSelectedUserId();
boolean trustAgentsEnabled = mUpdateMonitor.isTrustUsuallyManaged(currentUser);
boolean biometricsEnrolled =
mUpdateMonitor.isUnlockingWithBiometricsPossible(currentUser);
@@ -1316,6 +1321,7 @@
private DeviceConfigProxy mDeviceConfig;
private DozeParameters mDozeParameters;
+ private SelectedUserInteractor mSelectedUserInteractor;
private final KeyguardStateController mKeyguardStateController;
private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
@@ -1396,7 +1402,8 @@
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
SystemPropertiesHelper systemPropertiesHelper,
- Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
+ Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
+ SelectedUserInteractor selectedUserInteractor) {
mContext = context;
mUserTracker = userTracker;
mFalsingCollector = falsingCollector;
@@ -1436,6 +1443,7 @@
mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
}));
mDozeParameters = dozeParameters;
+ mSelectedUserInteractor = selectedUserInteractor;
mStatusBarStateController = statusBarStateController;
statusBarStateController.addCallback(this);
@@ -1493,14 +1501,17 @@
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId());
+ if (!mFeatureFlags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+ KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId());
+ }
// Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard
// is disabled.
if (isKeyguardServiceEnabled()) {
setShowingLocked(!shouldWaitForProvisioning()
&& !mLockPatternUtils.isLockScreenDisabled(
- KeyguardUpdateMonitor.getCurrentUser()), true /* forceCallbacks */);
+ mSelectedUserInteractor.getSelectedUserId()),
+ true /* forceCallbacks */);
} else {
// The system's keyguard is disabled or missing.
setShowingLocked(false /* showing */, true /* forceCallbacks */);
@@ -1622,11 +1633,11 @@
// Lock immediately based on setting if secure (user has a pin/pattern/password).
// This also "locks" the device when not secure to provide easy access to the
// camera while preventing unwanted input.
- int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+ int currentUser = mSelectedUserInteractor.getSelectedUserId();
final boolean lockImmediately =
mLockPatternUtils.getPowerButtonInstantlyLocks(currentUser)
|| !mLockPatternUtils.isSecure(currentUser);
- long timeout = getLockTimeout(KeyguardUpdateMonitor.getCurrentUser());
+ long timeout = getLockTimeout(mSelectedUserInteractor.getSelectedUserId());
mLockLater = false;
if (mShowing && !mKeyguardStateController.isKeyguardGoingAway()) {
// If we are going to sleep but the keyguard is showing (and will continue to be
@@ -1807,7 +1818,7 @@
}
private void doKeyguardLaterLocked() {
- long timeout = getLockTimeout(KeyguardUpdateMonitor.getCurrentUser());
+ long timeout = getLockTimeout(mSelectedUserInteractor.getSelectedUserId());
if (timeout == 0) {
doKeyguardLocked(null);
} else {
@@ -1916,7 +1927,7 @@
private void maybeSendUserPresentBroadcast() {
if (mSystemReady && mLockPatternUtils.isLockScreenDisabled(
- KeyguardUpdateMonitor.getCurrentUser())) {
+ mSelectedUserInteractor.getSelectedUserId())) {
// Lock screen is disabled because the user has set the preference to "None".
// In this case, send out ACTION_USER_PRESENT here instead of in
// handleKeyguardDone()
@@ -1925,7 +1936,7 @@
// Skipping the lockscreen because we're not yet provisioned, but we still need to
// notify the StrongAuthTracker that it's now safe to run trust agents, in case the
// user sets a credential later.
- mLockPatternUtils.userPresent(KeyguardUpdateMonitor.getCurrentUser());
+ mLockPatternUtils.userPresent(mSelectedUserInteractor.getSelectedUserId());
}
}
@@ -1966,7 +1977,8 @@
mExternallyEnabled = enabled;
if (!enabled && mShowing) {
- if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
+ if (mLockPatternUtils.isUserInLockdown(
+ mSelectedUserInteractor.getSelectedUserId())) {
Log.d(TAG, "keyguardEnabled(false) overridden by user lockdown");
return;
}
@@ -2197,7 +2209,8 @@
private void doKeyguardLocked(Bundle options) {
// if another app is disabling us, don't show
if (!mExternallyEnabled
- && !mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
+ && !mLockPatternUtils.isUserInLockdown(
+ mSelectedUserInteractor.getSelectedUserId())) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
mNeedToReshowWhenReenabled = true;
@@ -2253,7 +2266,7 @@
}
boolean forceShow = options != null && options.getBoolean(OPTION_FORCE_SHOW, false);
- if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser())
+ if (mLockPatternUtils.isLockScreenDisabled(mSelectedUserInteractor.getSelectedUserId())
&& !lockedOrMissing && !forceShow) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
return;
@@ -2384,7 +2397,7 @@
}
public boolean isSecure() {
- return isSecure(KeyguardUpdateMonitor.getCurrentUser());
+ return isSecure(mSelectedUserInteractor.getSelectedUserId());
}
public boolean isSecure(int userId) {
@@ -2605,7 +2618,7 @@
*/
private void handleKeyguardDone() {
Trace.beginSection("KeyguardViewMediator#handleKeyguardDone");
- final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+ final int currentUser = mSelectedUserInteractor.getSelectedUserId();
mUiBgExecutor.execute(() -> {
if (mLockPatternUtils.isSecure(currentUser)) {
mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(currentUser);
@@ -2631,7 +2644,7 @@
private void sendUserPresentBroadcast() {
synchronized (this) {
if (mBootCompleted) {
- int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
+ int currentUserId = mSelectedUserInteractor.getSelectedUserId();
final UserHandle currentUser = new UserHandle(currentUserId);
final UserManager um = (UserManager) mContext.getSystemService(
Context.USER_SERVICE);
@@ -2679,7 +2692,7 @@
private void playSound(int soundId) {
if (soundId == 0) return;
int lockscreenSoundsEnabled = mSystemSettings.getIntForUser(LOCKSCREEN_SOUNDS_ENABLED, 1,
- KeyguardUpdateMonitor.getCurrentUser());
+ mSelectedUserInteractor.getSelectedUserId());
if (lockscreenSoundsEnabled == 1) {
mLockSounds.stop(mLockSoundStreamId);
@@ -2732,7 +2745,7 @@
*/
private void handleShow(Bundle options) {
Trace.beginSection("KeyguardViewMediator#handleShow");
- final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+ final int currentUser = mSelectedUserInteractor.getSelectedUserId();
if (mLockPatternUtils.isSecure(currentUser)) {
mLockPatternUtils.getDevicePolicyManager().reportKeyguardSecured(currentUser);
}
@@ -2787,7 +2800,7 @@
* Schedule 4-hour idle timeout for non-strong biometrics when the device is locked
*/
private void scheduleNonStrongBiometricIdleTimeout() {
- final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+ final int currentUser = mSelectedUserInteractor.getSelectedUserId();
// If unlocking with non-strong (i.e. weak or convenience) biometrics is possible, schedule
// 4hr idle timeout after which non-strong biometrics can't be used to unlock device until
// unlocking with strong biometric or primary auth (i.e. PIN/pattern/password)
@@ -3378,7 +3391,8 @@
if (forceClearFlags) {
try {
mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
- mContext.getPackageName(), mUserTracker.getUserId());
+ mContext.getPackageName(),
+ mSelectedUserInteractor.getSelectedUserId(true));
} catch (RemoteException e) {
Log.d(TAG, "Failed to force clear flags", e);
}
@@ -3405,7 +3419,8 @@
try {
mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
- mContext.getPackageName(), mUserTracker.getUserId());
+ mContext.getPackageName(),
+ mSelectedUserInteractor.getSelectedUserId(true));
} catch (RemoteException e) {
Log.d(TAG, "Failed to set disable flags: " + flags, e);
}
@@ -3728,7 +3743,8 @@
for (int i = size - 1; i >= 0; i--) {
IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i);
try {
- callback.onShowingStateChanged(showing, KeyguardUpdateMonitor.getCurrentUser());
+ callback.onShowingStateChanged(showing,
+ mSelectedUserInteractor.getSelectedUserId());
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call onShowingStateChanged", e);
if (e instanceof DeadObjectException) {
@@ -3771,10 +3787,11 @@
mKeyguardStateCallbacks.add(callback);
try {
callback.onSimSecureStateChanged(mUpdateMonitor.isSimPinSecure());
- callback.onShowingStateChanged(mShowing, KeyguardUpdateMonitor.getCurrentUser());
+ callback.onShowingStateChanged(mShowing,
+ mSelectedUserInteractor.getSelectedUserId());
callback.onInputRestrictedStateChanged(mInputRestricted);
callback.onTrustedChanged(mUpdateMonitor.getUserHasTrust(
- KeyguardUpdateMonitor.getCurrentUser()));
+ mSelectedUserInteractor.getSelectedUserId()));
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call to IKeyguardStateCallback", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 081edd1..8b93b17 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -38,9 +38,6 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingModule;
-import com.android.systemui.communal.data.repository.CommunalRepositoryModule;
-import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule;
-import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -71,6 +68,7 @@
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.SecureSettings;
@@ -96,9 +94,6 @@
KeyguardStatusViewComponent.class,
KeyguardUserSwitcherComponent.class},
includes = {
- CommunalRepositoryModule.class,
- CommunalTutorialRepositoryModule.class,
- CommunalWidgetRepositoryModule.class,
FalsingModule.class,
KeyguardDataQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
@@ -156,7 +151,8 @@
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
SystemPropertiesHelper systemPropertiesHelper,
- Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
+ Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
+ SelectedUserInteractor selectedUserInteractor) {
return new KeyguardViewMediator(
context,
uiEventLogger,
@@ -200,7 +196,8 @@
mainDispatcher,
dreamingToLockscreenTransitionViewModel,
systemPropertiesHelper,
- wmLockscreenVisibilityManager);
+ wmLockscreenVisibilityManager,
+ selectedUserInteractor);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index e8740a4..654f2d1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -90,7 +90,7 @@
* If the current user can use face auth to enter the device. This is true when the user has
* face auth enrolled, and is enabled in settings/device policy.
*/
- val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
+ val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean>
/**
* If the current user can use face auth to enter the device right now. This is true when
@@ -348,10 +348,11 @@
.and(isFingerprintBiometricAllowed)
.stateIn(scope, SharingStarted.Eagerly, false)
- override val isFaceAuthEnrolledAndEnabled: Flow<Boolean> =
+ override val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean> =
isFaceAuthenticationEnabled
.and(isFaceEnrolled)
.and(mobileConnectionsRepository.isAnySimSecure.isFalse())
+ .stateIn(scope, SharingStarted.Eagerly, false)
override val isFaceAuthCurrentlyAllowed: Flow<Boolean> =
isFaceAuthEnrolledAndEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index a4a3126..2dc4908 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -31,7 +31,6 @@
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.dreams.DreamOverlayCallbackController
-import com.android.systemui.keyguard.ScreenLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DismissAction
@@ -41,9 +40,6 @@
import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.phone.BiometricUnlockController
-import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
-import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -111,6 +107,8 @@
/** Is the always-on display available to be used? */
val isAodAvailable: Flow<Boolean>
+ fun setAodAvailable(value: Boolean)
+
/**
* Observable for whether we are in doze state.
*
@@ -160,6 +158,8 @@
/** Observable for biometric unlock modes */
val biometricUnlockState: Flow<BiometricUnlockModel>
+ fun setBiometricUnlockState(value: BiometricUnlockModel)
+
/** Approximate location on the screen of the fingerprint sensor. */
val fingerprintSensorLocation: Flow<Point?>
@@ -240,12 +240,9 @@
@Inject
constructor(
statusBarStateController: StatusBarStateController,
- screenLifecycle: ScreenLifecycle,
- biometricUnlockController: BiometricUnlockController,
private val keyguardStateController: KeyguardStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dozeTransitionListener: DozeTransitionListener,
- private val dozeParameters: DozeParameters,
private val authController: AuthController,
private val dreamOverlayCallbackController: DreamOverlayCallbackController,
@Main private val mainDispatcher: CoroutineDispatcher,
@@ -303,24 +300,12 @@
}
.distinctUntilChanged()
- override val isAodAvailable: Flow<Boolean> =
- conflatedCallbackFlow {
- val callback =
- DozeParameters.Callback {
- trySendWithFailureLogging(
- dozeParameters.alwaysOn,
- TAG,
- "updated isAodAvailable"
- )
- }
+ private val _isAodAvailable = MutableStateFlow(false)
+ override val isAodAvailable: Flow<Boolean> = _isAodAvailable.asStateFlow()
- dozeParameters.addCallback(callback)
- // Adding the callback does not send an initial update.
- trySendWithFailureLogging(dozeParameters.alwaysOn, TAG, "initial isAodAvailable")
-
- awaitClose { dozeParameters.removeCallback(callback) }
- }
- .distinctUntilChanged()
+ override fun setAodAvailable(value: Boolean) {
+ _isAodAvailable.value = value
+ }
override val isKeyguardOccluded: Flow<Boolean> =
conflatedCallbackFlow {
@@ -506,30 +491,11 @@
statusBarStateIntToObject(statusBarStateController.state)
)
- override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
- fun dispatchUpdate() {
- trySendWithFailureLogging(
- biometricModeIntToObject(biometricUnlockController.mode),
- TAG,
- "biometric mode"
- )
- }
+ private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
+ override val biometricUnlockState = _biometricUnlockState.asStateFlow()
- val callback =
- object : BiometricUnlockController.BiometricUnlockEventsListener {
- override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
- dispatchUpdate()
- }
-
- override fun onResetMode() {
- dispatchUpdate()
- }
- }
-
- biometricUnlockController.addListener(callback)
- dispatchUpdate()
-
- awaitClose { biometricUnlockController.removeListener(callback) }
+ override fun setBiometricUnlockState(value: BiometricUnlockModel) {
+ _biometricUnlockState.value = value
}
override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow {
@@ -662,20 +628,6 @@
}
}
- private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
- return when (value) {
- 0 -> BiometricUnlockModel.NONE
- 1 -> BiometricUnlockModel.WAKE_AND_UNLOCK
- 2 -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
- 3 -> BiometricUnlockModel.SHOW_BOUNCER
- 4 -> BiometricUnlockModel.ONLY_WAKE
- 5 -> BiometricUnlockModel.UNLOCK_COLLAPSING
- 6 -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
- 7 -> BiometricUnlockModel.DISMISS_BOUNCER
- else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value")
- }
- }
-
private fun dozeMachineStateToModel(state: DozeMachine.State): DozeStateModel {
return when (state) {
DozeMachine.State.UNINITIALIZED -> DozeStateModel.UNINITIALIZED
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 2f80106..5659623 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -22,6 +22,7 @@
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
import com.android.systemui.bouncer.domain.interactor.BouncerMessageAuditLogger
+import com.android.systemui.keyguard.ui.binder.SideFpsProgressBarViewBinder
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -32,6 +33,11 @@
@Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
@Binds
+ @IntoMap
+ @ClassKey(SideFpsProgressBarViewBinder::class)
+ fun bindSideFpsProgressBarViewBinder(viewBinder: SideFpsProgressBarViewBinder): CoreStartable
+
+ @Binds
fun keyguardSurfaceBehindRepository(
impl: KeyguardSurfaceBehindRepositoryImpl
): KeyguardSurfaceBehindRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 84cd3ef..3eef6aa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -25,6 +25,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import java.util.UUID
@@ -48,8 +49,8 @@
* [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on
* this repository.
*
- * To print all transitions to logcat to help with debugging, run this command:
- * adb shell settings put global systemui/buffer/KeyguardLog VERBOSE
+ * To print all transitions to logcat to help with debugging, run this command: adb shell settings
+ * put global systemui/buffer/KeyguardLog VERBOSE
*
* This will print all keyguard transitions to logcat with the KeyguardTransitionAuditLogger tag.
*/
@@ -73,11 +74,8 @@
/**
* Begin a transition from one state to another. Transitions are interruptible, and will issue a
* [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one.
- *
- * When canceled, there are two options: to continue from the current position of the prior
- * transition, or to reset the position. When [resetIfCanceled] == true, it will do the latter.
*/
- fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean = false): UUID?
+ fun startTransition(info: TransitionInfo): UUID?
/**
* Allows manual control of a transition. When calling [startTransition], the consumer must pass
@@ -138,10 +136,7 @@
)
}
- override fun startTransition(
- info: TransitionInfo,
- resetIfCanceled: Boolean,
- ): UUID? {
+ override fun startTransition(info: TransitionInfo): UUID? {
if (lastStep.from == info.from && lastStep.to == info.to) {
Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
return null
@@ -149,10 +144,10 @@
val startingValue =
if (lastStep.transitionState != TransitionState.FINISHED) {
Log.i(TAG, "Transition still active: $lastStep, canceling")
- if (resetIfCanceled) {
- 0f
- } else {
- lastStep.value
+ when (info.modeOnCanceled) {
+ TransitionModeOnCanceled.LAST_VALUE -> lastStep.value
+ TransitionModeOnCanceled.RESET -> 0f
+ TransitionModeOnCanceled.REVERSE -> 1f - lastStep.value
}
} else {
0f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt
new file mode 100644
index 0000000..cb003a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt
@@ -0,0 +1,42 @@
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_ONLY_WAKE
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_SHOW_BOUNCER
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_COLLAPSING
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
+import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
+import javax.inject.Inject
+
+@SysUISingleton
+class BiometricUnlockInteractor
+@Inject
+constructor(
+ private val keyguardRepository: KeyguardRepository,
+) {
+
+ fun setBiometricUnlockState(@WakeAndUnlockMode unlockStateInt: Int) {
+ val state = biometricModeIntToObject(unlockStateInt)
+ keyguardRepository.setBiometricUnlockState(state)
+ }
+
+ private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
+ return when (value) {
+ MODE_NONE -> BiometricUnlockModel.NONE
+ MODE_WAKE_AND_UNLOCK -> BiometricUnlockModel.WAKE_AND_UNLOCK
+ MODE_WAKE_AND_UNLOCK_PULSING -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+ MODE_SHOW_BOUNCER -> BiometricUnlockModel.SHOW_BOUNCER
+ MODE_ONLY_WAKE -> BiometricUnlockModel.ONLY_WAKE
+ MODE_UNLOCK_COLLAPSING -> BiometricUnlockModel.UNLOCK_COLLAPSING
+ MODE_WAKE_AND_UNLOCK_FROM_DREAM -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+ MODE_DISMISS_BOUNCER -> BiometricUnlockModel.DISMISS_BOUNCER
+ else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value")
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
index 0c898be..af1802f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
@@ -28,6 +28,10 @@
private val keyguardRepository: KeyguardRepository,
) {
+ fun setAodAvailable(value: Boolean) {
+ keyguardRepository.setAodAvailable(value)
+ }
+
fun setIsDozing(isDozing: Boolean) {
keyguardRepository.setIsDozing(isDozing)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 38eb730..a331a66 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -24,12 +24,14 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
-import javax.inject.Inject
@SysUISingleton
class FromAodTransitionInteractor
@@ -64,8 +66,21 @@
)
.collect { (_, lastStartedStep, occluded) ->
if (lastStartedStep.to == KeyguardState.AOD) {
- startTransitionTo(
+ val toState =
if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
+ val modeOnCanceled =
+ if (
+ toState == KeyguardState.LOCKSCREEN &&
+ lastStartedStep.from == KeyguardState.LOCKSCREEN
+ ) {
+ TransitionModeOnCanceled.REVERSE
+ } else {
+ TransitionModeOnCanceled.LAST_VALUE
+ }
+
+ startTransitionTo(
+ toState = toState,
+ modeOnCanceled = modeOnCanceled,
)
}
}
@@ -86,15 +101,19 @@
}
}
}
-
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
- duration = TRANSITION_DURATION_MS
+ duration =
+ when (toState) {
+ KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+ else -> DEFAULT_DURATION
+ }.inWholeMilliseconds
}
}
companion object {
- private const val TRANSITION_DURATION_MS = 500L
+ val TO_LOCKSCREEN_DURATION = 500.milliseconds
+ private val DEFAULT_DURATION = 500.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index ad51e74..eace0c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -22,6 +22,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
@@ -114,7 +115,9 @@
.collect { (isAsleep, lastStartedStep, isAodAvailable) ->
if (lastStartedStep.to == KeyguardState.GONE && isAsleep) {
startTransitionTo(
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
+ toState =
+ if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
}
}
@@ -127,6 +130,7 @@
duration =
when (toState) {
KeyguardState.DREAMING -> TO_DREAMING_DURATION
+ KeyguardState.AOD -> TO_AOD_DURATION
else -> DEFAULT_DURATION
}.inWholeMilliseconds
}
@@ -134,5 +138,6 @@
companion object {
private val DEFAULT_DURATION = 500.milliseconds
val TO_DREAMING_DURATION = 933.milliseconds
+ val TO_AOD_DURATION = 1100.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 660bd84..d44a9d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -27,12 +27,16 @@
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -40,9 +44,6 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
-import java.util.UUID
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
@SysUISingleton
class FromLockscreenTransitionInteractor
@@ -340,8 +341,20 @@
)
.collect { (isAsleep, lastStartedStep, isAodAvailable) ->
if (lastStartedStep.to == KeyguardState.LOCKSCREEN && isAsleep) {
- startTransitionTo(
+ val toState =
if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
+ val modeOnCanceled =
+ if (
+ toState == KeyguardState.AOD &&
+ lastStartedStep.from == KeyguardState.AOD
+ ) {
+ TransitionModeOnCanceled.REVERSE
+ } else {
+ TransitionModeOnCanceled.LAST_VALUE
+ }
+ startTransitionTo(
+ toState = toState,
+ modeOnCanceled = modeOnCanceled,
)
}
}
@@ -355,6 +368,7 @@
when (toState) {
KeyguardState.DREAMING -> TO_DREAMING_DURATION
KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
+ KeyguardState.AOD -> TO_AOD_DURATION
else -> DEFAULT_DURATION
}.inWholeMilliseconds
}
@@ -364,5 +378,6 @@
private val DEFAULT_DURATION = 400.milliseconds
val TO_DREAMING_DURATION = 933.milliseconds
val TO_OCCLUDED_DURATION = 450.milliseconds
+ val TO_AOD_DURATION = 500.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index ad2ec69..24b6661 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -18,7 +18,6 @@
import android.animation.ValueAnimator
import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
@@ -26,20 +25,22 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toQuint
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import com.android.wm.shell.animation.Interpolators
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
@SysUISingleton
class FromPrimaryBouncerTransitionInteractor
@@ -51,6 +52,7 @@
private val keyguardInteractor: KeyguardInteractor,
private val flags: FeatureFlags,
private val keyguardSecurityModel: KeyguardSecurityModel,
+ private val selectedUserInteractor: SelectedUserInteractor,
private val powerInteractor: PowerInteractor,
) :
TransitionInteractor(
@@ -132,7 +134,7 @@
.collect {
(
isBouncerShowing,
- isAwake,
+ isAwake,
lastStartedTransitionStep,
occluded,
isActiveDreamLockscreenHosted) ->
@@ -162,8 +164,7 @@
),
::toQuad
)
- .collect {
- (isBouncerShowing, isAsleep, lastStartedTransitionStep, isAodAvailable)
+ .collect { (isBouncerShowing, isAsleep, lastStartedTransitionStep, isAodAvailable)
->
if (
!isBouncerShowing &&
@@ -181,25 +182,24 @@
private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
- .sample(
- combine(
- keyguardInteractor.isActiveDreamLockscreenHosted,
- transitionInteractor.startedKeyguardTransitionStep,
- ::Pair
- ),
- ::toTriple
- )
- .collect { (isBouncerShowing,
- isActiveDreamLockscreenHosted,
- lastStartedTransitionStep) ->
- if (
- !isBouncerShowing &&
- isActiveDreamLockscreenHosted &&
- lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
- ) {
- startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
- }
+ .sample(
+ combine(
+ keyguardInteractor.isActiveDreamLockscreenHosted,
+ transitionInteractor.startedKeyguardTransitionStep,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect {
+ (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
+ if (
+ !isBouncerShowing &&
+ isActiveDreamLockscreenHosted &&
+ lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
+ ) {
+ startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
}
+ }
}
}
@@ -221,7 +221,7 @@
) {
val securityMode =
keyguardSecurityModel.getSecurityMode(
- KeyguardUpdateMonitor.getCurrentUser()
+ selectedUserInteractor.getSelectedUserId()
)
// IME for password requires a slightly faster animation
val duration =
@@ -237,7 +237,7 @@
getDefaultAnimatorForTransitionsToState(KeyguardState.GONE).apply {
this.duration = duration.inWholeMilliseconds
},
- resetIfCancelled = true
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
index cab6928..628e912 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
@@ -24,7 +24,7 @@
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -40,11 +40,11 @@
@Inject
constructor(
trustRepository: TrustRepository,
- val keyguardRepository: KeyguardRepository,
- val primaryBouncerInteractor: PrimaryBouncerInteractor,
- val alternateBouncerInteractor: AlternateBouncerInteractor,
- val powerInteractor: PowerInteractor,
- val userInteractor: UserInteractor,
+ private val keyguardRepository: KeyguardRepository,
+ primaryBouncerInteractor: PrimaryBouncerInteractor,
+ alternateBouncerInteractor: AlternateBouncerInteractor,
+ powerInteractor: PowerInteractor,
+ private val selectedUserInteractor: SelectedUserInteractor,
) {
/*
* Updates when a biometric has authenticated the device and is requesting to dismiss
@@ -82,7 +82,7 @@
*/
private val primaryAuthenticated: Flow<Unit> =
primaryBouncerInteractor.keyguardAuthenticatedPrimaryAuth
- .filter { authedUserId -> authedUserId == userInteractor.getSelectedUserId() }
+ .filter { authedUserId -> authedUserId == selectedUserInteractor.getSelectedUserId() }
.map {} // map to Unit
/*
@@ -92,7 +92,7 @@
*/
private val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Unit> =
primaryBouncerInteractor.userRequestedBouncerWhenAlreadyAuthenticated
- .filter { authedUserId -> authedUserId == userInteractor.getSelectedUserId() }
+ .filter { authedUserId -> authedUserId == selectedUserInteractor.getSelectedUserId() }
.map {} // map to Unit
/** Updates when keyguardDone should be requested. */
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 89aca76..85b0f4fb 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
@@ -41,6 +41,9 @@
/** Whether face auth is in lock out state. */
fun isLockedOut(): Boolean
+ /** Whether face auth is enrolled and enabled for the current user */
+ fun isFaceAuthEnabledAndEnrolled(): Boolean
+
/**
* Register listener for use from code that cannot use [authenticationStatus] or
* [detectionStatus]
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 6e19fdb..b953b48 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -39,7 +39,6 @@
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
-import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
@@ -49,6 +48,8 @@
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
@@ -64,8 +65,6 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
-import javax.inject.Inject
-import javax.inject.Provider
/**
* Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -148,9 +147,7 @@
.combine(dozeTransitionModel) { isDreaming, dozeTransitionModel ->
isDreaming && isDozeOff(dozeTransitionModel.to)
}
- .sample(powerInteractor.isAwake) { isAbleToDream, isAwake ->
- isAbleToDream && isAwake
- }
+ .sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake }
.flatMapLatest { isAbleToDream ->
flow {
delay(50)
@@ -217,11 +214,8 @@
val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
/** Notifies when a new configuration is set */
- val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
-
- /** Represents the current state of the KeyguardRootView visibility */
- val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
- repository.keyguardRootViewVisibility
+ val configurationChange: Flow<Unit> =
+ configurationRepository.onAnyConfigurationChange.onStart { emit(Unit) }
/** The position of the keyguard clock. */
val clockPosition: Flow<Position> = repository.clockPosition
@@ -235,12 +229,17 @@
R.dimen.keyguard_translate_distance_on_swipe_up
)
shadeRepository.shadeModel.map {
- // On swipe up, translate the keyguard to reveal the bouncer
- MathUtils.lerp(
- translationDistance,
- 0,
- Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount)
- )
+ if (it.expansionAmount == 0f) {
+ // Reset the translation value
+ 0f
+ } else {
+ // On swipe up, translate the keyguard to reveal the bouncer
+ MathUtils.lerp(
+ translationDistance,
+ 0,
+ Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount)
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
index f38bb2b..fbadde6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
@@ -43,6 +43,7 @@
override fun isLockedOut(): Boolean = false
override fun isEnabled() = false
+ override fun isFaceAuthEnabledAndEnrolled(): Boolean = false
override fun registerListener(listener: FaceAuthenticationListener) {}
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 797dec2..2641846 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
@@ -32,6 +32,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
@@ -81,6 +82,7 @@
private val facePropertyRepository: FacePropertyRepository,
private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig,
private val powerInteractor: PowerInteractor,
+ private val biometricSettingsRepository: BiometricSettingsRepository,
) : CoreStartable, KeyguardFaceAuthInteractor {
private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
@@ -149,7 +151,10 @@
.onEach {
if (it) {
faceAuthenticationLogger.faceLockedOut("Fingerprint locked out")
- repository.setLockedOut(true)
+ // We don't care about this if face auth is not enabled.
+ if (isFaceAuthEnabledAndEnrolled()) {
+ repository.setLockedOut(true)
+ }
}
}
.launchIn(applicationScope)
@@ -263,6 +268,9 @@
}
}
+ override fun isFaceAuthEnabledAndEnrolled(): Boolean =
+ biometricSettingsRepository.isFaceAuthEnrolledAndEnabled.value
+
private fun observeFaceAuthStateUpdates() {
authenticationStatus
.onEach { authStatusUpdate ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 54c6d5f..7601808 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import kotlinx.coroutines.CoroutineScope
@@ -49,7 +50,7 @@
fun startTransitionTo(
toState: KeyguardState,
animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
- resetIfCancelled: Boolean = false,
+ modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE
): UUID? {
if (
fromState != transitionInteractor.startedKeyguardState.value &&
@@ -73,8 +74,8 @@
fromState,
toState,
animator,
- ),
- resetIfCancelled
+ modeOnCanceled,
+ )
)
}
@@ -91,8 +92,8 @@
// so use the last finishedKeyguardState to determine the overriding FROM state
if (finishedKeyguardState == fromState) {
startTransitionTo(
- KeyguardState.OCCLUDED,
- resetIfCancelled = true,
+ toState = KeyguardState.OCCLUDED,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
index ae18681..3c143fe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.shared.model
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
import android.hardware.fingerprint.FingerprintManager
import android.os.SystemClock.elapsedRealtime
@@ -39,7 +41,12 @@
/** Fingerprint acquired message. */
data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) :
- FingerprintAuthenticationStatus()
+ FingerprintAuthenticationStatus() {
+
+ val fingerprintCaptureStarted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_START
+
+ val fingerprintCaptureCompleted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_GOOD
+}
/** Fingerprint authentication failed message. */
object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
index bfccf3fe..7a37365 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
@@ -22,7 +22,13 @@
val ownerName: String,
val from: KeyguardState,
val to: KeyguardState,
- val animator: ValueAnimator?, // 'null' animator signal manual control
+ /** [null] animator signals manual control, otherwise transition run by the animator */
+ val animator: ValueAnimator?,
+ /**
+ * If the transition resets in the cancellation of another transition, use this mode to
+ * determine how to continue.
+ */
+ val modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
) {
override fun toString(): String =
"TransitionInfo(ownerName=$ownerName, from=$from, to=$to, " +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt
new file mode 100644
index 0000000..56f90bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.keyguard.shared.model
+
+/** When canceled, provide different ways to start the next transition. */
+enum class TransitionModeOnCanceled {
+ /** Proceed from the last value. If canceled at .7, start from .7 and end at 1 */
+ LAST_VALUE,
+ /** Start over from 0. If canceled at .7, start from 0 and end at 1 */
+ RESET,
+ /** Reverse the transition. If canceled at .7, start from 1-.7 (0.3) and end at 1 */
+ REVERSE
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
index f14552b..87d8164 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
@@ -25,20 +25,18 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.log.core.LogLevel
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
/** Handles keyguard dismissal requests. */
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class KeyguardDismissBinder
@Inject
constructor(
private val interactor: KeyguardDismissInteractor,
- private val userInteractor: UserInteractor,
+ private val selectedUserInteractor: SelectedUserInteractor,
private val viewMediatorCallback: ViewMediatorCallback,
@Application private val scope: CoroutineScope,
private val keyguardLogger: KeyguardLogger,
@@ -55,11 +53,15 @@
when (keyguardDoneTiming) {
KeyguardDone.LATER -> {
log("keyguardDonePending")
- viewMediatorCallback.keyguardDonePending(userInteractor.getSelectedUserId())
+ viewMediatorCallback.keyguardDonePending(
+ selectedUserInteractor.getSelectedUserId()
+ )
}
else -> {
log("keyguardDone")
- viewMediatorCallback.keyguardDone(userInteractor.getSelectedUserId())
+ viewMediatorCallback.keyguardDone(
+ selectedUserInteractor.getSelectedUserId()
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index ac4ad39..4d5c503 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -17,24 +17,30 @@
package com.android.systemui.keyguard.ui.binder
import android.annotation.DrawableRes
+import android.view.HapticFeedbackConstants
import android.view.View
import android.view.View.OnLayoutChangeListener
import android.view.ViewGroup
+import android.view.ViewGroup.OnHierarchyChangeListener
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.animation.Interpolators
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
+import com.android.keyguard.KeyguardClockSwitch.MISSING_CLOCK_ID
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.ClockController
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -42,14 +48,13 @@
import javax.inject.Provider
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
/** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */
@ExperimentalCoroutinesApi
object KeyguardRootViewBinder {
- private var onLayoutChangeListener: OnLayoutChange? = null
-
@JvmStatic
fun bind(
view: ViewGroup,
@@ -60,7 +65,16 @@
keyguardStateController: KeyguardStateController,
shadeInteractor: ShadeInteractor,
clockControllerProvider: Provider<ClockController>?,
+ interactionJankMonitor: InteractionJankMonitor?,
+ deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?,
+ vibratorHelper: VibratorHelper?,
): DisposableHandle {
+ var onLayoutChangeListener: OnLayoutChange? = null
+ val childViews = mutableMapOf<Int, View?>()
+ val statusViewId = R.id.keyguard_status_view
+ val burnInLayerId = R.id.burn_in_layer
+ val aodNotificationIconContainerId = R.id.aod_notification_icon_container
+ val largeClockId = R.id.lockscreen_clock_view_large
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -86,36 +100,74 @@
if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
launch {
- viewModel.translationY.collect { y ->
- val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
- burnInLayer.translationY = y
+ viewModel.burnInLayerVisibility.collect { visibility ->
+ childViews[burnInLayerId]?.visibility = visibility
+ // Reset alpha only for the icons, as they currently have their
+ // own animator
+ childViews[aodNotificationIconContainerId]?.alpha = 0f
}
}
- }
- if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ launch {
+ viewModel.burnInLayerAlpha.collect { alpha ->
+ childViews[statusViewId]?.alpha = alpha
+ childViews[aodNotificationIconContainerId]?.alpha = alpha
+ }
+ }
+
+ launch {
+ viewModel.lockscreenStateAlpha.collect { alpha ->
+ childViews[statusViewId]?.alpha = alpha
+ }
+ }
+
+ launch {
+ viewModel.translationY.collect { y ->
+ childViews[burnInLayerId]?.translationY = y
+ }
+ }
+
launch {
viewModel.translationX.collect { x ->
- val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
- burnInLayer.translationX = x
+ childViews[burnInLayerId]?.translationX = x
}
}
- }
- if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
launch {
viewModel.scale.collect { (scale, scaleClockOnly) ->
if (scaleClockOnly) {
- val largeClock =
- view.findViewById<View?>(R.id.lockscreen_clock_view_large)
- largeClock?.let {
+ childViews[largeClockId]?.let {
it.scaleX = scale
it.scaleY = scale
}
} else {
- val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
- burnInLayer.scaleX = scale
- burnInLayer.scaleY = scale
+ childViews[burnInLayerId]?.scaleX = scale
+ childViews[burnInLayerId]?.scaleY = scale
+ }
+ }
+ }
+
+ interactionJankMonitor?.let { jankMonitor ->
+ launch {
+ viewModel.goneToAodTransition.collect {
+ when (it.transitionState) {
+ TransitionState.STARTED -> {
+ val clockId =
+ clockControllerProvider?.get()?.config?.id
+ ?: MISSING_CLOCK_ID
+ val builder =
+ InteractionJankMonitor.Configuration.Builder
+ .withView(CUJ_SCREEN_OFF_SHOW_AOD, view)
+ .setTag(clockId)
+
+ jankMonitor.begin(builder)
+ }
+ TransitionState.CANCELED ->
+ jankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+ TransitionState.FINISHED ->
+ jankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
+ TransitionState.RUNNING -> Unit
+ }
}
}
}
@@ -131,42 +183,42 @@
}
}
}
- }
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
launch {
- viewModel.keyguardRootViewVisibilityState.collect { visibilityState ->
- view.animate().cancel()
- val goingToFullShade = visibilityState.goingToFullShade
- val statusBarState = visibilityState.statusBarState
- val isOcclusionTransitionRunning =
- visibilityState.occlusionTransitionRunning
- if (goingToFullShade) {
- view
- .animate()
- .alpha(0f)
- .setStartDelay(
- keyguardStateController.keyguardFadingAwayDelay
+ deviceEntryHapticsInteractor.playSuccessHaptic
+ .filter { it }
+ .collect {
+ if (
+ featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION)
+ ) {
+ vibratorHelper.performHapticFeedback(
+ view,
+ HapticFeedbackConstants.CONFIRM,
)
- .setDuration(
- keyguardStateController.shortenedFadingAwayDuration
- )
- .setInterpolator(Interpolators.ALPHA_OUT)
- .withEndAction { view.visibility = View.GONE }
- .start()
- } else if (
- statusBarState == StatusBarState.KEYGUARD ||
- statusBarState == StatusBarState.SHADE_LOCKED
- ) {
- view.visibility = View.VISIBLE
- if (!isOcclusionTransitionRunning) {
- view.alpha = 1f
+ } else {
+ vibratorHelper.vibrateAuthSuccess("device-entry::success")
}
- } else {
- view.visibility = View.GONE
+ deviceEntryHapticsInteractor.handleSuccessHaptic()
}
- }
+ }
+
+ launch {
+ deviceEntryHapticsInteractor.playErrorHaptic
+ .filter { it }
+ .collect {
+ if (
+ featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION)
+ ) {
+ vibratorHelper.performHapticFeedback(
+ view,
+ HapticFeedbackConstants.REJECT,
+ )
+ } else {
+ vibratorHelper.vibrateAuthSuccess("device-entry::error")
+ }
+ deviceEntryHapticsInteractor.handleErrorHaptic()
+ }
}
}
}
@@ -176,10 +228,26 @@
onLayoutChangeListener = OnLayoutChange(viewModel)
view.addOnLayoutChangeListener(onLayoutChangeListener)
+ // Views will be added or removed after the call to bind(). This is needed to avoid many
+ // calls to findViewById
+ view.setOnHierarchyChangeListener(
+ object : OnHierarchyChangeListener {
+ override fun onChildViewAdded(parent: View, child: View) {
+ childViews.put(child.id, child)
+ }
+
+ override fun onChildViewRemoved(parent: View, child: View) {
+ childViews.remove(child.id)
+ }
+ }
+ )
+
return object : DisposableHandle {
override fun dispose() {
disposableHandle.dispose()
view.removeOnLayoutChangeListener(onLayoutChangeListener)
+ view.setOnHierarchyChangeListener(null)
+ childViews.clear()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt
index f3586ba..2feaa2e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt
@@ -24,6 +24,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.launch
/**
@@ -46,8 +47,8 @@
constraintLayout: ConstraintLayout,
viewModel: KeyguardBlueprintViewModel,
finishedAddViewCallback: () -> Unit
- ) {
- constraintLayout.repeatWhenAttached {
+ ): DisposableHandle {
+ return constraintLayout.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.blueprint.collect { blueprint ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
new file mode 100644
index 0000000..1acea5c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.keyguard.ui.binder
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.biometrics.SideFpsController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.ui.view.SideFpsProgressBar
+import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
+import com.android.systemui.util.kotlin.Quint
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class SideFpsProgressBarViewBinder
+@Inject
+constructor(
+ private val viewModel: SideFpsProgressBarViewModel,
+ private val view: SideFpsProgressBar,
+ @Application private val applicationScope: CoroutineScope,
+ private val sfpsController: dagger.Lazy<SideFpsController>,
+) : CoreStartable {
+
+ override fun start() {
+ applicationScope.launch {
+ viewModel.isProlongedTouchRequiredForAuthentication.collectLatest { enabled ->
+ if (enabled) {
+ launch {
+ combine(
+ viewModel.isVisible,
+ viewModel.sensorLocation,
+ viewModel.shouldRotate90Degrees,
+ viewModel.isFingerprintAuthRunning,
+ viewModel.sensorWidth,
+ ::Quint
+ )
+ .collectLatest {
+ (visible, location, shouldRotate, fpDetectRunning, sensorWidth) ->
+ view.updateView(visible, location, shouldRotate, sensorWidth)
+ // We have to hide the SFPS indicator as the progress bar will
+ // be shown at the same location
+ if (visible) {
+ sfpsController.get().hideIndicator()
+ } else if (fpDetectRunning) {
+ sfpsController.get().showIndicator()
+ }
+ }
+ }
+ launch { viewModel.progress.collectLatest { view.setProgress(it) } }
+ } else {
+ view.hideOverlay()
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 4a2954d..692984a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -27,6 +27,7 @@
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
+import android.view.ContextThemeWrapper
import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.DisplayInfo
@@ -45,6 +46,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
@@ -113,6 +115,7 @@
private val chipbarCoordinator: ChipbarCoordinator,
private val keyguardStateController: KeyguardStateController,
private val shadeInteractor: ShadeInteractor,
+ private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
) {
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
@@ -179,7 +182,11 @@
fun render() {
mainHandler.post {
- val previewContext = display?.let { context.createDisplayContext(it) } ?: context
+ val previewContext =
+ display?.let {
+ ContextThemeWrapper(context.createDisplayContext(it), context.getTheme())
+ }
+ ?: context
val rootView = FrameLayout(previewContext)
@@ -322,7 +329,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) {
- val keyguardRootView = KeyguardRootView(previewContext, null).apply { removeAllViews() }
+ val keyguardRootView = KeyguardRootView(previewContext, null)
disposables.add(
KeyguardRootViewBinder.bind(
keyguardRootView,
@@ -333,6 +340,9 @@
keyguardStateController,
shadeInteractor,
null, // clock provider only needed for burn in
+ null, // jank monitor not required for preview mode
+ null, // device entry haptics not required for preview mode
+ null, // device entry haptics not required for preview mode
)
)
rootView.addView(
@@ -342,26 +352,29 @@
FrameLayout.LayoutParams.MATCH_PARENT,
),
)
- PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
- setupShortcuts(keyguardRootView)
- }
- setUpUdfps(previewContext, rootView)
- if (!shouldHideClock) {
- setUpClock(previewContext, rootView)
- KeyguardPreviewClockViewBinder.bind(
- largeClockHostView,
- smallClockHostView,
- clockViewModel,
- )
- }
+ disposables.add(
+ PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ setupShortcuts(keyguardRootView)
+ }
+ setUpUdfps(previewContext, rootView)
- setUpSmartspace(previewContext, rootView)
- smartSpaceView?.let {
- KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel)
+ if (!shouldHideClock) {
+ setUpClock(previewContext, rootView)
+ KeyguardPreviewClockViewBinder.bind(
+ largeClockHostView,
+ smallClockHostView,
+ clockViewModel,
+ )
+ }
+
+ setUpSmartspace(previewContext, rootView)
+ smartSpaceView?.let {
+ KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel)
+ }
}
- }
+ )
}
private fun setupShortcuts(keyguardRootView: ConstraintLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
new file mode 100644
index 0000000..f7ab1ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.keyguard.ui.view
+
+import android.graphics.PixelFormat
+import android.graphics.Point
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import android.widget.ProgressBar
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+private const val TAG = "SideFpsProgressBar"
+
+const val progressBarHeight = 100
+
+@SysUISingleton
+class SideFpsProgressBar
+@Inject
+constructor(
+ private val layoutInflater: LayoutInflater,
+ private val windowManager: WindowManager,
+) {
+ private var progressBarWidth = 200
+ fun updateView(
+ visible: Boolean,
+ location: Point,
+ shouldRotate90Degrees: Boolean,
+ progressBarWidth: Int
+ ) {
+ if (visible) {
+ this.progressBarWidth = progressBarWidth
+ createAndShowOverlay(location, shouldRotate90Degrees)
+ } else {
+ hideOverlay()
+ }
+ }
+
+ fun hideOverlay() {
+ overlayView = null
+ }
+
+ private val overlayViewParams =
+ WindowManager.LayoutParams(
+ progressBarHeight,
+ progressBarWidth,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
+ PixelFormat.TRANSPARENT
+ )
+ .apply {
+ title = TAG
+ fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
+ gravity = Gravity.TOP or Gravity.LEFT
+ layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ privateFlags =
+ WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or
+ WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+ }
+
+ private var overlayView: View? = null
+ set(value) {
+ field?.let { oldView -> windowManager.removeView(oldView) }
+ field = value
+ field?.let { newView -> windowManager.addView(newView, overlayViewParams) }
+ }
+
+ private fun createAndShowOverlay(
+ fingerprintSensorLocation: Point,
+ shouldRotate90Degrees: Boolean
+ ) {
+ if (overlayView == null) {
+ overlayView = layoutInflater.inflate(R.layout.sidefps_progress_bar, null, false)
+ }
+ overlayViewParams.x = fingerprintSensorLocation.x
+ overlayViewParams.y = fingerprintSensorLocation.y
+ if (shouldRotate90Degrees) {
+ overlayView?.rotation = 270.0f
+ overlayViewParams.width = progressBarHeight
+ overlayViewParams.height = progressBarWidth
+ } else {
+ overlayView?.rotation = 0.0f
+ overlayViewParams.width = progressBarWidth
+ overlayViewParams.height = progressBarHeight
+ }
+ windowManager.updateViewLayout(overlayView, overlayViewParams)
+ }
+
+ fun setProgress(value: Float) {
+ overlayView
+ ?.findViewById<ProgressBar?>(R.id.side_fps_progress_bar)
+ ?.setProgress((value * 100).toInt(), false)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
new file mode 100644
index 0000000..024707a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down AOD->LOCKSCREEN transition into discrete steps for corresponding views to consume.
+ */
+@SysUISingleton
+class AodToLockscreenTransitionViewModel
+@Inject
+constructor(
+ private val interactor: KeyguardTransitionInteractor,
+) {
+
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_LOCKSCREEN_DURATION,
+ transitionFlow = interactor.aodToLockscreenTransition,
+ )
+
+ /** Ensure alpha is set to be visible */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 500.milliseconds,
+ onStart = { 1f },
+ onStep = { 1f },
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
new file mode 100644
index 0000000..601dbcc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/** Breaks down GONE->AOD transition into discrete steps for corresponding views to consume. */
+@SysUISingleton
+class GoneToAodTransitionViewModel
+@Inject
+constructor(
+ private val interactor: KeyguardTransitionInteractor,
+) {
+
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_AOD_DURATION,
+ transitionFlow = interactor.goneToAodTransition,
+ )
+
+ /** y-translation from the top of the screen for AOD */
+ fun enterFromTopTranslationY(translatePx: Int): Flow<Float> {
+ return transitionAnimation.createFlow(
+ startTime = 600.milliseconds,
+ duration = 500.milliseconds,
+ onStart = { translatePx },
+ onStep = { translatePx + it * -translatePx },
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED_DECELERATE,
+ )
+ }
+
+ /** alpha animation upon entering AOD */
+ val enterFromTopAnimationAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ startTime = 600.milliseconds,
+ duration = 500.milliseconds,
+ onStart = { 0f },
+ onStep = { it },
+ )
+}
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 89835fe..1f98082 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
@@ -17,14 +17,19 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.Context
import android.util.MathUtils
+import android.view.View.VISIBLE
import com.android.app.animation.Interpolators
import com.android.systemui.common.shared.model.SharedNotificationContainerPosition
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
-import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.plugins.ClockController
+import com.android.systemui.res.R
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -32,17 +37,23 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
@OptIn(ExperimentalCoroutinesApi::class)
class KeyguardRootViewModel
@Inject
constructor(
+ private val context: Context,
private val keyguardInteractor: KeyguardInteractor,
private val burnInInteractor: BurnInInteractor,
+ private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
+ private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
data class PreviewMode(val isInPreviewMode: Boolean = false)
@@ -56,9 +67,12 @@
public var clockControllerProvider: Provider<ClockController>? = null
- /** Represents the current state of the KeyguardRootView visibility */
- val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
- keyguardInteractor.keyguardRootViewVisibilityState
+ val burnInLayerVisibility: Flow<Int> =
+ keyguardTransitionInteractor.startedKeyguardState
+ .filter { it == AOD || it == LOCKSCREEN }
+ .map { VISIBLE }
+
+ val goneToAodTransition = keyguardTransitionInteractor.goneToAodTransition
/** An observable for the alpha level for the entire keyguard root view. */
val alpha: Flow<Float> =
@@ -70,9 +84,14 @@
}
}
- private val burnIn: Flow<BurnInModel> =
- combine(keyguardInteractor.dozeAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn
- ->
+ private fun burnIn(): Flow<BurnInModel> {
+ val dozingAmount: Flow<Float> =
+ merge(
+ keyguardTransitionInteractor.goneToAodTransition.map { it.value },
+ keyguardTransitionInteractor.dozeAmountTransition.map { it.value },
+ )
+
+ return combine(dozingAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn ->
val interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount)
val useScaleOnly =
clockControllerProvider?.get()?.config?.useAlternateSmartspaceAODTransition ?: false
@@ -91,13 +110,61 @@
)
}
}
+ }
+
+ /** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */
+ val lockscreenStateAlpha: Flow<Float> = aodToLockscreenTransitionViewModel.lockscreenAlpha
+
+ /** For elements that appear and move during the animation -> AOD */
+ val burnInLayerAlpha: Flow<Float> =
+ previewMode.flatMapLatest {
+ if (it.isInPreviewMode) {
+ flowOf(1f)
+ } else {
+ goneToAodTransitionViewModel.enterFromTopAnimationAlpha
+ }
+ }
val translationY: Flow<Float> =
- merge(keyguardInteractor.keyguardTranslationY, burnIn.map { it.translationY.toFloat() })
+ previewMode.flatMapLatest {
+ if (it.isInPreviewMode) {
+ flowOf(0f)
+ } else {
+ keyguardInteractor.configurationChange.flatMapLatest { _ ->
+ val enterFromTopAmount =
+ context.resources.getDimensionPixelSize(
+ R.dimen.keyguard_enter_from_top_translation_y
+ )
+ combine(
+ keyguardInteractor.keyguardTranslationY.onStart { emit(0f) },
+ burnIn().map { it.translationY.toFloat() }.onStart { emit(0f) },
+ goneToAodTransitionViewModel
+ .enterFromTopTranslationY(enterFromTopAmount)
+ .onStart { emit(0f) },
+ ) { keyguardTransitionY, burnInTranslationY, goneToAodTransitionTranslationY ->
+ // All 3 values need to be combined for a smooth translation
+ keyguardTransitionY + burnInTranslationY + goneToAodTransitionTranslationY
+ }
+ }
+ }
+ }
- val translationX: Flow<Float> = burnIn.map { it.translationX.toFloat() }
+ val translationX: Flow<Float> =
+ previewMode.flatMapLatest {
+ if (it.isInPreviewMode) {
+ flowOf(0f)
+ } else {
+ burnIn().map { it.translationX.toFloat() }
+ }
+ }
- val scale: Flow<Pair<Float, Boolean>> = burnIn.map { Pair(it.scale, it.scaleClockOnly) }
+ val scale: Flow<Pair<Float, Boolean>> =
+ previewMode.flatMapLatest { previewMode ->
+ burnIn().map {
+ val scale = if (previewMode.isInPreviewMode) 1f else it.scale
+ Pair(scale, it.scaleClockOnly)
+ }
+ }
/**
* Puts this view-model in "preview mode", which means it's being used for UI that is rendering
@@ -105,11 +172,14 @@
* lock screen.
*/
fun enablePreviewMode() {
- val newPreviewMode = PreviewMode(true)
- previewMode.value = newPreviewMode
+ previewMode.value = PreviewMode(true)
}
fun onSharedNotificationContainerPositionChanged(top: Float, bottom: Float) {
+ // Notifications should not be visible in preview mode
+ if (previewMode.value.isInPreviewMode) {
+ return
+ }
keyguardInteractor.sharedNotificationContainerPosition.value =
SharedNotificationContainerPosition(top, bottom)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
new file mode 100644
index 0000000..2c3b431
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import android.animation.ValueAnimator
+import android.graphics.Point
+import androidx.core.animation.doOnEnd
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.shared.model.isDefaultOrientation
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class SideFpsProgressBarViewModel
+@Inject
+constructor(
+ private val fpAuthRepository: DeviceEntryFingerprintAuthRepository,
+ private val sfpsSensorInteractor: SideFpsSensorInteractor,
+ displayStateInteractor: DisplayStateInteractor,
+ @Application private val applicationScope: CoroutineScope,
+) {
+ private val _progress = MutableStateFlow(0.0f)
+ private val _visible = MutableStateFlow(false)
+ private var _animator: ValueAnimator? = null
+
+ private fun onFingerprintCaptureCompleted() {
+ _visible.value = false
+ _progress.value = 0.0f
+ }
+
+ val isVisible: Flow<Boolean> = _visible.asStateFlow()
+
+ val progress: Flow<Float> = _progress.asStateFlow()
+
+ val sensorWidth: Flow<Int> = sfpsSensorInteractor.sensorLocation.map { it.width }
+
+ val sensorLocation: Flow<Point> =
+ sfpsSensorInteractor.sensorLocation.map { Point(it.left, it.top) }
+
+ val isFingerprintAuthRunning: Flow<Boolean> = fpAuthRepository.isRunning
+
+ val shouldRotate90Degrees: Flow<Boolean> =
+ combine(displayStateInteractor.currentRotation, sfpsSensorInteractor.sensorLocation, ::Pair)
+ .map { (rotation, sensorLocation) ->
+ if (rotation.isDefaultOrientation()) {
+ sensorLocation.isSensorVerticalInDefaultOrientation
+ } else {
+ !sensorLocation.isSensorVerticalInDefaultOrientation
+ }
+ }
+
+ val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
+ sfpsSensorInteractor.isProlongedTouchRequiredForAuthentication
+
+ init {
+ applicationScope.launch {
+ combine(
+ sfpsSensorInteractor.isProlongedTouchRequiredForAuthentication,
+ sfpsSensorInteractor.authenticationDuration,
+ ::Pair
+ )
+ .collectLatest { (enabled, authDuration) ->
+ if (!enabled) return@collectLatest
+
+ launch {
+ fpAuthRepository.authenticationStatus.collectLatest { authStatus ->
+ when (authStatus) {
+ is AcquiredFingerprintAuthenticationStatus -> {
+ if (authStatus.fingerprintCaptureStarted) {
+
+ _visible.value = true
+ _animator?.cancel()
+ _animator =
+ ValueAnimator.ofFloat(0.0f, 1.0f)
+ .setDuration(authDuration)
+ .apply {
+ addUpdateListener {
+ _progress.value = it.animatedValue as Float
+ }
+ addListener(
+ doOnEnd {
+ if (_progress.value == 0.0f) {
+ _visible.value = false
+ }
+ }
+ )
+ }
+ _animator?.start()
+ } else if (authStatus.fingerprintCaptureCompleted) {
+ onFingerprintCaptureCompleted()
+ } else {
+ // Abandoned FP Auth attempt
+ _animator?.reverse()
+ }
+ }
+ is ErrorFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ is FailFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ is SuccessFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ else -> Unit
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 67531ad..fd6b3f1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -190,17 +190,6 @@
}
}
- /**
- * Provides a logging buffer for all logs related to Quick Settings tiles. This LogBuffer is
- * unique for each tile.
- * go/qs-tile-refactor
- */
- @Provides
- @QSTilesDefaultLog
- public static LogBuffer provideQuickSettingsTilesLogBuffer(LogBufferFactory factory) {
- return factory.create("QSTileLog", 25 /* maxSize */, false /* systrace */);
- }
-
@Provides
@QSTilesLogBuffers
public static Map<TileSpec, LogBuffer> provideQuickSettingsTilesLogBufferCache() {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
deleted file mode 100644
index 6575cdd..0000000
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
+++ /dev/null
@@ -1,28 +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.systemui.log.dagger
-
-import javax.inject.Qualifier
-
-/**
- * A default [com.android.systemui.log.LogBuffer] for QS tiles messages. It's used exclusively in
- * [com.android.systemui.qs.tiles.base.logging.QSTileLogger]. If you need to increase it for you
- * tile, add one to the map provided by the [QSTilesLogBuffers]
- */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class QSTilesDefaultLog
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index a48e56a..7cb5b3b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -731,6 +731,7 @@
removePlayer(existingSmartspaceMediaKey, dismissMediaData = false)
removedPlayer?.run {
debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey)
+ onDestroy()
}
}
@@ -1302,6 +1303,7 @@
val removedPlayer = removeMediaPlayer(key)
if (removedPlayer != null && removedPlayer != player) {
debugLogger?.logPotentialMemoryLeak(key)
+ removedPlayer.onDestroy()
}
val sortKey =
MediaSortKey(
@@ -1329,6 +1331,7 @@
val removedPlayer = removeMediaPlayer(key)
if (!update && removedPlayer != null && removedPlayer != player) {
debugLogger?.logPotentialMemoryLeak(key)
+ removedPlayer.onDestroy()
}
val sortKey =
MediaSortKey(
@@ -1357,7 +1360,10 @@
// MediaPlayer should not be visible
// no need to set isDismissed flag.
val removedPlayer = removeMediaPlayer(newKey)
- removedPlayer?.run { debugLogger?.logPotentialMemoryLeak(newKey) }
+ removedPlayer?.run {
+ debugLogger?.logPotentialMemoryLeak(newKey)
+ onDestroy()
+ }
mediaData.put(newKey, it)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
new file mode 100644
index 0000000..ce8b79c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.mediaprojection
+
+import android.media.projection.IMediaProjectionManager
+import android.os.Process
+import android.os.RemoteException
+import android.util.Log
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED as METRICS_STATE_PERMISSION_REQUEST_DISPLAYED
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * Helper class for requesting that the server emit logs describing the MediaProjection setup
+ * experience.
+ */
+@SysUISingleton
+class MediaProjectionMetricsLogger
+@Inject
+constructor(private val service: IMediaProjectionManager) {
+ /**
+ * Request to log that the permission was requested.
+ *
+ * @param sessionCreationSource The entry point requesting permission to capture.
+ */
+ fun notifyProjectionInitiated(sessionCreationSource: SessionCreationSource) {
+ notifyToServer(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED,
+ sessionCreationSource
+ )
+ }
+
+ fun notifyPermissionRequestDisplayed() {
+ notifyToServer(METRICS_STATE_PERMISSION_REQUEST_DISPLAYED, SessionCreationSource.UNKNOWN)
+ }
+
+ /**
+ * Request to log that the permission request moved to the given state.
+ *
+ * Should not be used for the initialization state, since that should use {@link
+ * MediaProjectionMetricsLogger#notifyProjectionInitiated(Int)} and pass the
+ * sessionCreationSource.
+ */
+ fun notifyPermissionProgress(state: Int) {
+ // TODO validate state is valid
+ notifyToServer(state, SessionCreationSource.UNKNOWN)
+ }
+
+ /**
+ * Notifies system server that we are handling a particular state during the consent flow.
+ *
+ * Only used for emitting atoms.
+ *
+ * @param state The state that SystemUI is handling during the consent flow. Must be a valid
+ * state defined in the MediaProjectionState enum.
+ * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED.
+ * Indicates the entry point for requesting the permission. Must be a valid state defined in
+ * the SessionCreationSource enum.
+ */
+ private fun notifyToServer(state: Int, sessionCreationSource: SessionCreationSource) {
+ Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource")
+ try {
+ service.notifyPermissionRequestStateChange(
+ Process.myUid(),
+ state,
+ sessionCreationSource.toMetricsConstant()
+ )
+ } catch (e: RemoteException) {
+ Log.e(
+ TAG,
+ "Error notifying server of permission flow state $state from source " +
+ "$sessionCreationSource",
+ e
+ )
+ }
+ }
+
+ companion object {
+ const val TAG = "MediaProjectionMetricsLogger"
+ }
+}
+
+enum class SessionCreationSource {
+ APP,
+ CAST,
+ SYSTEM_UI_SCREEN_RECORDER,
+ UNKNOWN;
+
+ fun toMetricsConstant(): Int =
+ when (this) {
+ APP -> METRICS_CREATION_SOURCE_APP
+ CAST -> METRICS_CREATION_SOURCE_CAST
+ SYSTEM_UI_SCREEN_RECORDER -> METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
+ UNKNOWN -> METRICS_CREATION_SOURCE_UNKNOWN
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
index b5d3e91..0bbcfd9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
@@ -87,14 +87,15 @@
override fun getLayoutResource() = R.layout.media_projection_app_selector
- public override fun onCreate(bundle: Bundle?) {
+ public override fun onCreate(savedInstanceState: Bundle?) {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
component =
componentFactory.create(
hostUserHandle = hostUserHandle,
callingPackage = callingPackage,
view = this,
- resultHandler = this
+ resultHandler = this,
+ isFirstStart = savedInstanceState == null
)
component.lifecycleObservers.forEach { lifecycle.addObserver(it) }
@@ -113,7 +114,7 @@
reviewGrantedConsentRequired =
intent.getBooleanExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, false)
- super.onCreate(bundle)
+ super.onCreate(savedInstanceState)
controller.init()
// we override AppList's AccessibilityDelegate set in ResolverActivity.onCreate because in
// our case this delegate must extend RecyclerViewAccessibilityDelegate, otherwise
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 2217509..8c6f307 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -146,6 +146,9 @@
@BindsInstance @MediaProjectionAppSelector callingPackage: String?,
@BindsInstance view: MediaProjectionAppSelectorView,
@BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler,
+ // Whether the app selector is starting for the first time. False when it is re-starting
+ // due to a config change.
+ @BindsInstance @MediaProjectionAppSelector isFirstStart: Boolean,
): MediaProjectionAppSelectorComponent
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index fced117..69132d3 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -18,6 +18,8 @@
import android.content.ComponentName
import android.os.UserHandle
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
@@ -43,9 +45,17 @@
@MediaProjectionAppSelector private val appSelectorComponentName: ComponentName,
@MediaProjectionAppSelector private val callerPackageName: String?,
private val thumbnailLoader: RecentTaskThumbnailLoader,
+ @MediaProjectionAppSelector private val isFirstStart: Boolean,
+ private val logger: MediaProjectionMetricsLogger,
) {
fun init() {
+ // Only log during the first start of the app selector.
+ // Don't log when the app selector restarts due to a config change.
+ if (isFirstStart) {
+ logger.notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+ }
+
scope.launch {
val recentTasks = recentTaskListProvider.loadRecentTasks()
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index a9e6c53..e9b4582 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -22,6 +22,7 @@
data class RecentTask(
val taskId: Int,
+ val displayId: Int,
@UserIdInt val userId: Int,
val topActivityComponent: ComponentName?,
val baseIntentComponent: ComponentName?,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index aa4c4e5..730aa62 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -60,6 +60,7 @@
.map {
RecentTask(
it.taskId,
+ it.displayId,
it.userId,
it.topActivity,
it.baseIntent?.component,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index fd1a683..ba837db 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -130,10 +130,10 @@
view.width,
view.height
)
- activityOptions.setPendingIntentBackgroundActivityStartMode(
+ activityOptions.pendingIntentBackgroundActivityStartMode =
MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- )
activityOptions.launchCookie = launchCookie
+ activityOptions.launchDisplayId = task.displayId
activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
resultHandler.returnSelectedApp(launchCookie)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index d08d040..2d830d3 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -53,7 +53,9 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.mediaprojection.MediaProjectionServiceHelper;
+import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
@@ -74,6 +76,7 @@
private final FeatureFlags mFeatureFlags;
private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
private final StatusBarManager mStatusBarManager;
+ private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
private String mPackageName;
private int mUid;
@@ -90,15 +93,17 @@
@Inject
public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
- StatusBarManager statusBarManager) {
+ StatusBarManager statusBarManager,
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {
mFeatureFlags = featureFlags;
mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
mStatusBarManager = statusBarManager;
+ mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
}
@Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
final Intent launchingIntent = getIntent();
mReviewGrantedConsentRequired = launchingIntent.getBooleanExtra(
@@ -133,6 +138,10 @@
try {
if (MediaProjectionServiceHelper.hasProjectionPermission(mUid, mPackageName)) {
+ if (savedInstanceState == null) {
+ mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+ SessionCreationSource.APP);
+ }
final IMediaProjection projection =
MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName,
mReviewGrantedConsentRequired);
@@ -231,8 +240,19 @@
mDialog = dialogBuilder.create();
}
+ if (savedInstanceState == null) {
+ mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+ appName == null
+ ? SessionCreationSource.CAST
+ : SessionCreationSource.APP);
+ }
+
setUpDialog(mDialog);
mDialog.show();
+
+ if (savedInstanceState == null) {
+ mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
index 2f10ad3..b9bafd4 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
@@ -89,15 +89,15 @@
}
return listOf(
ScreenShareOption(
- mode = ENTIRE_SCREEN,
- spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
- warningText = entireScreenWarningText
- ),
- ScreenShareOption(
mode = SINGLE_APP,
spinnerText = R.string.screen_share_permission_dialog_option_single_app,
warningText = singleAppWarningText,
spinnerDisabledText = singleAppDisabledText,
+ ),
+ ScreenShareOption(
+ mode = ENTIRE_SCREEN,
+ spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
+ warningText = entireScreenWarningText
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
index 37e8d9f..9bd5783 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
@@ -23,8 +23,8 @@
@IntDef(ENTIRE_SCREEN, SINGLE_APP)
annotation class ScreenShareMode
-const val ENTIRE_SCREEN = 0
-const val SINGLE_APP = 1
+const val SINGLE_APP = 0
+const val ENTIRE_SCREEN = 1
class ScreenShareOption(
@ScreenShareMode val mode: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index c4749e0..c77f3f4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -231,7 +231,6 @@
animation.removeEndListener(this)
if (!canceled) {
-
// The delay between finishing this animation and starting the runnable
val delay = max(0, runnableDelay - elapsedTimeSinceEntry)
@@ -461,7 +460,6 @@
}
private fun handleMoveEvent(event: MotionEvent) {
-
val x = event.x
val y = event.y
@@ -927,17 +925,7 @@
GestureState.ACTIVE -> {
previousXTranslationOnActiveOffset = previousXTranslation
updateRestingArrowDimens()
- if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- vibratorHelper.performHapticFeedback(
- mView,
- HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
- )
- } else {
- vibratorHelper.cancel()
- mainHandler.postDelayed(10L) {
- vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
- }
- }
+ performActivatedHapticFeedback()
val popVelocity =
if (previousState == GestureState.INACTIVE) {
POP_ON_INACTIVE_TO_ACTIVE_VELOCITY
@@ -958,25 +946,24 @@
mView.popOffEdge(POP_ON_INACTIVE_VELOCITY)
- if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- vibratorHelper.performHapticFeedback(
- mView,
- HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
- )
- } else {
- vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT)
- }
+ performDeactivatedHapticFeedback()
updateRestingArrowDimens()
}
GestureState.FLUNG -> {
+ // Typically a vibration is only played while transitioning to ACTIVE. However there
+ // are instances where a fling to trigger back occurs while not in that state.
+ // (e.g. A fling is detected before crossing the trigger threshold.)
+ if (previousState != GestureState.ACTIVE) {
+ performActivatedHapticFeedback()
+ }
mainHandler.postDelayed(POP_ON_FLING_DELAY) {
mView.popScale(POP_ON_FLING_VELOCITY)
}
- updateRestingArrowDimens()
mainHandler.postDelayed(
onEndSetCommittedStateListener.runnable,
MIN_DURATION_FLING_ANIMATION
)
+ updateRestingArrowDimens()
}
GestureState.COMMITTED -> {
// In most cases, animating between states is handled via `updateRestingArrowDimens`
@@ -1011,6 +998,31 @@
}
}
+ private fun performDeactivatedHapticFeedback() {
+ if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ vibratorHelper.performHapticFeedback(
+ mView,
+ HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
+ )
+ } else {
+ vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT)
+ }
+ }
+
+ private fun performActivatedHapticFeedback() {
+ if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ vibratorHelper.performHapticFeedback(
+ mView,
+ HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
+ )
+ } else {
+ vibratorHelper.cancel()
+ mainHandler.postDelayed(10L) {
+ vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
+ }
+ }
+ }
+
private fun convertVelocityToAnimationFactor(
valueOnFastVelocity: Float,
valueOnSlowVelocity: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index 733383e..58c4f0d 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -96,43 +96,9 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-/** Functions that help creating the People tile layouts. */
-public class PeopleTileViewHelper {
- /** Turns on debugging information about People Space. */
- private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
- private static final String TAG = "PeopleTileView";
-
- private static final int DAYS_IN_A_WEEK = 7;
- private static final int ONE_DAY = 1;
-
- public static final int LAYOUT_SMALL = 0;
- public static final int LAYOUT_MEDIUM = 1;
- public static final int LAYOUT_LARGE = 2;
-
- private static final int MIN_CONTENT_MAX_LINES = 2;
- private static final int NAME_MAX_LINES_WITHOUT_LAST_INTERACTION = 3;
- private static final int NAME_MAX_LINES_WITH_LAST_INTERACTION = 1;
-
- private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_NOTIF_CONTENT = 16 + 22 + 8 + 16;
- private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_STATUS_CONTENT = 16 + 16 + 24 + 4 + 16;
- private static final int MIN_MEDIUM_VERTICAL_PADDING = 4;
- private static final int MAX_MEDIUM_PADDING = 16;
- private static final int FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT_BEFORE_PADDING = 8 + 4;
- private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL_VERTICAL = 6 + 4 + 8;
- private static final int FIXED_WIDTH_DIMENS_FOR_SMALL_VERTICAL = 4 + 4;
- private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL_HORIZONTAL = 6 + 4;
- private static final int FIXED_WIDTH_DIMENS_FOR_SMALL_HORIZONTAL = 8 + 8;
-
- private static final int MESSAGES_COUNT_OVERFLOW = 6;
-
- private static final CharSequence EMOJI_CAKE = "\ud83c\udf82";
-
- private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+");
- private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+");
- private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+");
- private static final Pattern MIXED_MARK_PATTERN = Pattern.compile("![?].*|.*[?]!");
-
- static final String BRIEF_PAUSE_ON_TALKBACK = "\n\n";
+/** Variables and functions that is related to Emoji. */
+class EmojiHelper {
+ static final CharSequence EMOJI_CAKE = "\ud83c\udf82";
// This regex can be used to match Unicode emoji characters and character sequences. It's from
// the official Unicode site (https://unicode.org/reports/tr51/#EBNF_and_Regex) with minor
@@ -165,7 +131,44 @@
+ "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})"
+ "?)*";
- private static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX);
+ static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX);
+}
+
+/** Functions that help creating the People tile layouts. */
+public class PeopleTileViewHelper {
+ /** Turns on debugging information about People Space. */
+ private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
+ private static final String TAG = "PeopleTileView";
+
+ private static final int DAYS_IN_A_WEEK = 7;
+ private static final int ONE_DAY = 1;
+
+ public static final int LAYOUT_SMALL = 0;
+ public static final int LAYOUT_MEDIUM = 1;
+ public static final int LAYOUT_LARGE = 2;
+
+ private static final int MIN_CONTENT_MAX_LINES = 2;
+ private static final int NAME_MAX_LINES_WITHOUT_LAST_INTERACTION = 3;
+ private static final int NAME_MAX_LINES_WITH_LAST_INTERACTION = 1;
+
+ private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_NOTIF_CONTENT = 16 + 22 + 8 + 16;
+ private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_STATUS_CONTENT = 16 + 16 + 24 + 4 + 16;
+ private static final int MIN_MEDIUM_VERTICAL_PADDING = 4;
+ private static final int MAX_MEDIUM_PADDING = 16;
+ private static final int FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT_BEFORE_PADDING = 8 + 4;
+ private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL_VERTICAL = 6 + 4 + 8;
+ private static final int FIXED_WIDTH_DIMENS_FOR_SMALL_VERTICAL = 4 + 4;
+ private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL_HORIZONTAL = 6 + 4;
+ private static final int FIXED_WIDTH_DIMENS_FOR_SMALL_HORIZONTAL = 8 + 8;
+
+ private static final int MESSAGES_COUNT_OVERFLOW = 6;
+
+ private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+");
+ private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+");
+ private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+");
+ private static final Pattern MIXED_MARK_PATTERN = Pattern.compile("![?].*|.*[?]!");
+
+ static final String BRIEF_PAUSE_ON_TALKBACK = "\n\n";
public static final String EMPTY_STRING = "";
@@ -831,7 +834,7 @@
if (status.getActivity() == ACTIVITY_BIRTHDAY
|| status.getActivity() == ACTIVITY_UPCOMING_BIRTHDAY) {
- setEmojiBackground(views, EMOJI_CAKE);
+ setEmojiBackground(views, EmojiHelper.EMOJI_CAKE);
}
Icon statusIcon = status.getIcon();
@@ -1072,7 +1075,7 @@
/** Returns emoji if {@code message} has two of the same emoji in sequence. */
@VisibleForTesting
CharSequence getDoubleEmoji(CharSequence message) {
- Matcher unicodeEmojiMatcher = EMOJI_PATTERN.matcher(message);
+ Matcher unicodeEmojiMatcher = EmojiHelper.EMOJI_PATTERN.matcher(message);
// Stores the start and end indices of each matched emoji.
List<Pair<Integer, Integer>> emojiIndices = new ArrayList<>();
// Stores each emoji text.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index c91ed13..8e30740 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -42,7 +42,7 @@
import com.android.systemui.security.data.repository.SecurityRepository
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.user.data.repository.UserSwitcherRepository
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -102,7 +102,7 @@
private val deviceProvisionedController: DeviceProvisionedController,
private val qsSecurityFooterUtils: QSSecurityFooterUtils,
private val fgsManagerController: FgsManagerController,
- private val userInteractor: UserInteractor,
+ private val userSwitcherInteractor: UserSwitcherInteractor,
securityRepository: SecurityRepository,
foregroundServicesRepository: ForegroundServicesRepository,
userSwitcherRepository: UserSwitcherRepository,
@@ -178,6 +178,6 @@
}
override fun showUserSwitcher(expandable: Expandable) {
- userInteractor.showUserSwitcher(expandable)
+ userSwitcherInteractor.showUserSwitcher(expandable)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 5c6e902..a698208 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -38,7 +38,6 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
@@ -53,12 +52,13 @@
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 com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.connectivity.SignalCallback;
import com.android.systemui.statusbar.connectivity.WifiIndicators;
import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor;
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel;
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
@@ -85,10 +85,9 @@
private final NetworkController mNetworkController;
private final DialogLaunchAnimator mDialogLaunchAnimator;
private final Callback mCallback = new Callback();
- private final WifiInteractor mWifiInteractor;
private final TileJavaAdapter mJavaAdapter;
private final FeatureFlags mFeatureFlags;
- private boolean mWifiConnected;
+ private boolean mCastTransportAllowed;
private boolean mHotspotConnected;
@Inject
@@ -107,7 +106,7 @@
NetworkController networkController,
HotspotController hotspotController,
DialogLaunchAnimator dialogLaunchAnimator,
- WifiInteractor wifiInteractor,
+ ConnectivityRepository connectivityRepository,
TileJavaAdapter javaAdapter,
FeatureFlags featureFlags
) {
@@ -117,7 +116,6 @@
mKeyguard = keyguardStateController;
mNetworkController = networkController;
mDialogLaunchAnimator = dialogLaunchAnimator;
- mWifiInteractor = wifiInteractor;
mJavaAdapter = javaAdapter;
mFeatureFlags = featureFlags;
mController.observe(this, mCallback);
@@ -125,7 +123,11 @@
if (!mFeatureFlags.isEnabled(SIGNAL_CALLBACK_DEPRECATION)) {
mNetworkController.observe(this, mSignalCallback);
} else {
- mJavaAdapter.bind(this, mWifiInteractor.getWifiNetwork(), mNetworkModelConsumer);
+ mJavaAdapter.bind(
+ this,
+ connectivityRepository.getDefaultConnections(),
+ mNetworkModelConsumer
+ );
}
hotspotController.observe(this, mHotspotCallback);
}
@@ -282,7 +284,7 @@
}
state.icon = ResourceIcon.get(state.value ? R.drawable.ic_cast_connected
: R.drawable.ic_cast);
- if (canCastToWifi() || state.value) {
+ if (canCastToNetwork() || state.value) {
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
if (!state.value) {
state.secondaryLabel = "";
@@ -291,7 +293,7 @@
state.forceExpandIcon = willPopDialog();
} else {
state.state = Tile.STATE_UNAVAILABLE;
- String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
+ String noWifi = mContext.getString(R.string.quick_settings_cast_no_network);
state.secondaryLabel = noWifi;
state.forceExpandIcon = false;
}
@@ -308,13 +310,13 @@
: mContext.getString(R.string.quick_settings_cast_device_default_name);
}
- private boolean canCastToWifi() {
- return mWifiConnected || mHotspotConnected;
+ private boolean canCastToNetwork() {
+ return mCastTransportAllowed || mHotspotConnected;
}
- private void setWifiConnected(boolean connected) {
- if (connected != mWifiConnected) {
- mWifiConnected = connected;
+ private void setCastTransportAllowed(boolean connected) {
+ if (connected != mCastTransportAllowed) {
+ mCastTransportAllowed = connected;
// Hotspot is not connected, so changes here should update
if (!mHotspotConnected) {
refreshState();
@@ -326,14 +328,17 @@
if (connected != mHotspotConnected) {
mHotspotConnected = connected;
// Wifi is not connected, so changes here should update
- if (!mWifiConnected) {
+ if (!mCastTransportAllowed) {
refreshState();
}
}
}
- private final Consumer<WifiNetworkModel> mNetworkModelConsumer = (model) -> {
- setWifiConnected(model instanceof WifiNetworkModel.Active);
+ private final Consumer<DefaultConnectionModel> mNetworkModelConsumer = (model) -> {
+ boolean isWifiDefault = model.getWifi().isDefault();
+ boolean isEthernetDefault = model.getEthernet().isDefault();
+ boolean hasCellularTransport = model.getMobile().isDefault();
+ setCastTransportAllowed((isWifiDefault || isEthernetDefault) && !hasCellularTransport);
};
private final SignalCallback mSignalCallback = new SignalCallback() {
@@ -342,7 +347,7 @@
// statusIcon.visible has the connected status information
boolean enabledAndConnected = indicators.enabled
&& (indicators.qsIcon != null && indicators.qsIcon.visible);
- setWifiConnected(enabledAndConnected);
+ setCastTransportAllowed(enabledAndConnected);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 959afd8..e27a59c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -30,12 +30,12 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile;
@@ -45,6 +45,7 @@
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -69,6 +70,7 @@
private final DialogLaunchAnimator mDialogLaunchAnimator;
private final FeatureFlags mFlags;
private final PanelInteractor mPanelInteractor;
+ private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
private long mMillisUntilFinished = 0;
@@ -88,7 +90,8 @@
KeyguardDismissUtil keyguardDismissUtil,
KeyguardStateController keyguardStateController,
DialogLaunchAnimator dialogLaunchAnimator,
- PanelInteractor panelInteractor
+ PanelInteractor panelInteractor,
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger
) {
super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
@@ -99,6 +102,7 @@
mKeyguardStateController = keyguardStateController;
mDialogLaunchAnimator = dialogLaunchAnimator;
mPanelInteractor = panelInteractor;
+ mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
}
@Override
@@ -190,6 +194,9 @@
} else {
dialog.show();
}
+
+ mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed();
+
return false;
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index 1b0d5f9..2f8fe42 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -32,7 +32,6 @@
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -43,6 +42,7 @@
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 com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -55,7 +55,7 @@
private final KeyguardStateController mKeyguard;
protected IndividualSensorPrivacyController mSensorPrivacyController;
- private final SafetyCenterManager mSafetyCenterManager;
+ private final Boolean mIsSafetyCenterEnabled;
/**
* @return Id of the sensor that will be toggled
@@ -89,7 +89,7 @@
statusBarStateController, activityStarter, qsLogger);
mSensorPrivacyController = sensorPrivacyController;
mKeyguard = keyguardStateController;
- mSafetyCenterManager = safetyCenterManager;
+ mIsSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabled();
mSensorPrivacyController.observe(getLifecycle(), this);
}
@@ -138,7 +138,7 @@
@Override
public Intent getLongClickIntent() {
- if (mSafetyCenterManager.isSafetyCenterEnabled()) {
+ if (mIsSafetyCenterEnabled) {
return new Intent(Settings.ACTION_PRIVACY_CONTROLS);
} else {
return new Intent(Settings.ACTION_PRIVACY_SETTINGS);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
index 70a683b..f8de365 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
@@ -19,8 +19,8 @@
import androidx.annotation.GuardedBy
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.dagger.QSTilesDefaultLog
import com.android.systemui.log.dagger.QSTilesLogBuffers
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -29,14 +29,13 @@
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.statusbar.StatusBarState
import javax.inject.Inject
-import javax.inject.Provider
@SysUISingleton
class QSTileLogger
@Inject
constructor(
@QSTilesLogBuffers logBuffers: Map<TileSpec, LogBuffer>,
- @QSTilesDefaultLog private val defaultLogBufferProvider: Provider<LogBuffer>,
+ private val factory: LogBufferFactory,
private val mStatusBarStateController: StatusBarStateController,
) {
@GuardedBy("logBufferCache") private val logBufferCache = logBuffers.toMutableMap()
@@ -154,7 +153,13 @@
private fun TileSpec.getLogBuffer(): LogBuffer =
synchronized(logBufferCache) {
- logBufferCache.getOrPut(this) { defaultLogBufferProvider.get() }
+ logBufferCache.getOrPut(this) {
+ factory.create(
+ "QSTileLog_${this.getLogTag()}",
+ BUFFER_MAX_SIZE /* maxSize */,
+ false /* systrace */
+ )
+ }
}
private fun StateUpdateTrigger.toLogString(): String =
@@ -185,5 +190,6 @@
private companion object {
const val TAG_FORMAT_PREFIX = "QSLog"
const val DATA_MAX_LENGTH = 50
+ const val BUFFER_MAX_SIZE = 25
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 7a0c087..f469c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -38,6 +38,8 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
+import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
import com.android.systemui.plugins.ActivityStarter;
@@ -45,13 +47,13 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.CallbackController;
+import dagger.Lazy;
+
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import javax.inject.Inject;
-import dagger.Lazy;
-
/**
* Helper class to initiate a screen recording
*/
@@ -71,6 +73,7 @@
private final FeatureFlags mFlags;
private final UserContextProvider mUserContextProvider;
private final UserTracker mUserTracker;
+ private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
protected static final String INTENT_UPDATE_STATE =
"com.android.systemui.screenrecord.UPDATE_STATE";
@@ -115,7 +118,8 @@
FeatureFlags flags,
UserContextProvider userContextProvider,
Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver,
- UserTracker userTracker) {
+ UserTracker userTracker,
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {
mMainExecutor = mainExecutor;
mContext = context;
mFlags = flags;
@@ -123,6 +127,7 @@
mBroadcastDispatcher = broadcastDispatcher;
mUserContextProvider = userContextProvider;
mUserTracker = userTracker;
+ mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setInteractive(true);
@@ -149,6 +154,9 @@
return new ScreenCaptureDisabledDialog(mContext);
}
+ mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+ SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
+
return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
? new ScreenRecordPermissionDialog(context, getHostUserHandle(), this,
activityStarter, mUserContextProvider, onStartRecordingClicked)
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index a1d5d98..ade93b1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -186,14 +186,14 @@
private fun createOptionList(): List<ScreenShareOption> {
return listOf(
ScreenShareOption(
- ENTIRE_SCREEN,
- R.string.screen_share_permission_dialog_option_entire_screen,
- R.string.screenrecord_permission_dialog_warning_entire_screen
- ),
- ScreenShareOption(
SINGLE_APP,
R.string.screen_share_permission_dialog_option_single_app,
R.string.screenrecord_permission_dialog_warning_single_app
+ ),
+ ScreenShareOption(
+ ENTIRE_SCREEN,
+ R.string.screen_share_permission_dialog_option_entire_screen,
+ R.string.screenrecord_permission_dialog_warning_entire_screen
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 5154067..c34fd42 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -20,11 +20,10 @@
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
import java.util.function.Consumer
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/** Processes a screenshot request sent from [ScreenshotHelper]. */
interface ScreenshotRequestProcessor {
@@ -36,16 +35,15 @@
suspend fun process(screenshot: ScreenshotData): ScreenshotData
}
-/**
- * Implementation of [ScreenshotRequestProcessor]
- */
+/** Implementation of [ScreenshotRequestProcessor] */
@SysUISingleton
-class RequestProcessor @Inject constructor(
- private val capture: ImageCapture,
- private val policy: ScreenshotPolicy,
- private val flags: FeatureFlags,
- /** For the Java Async version, to invoke the callback. */
- @Application private val mainScope: CoroutineScope
+class RequestProcessor
+@Inject
+constructor(
+ private val capture: ImageCapture,
+ private val policy: ScreenshotPolicy,
+ /** For the Java Async version, to invoke the callback. */
+ @Application private val mainScope: CoroutineScope
) : ScreenshotRequestProcessor {
override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
@@ -67,8 +65,9 @@
result.userHandle = info.user
if (policy.isManagedProfile(info.user.identifier)) {
- val image = capture.captureTask(info.taskId)
- ?: error("Task snapshot returned a null Bitmap!")
+ val image =
+ capture.captureTask(info.taskId)
+ ?: throw RequestProcessorException("Task snapshot returned a null Bitmap!")
// Provide the task snapshot as the screenshot
result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE
@@ -97,3 +96,6 @@
}
private const val TAG = "RequestProcessor"
+
+/** Exception thrown by [RequestProcessor] if something goes wrong. */
+class RequestProcessorException(message: String) : IllegalStateException(message)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 4b3bd0b..21a08a9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -303,6 +303,13 @@
private String mPackageName = "";
private BroadcastReceiver mCopyBroadcastReceiver;
+ // When false, the screenshot is taken without showing the ui. Note that this only applies to
+ // external displays, as on the default one the UI should **always** be shown.
+ // This is needed in case of screenshot during display mirroring, as adding another window to
+ // the external display makes mirroring stop.
+ // When there is a way to distinguish between displays that are mirroring or extending, this
+ // can be removed and we can directly show the ui only in the extended case.
+ private final Boolean mShowUIOnExternalDisplay;
/** Tracks config changes that require re-creating UI */
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
ActivityInfo.CONFIG_ORIENTATION
@@ -318,7 +325,7 @@
Context context,
FeatureFlags flags,
ScreenshotSmartActions screenshotSmartActions,
- ScreenshotNotificationsController screenshotNotificationsController,
+ ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
ScrollCaptureClient scrollCaptureClient,
UiEventLogger uiEventLogger,
ImageExporter imageExporter,
@@ -335,10 +342,11 @@
AssistContentRequester assistContentRequester,
MessageContainerController messageContainerController,
Provider<ScreenshotSoundController> screenshotSoundController,
- @Assisted int displayId
+ @Assisted int displayId,
+ @Assisted boolean showUIOnExternalDisplay
) {
mScreenshotSmartActions = screenshotSmartActions;
- mNotificationsController = screenshotNotificationsController;
+ mNotificationsController = screenshotNotificationsControllerFactory.create(displayId);
mScrollCaptureClient = scrollCaptureClient;
mUiEventLogger = uiEventLogger;
mImageExporter = imageExporter;
@@ -401,6 +409,7 @@
mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
ClipboardOverlayController.COPY_OVERLAY_ACTION),
ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED);
+ mShowUIOnExternalDisplay = showUIOnExternalDisplay;
}
void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher,
@@ -448,6 +457,23 @@
prepareViewForNewScreenshot(screenshot, oldPackageName);
+ if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) {
+ mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
+ new AssistContentRequester.Callback() {
+ @Override
+ public void onAssistContentAvailable(AssistContent assistContent) {
+ screenshot.setContextUrl(assistContent.getWebUri());
+ }
+ });
+ }
+
+ if (!shouldShowUi()) {
+ saveScreenshotInWorkerThread(
+ screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady,
+ (ignored) -> {});
+ return;
+ }
+
saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
@@ -482,22 +508,16 @@
screenshot.getUserHandle()));
mScreenshotView.setScreenshot(screenshot);
- if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) {
- mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
- new AssistContentRequester.Callback() {
- @Override
- public void onAssistContentAvailable(AssistContent assistContent) {
- screenshot.setContextUrl(assistContent.getWebUri());
- }
- });
- }
-
// ignore system bar insets for the purpose of window layout
mWindow.getDecorView().setOnApplyWindowInsetsListener(
(v, insets) -> WindowInsets.CONSUMED);
mScreenshotHandler.cancelTimeout(); // restarted after animation
}
+ private boolean shouldShowUi() {
+ return mDisplayId == Display.DEFAULT_DISPLAY || mShowUIOnExternalDisplay;
+ }
+
void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) {
withWindowAttached(() -> {
if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
@@ -1199,7 +1219,13 @@
/** Injectable factory to create screenshot controller instances for a specific display. */
@AssistedFactory
public interface Factory {
- /** Creates an instance of the controller for that specific displayId. */
- ScreenshotController create(int displayId);
+ /**
+ * Creates an instance of the controller for that specific displayId.
+ *
+ * @param displayId: display to capture
+ * @param showUIOnExternalDisplay: Whether the UI should be shown if this is an external
+ * display.
+ */
+ ScreenshotController create(int displayId, boolean showUIOnExternalDisplay);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
deleted file mode 100644
index 4344fd1..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2019 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.screenshot;
-
-import static android.content.Context.NOTIFICATION_SERVICE;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.os.UserHandle;
-import android.util.DisplayMetrics;
-import android.view.WindowManager;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.systemui.res.R;
-import com.android.systemui.SystemUIApplication;
-import com.android.systemui.util.NotificationChannels;
-
-import javax.inject.Inject;
-
-/**
- * Convenience class to handle showing and hiding notifications while taking a screenshot.
- */
-public class ScreenshotNotificationsController {
- private static final String TAG = "ScreenshotNotificationManager";
-
- private final Context mContext;
- private final Resources mResources;
- private final NotificationManager mNotificationManager;
-
- @Inject
- ScreenshotNotificationsController(Context context, WindowManager windowManager) {
- mContext = context;
- mResources = context.getResources();
- mNotificationManager =
- (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
-
- DisplayMetrics displayMetrics = new DisplayMetrics();
- windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
- }
-
- /**
- * Sends a notification that the screenshot capture has failed.
- */
- public void notifyScreenshotError(int msgResId) {
- Resources res = mContext.getResources();
- String errorMsg = res.getString(msgResId);
-
- // Repurpose the existing notification to notify the user of the error
- Notification.Builder b = new Notification.Builder(mContext, NotificationChannels.ALERTS)
- .setTicker(res.getString(R.string.screenshot_failed_title))
- .setContentTitle(res.getString(R.string.screenshot_failed_title))
- .setContentText(errorMsg)
- .setSmallIcon(R.drawable.stat_notify_image_error)
- .setWhen(System.currentTimeMillis())
- .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
- .setCategory(Notification.CATEGORY_ERROR)
- .setAutoCancel(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- final DevicePolicyManager dpm =
- (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- final Intent intent =
- dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
- if (intent != null) {
- final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
- mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
- b.setContentIntent(pendingIntent);
- }
-
- SystemUIApplication.overrideNotificationAppName(mContext, b, true);
-
- Notification n = new Notification.BigTextStyle(b)
- .bigText(errorMsg)
- .build();
- mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt
new file mode 100644
index 0000000..d874eb6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.screenshot
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.os.UserHandle
+import android.view.Display
+import com.android.internal.R
+import com.android.internal.messages.nano.SystemMessageProto
+import com.android.systemui.SystemUIApplication
+import com.android.systemui.util.NotificationChannels
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Convenience class to handle showing and hiding notifications while taking a screenshot. */
+class ScreenshotNotificationsController
+@AssistedInject
+internal constructor(
+ @Assisted private val displayId: Int,
+ private val context: Context,
+ private val notificationManager: NotificationManager,
+ private val devicePolicyManager: DevicePolicyManager,
+) {
+ private val res = context.resources
+
+ /**
+ * Sends a notification that the screenshot capture has failed.
+ *
+ * Errors for the non-default display are shown in a unique separate notification.
+ */
+ fun notifyScreenshotError(msgResId: Int) {
+ val displayErrorString =
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ " ($externalDisplayString)"
+ } else {
+ ""
+ }
+ val errorMsg = res.getString(msgResId) + displayErrorString
+
+ // Repurpose the existing notification or create a new one
+ val builder =
+ Notification.Builder(context, NotificationChannels.ALERTS)
+ .setTicker(res.getString(com.android.systemui.res.R.string.screenshot_failed_title))
+ .setContentTitle(
+ res.getString(com.android.systemui.res.R.string.screenshot_failed_title)
+ )
+ .setContentText(errorMsg)
+ .setSmallIcon(com.android.systemui.res.R.drawable.stat_notify_image_error)
+ .setWhen(System.currentTimeMillis())
+ .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
+ .setCategory(Notification.CATEGORY_ERROR)
+ .setAutoCancel(true)
+ .setColor(context.getColor(R.color.system_notification_accent_color))
+ val intent =
+ devicePolicyManager.createAdminSupportIntent(
+ DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE
+ )
+ if (intent != null) {
+ val pendingIntent =
+ PendingIntent.getActivityAsUser(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE,
+ null,
+ UserHandle.CURRENT
+ )
+ builder.setContentIntent(pendingIntent)
+ }
+ SystemUIApplication.overrideNotificationAppName(context, builder, true)
+ val notification = Notification.BigTextStyle(builder).bigText(errorMsg).build()
+ // A different id for external displays to keep the 2 error notifications separated.
+ val id =
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT
+ } else {
+ SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY
+ }
+ notificationManager.notify(id, notification)
+ }
+
+ private val externalDisplayString: String
+ get() =
+ res.getString(
+ com.android.systemui.res.R.string.screenshot_failed_external_display_indication
+ )
+
+ /** Factory for [ScreenshotNotificationsController]. */
+ @AssistedFactory
+ interface Factory {
+ fun create(displayId: Int = Display.DEFAULT_DISPLAY): ScreenshotNotificationsController
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
index 070fb1e..049799e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
@@ -16,25 +16,29 @@
package com.android.systemui.screenshot;
+import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.view.WindowManager;
+import android.view.Display;
import com.android.systemui.res.R;
/**
- * Performs a number of miscellaneous, non-system-critical actions
- * after the system has finished booting.
+ * Receives errors related to screenshot.
*/
public class ScreenshotServiceErrorReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
// Show a message that we've failed to save the image to disk
- WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- ScreenshotNotificationsController controller =
- new ScreenshotNotificationsController(context, wm);
+ NotificationManager notificationManager = context.getSystemService(
+ NotificationManager.class);
+ DevicePolicyManager devicePolicyManager = context.getSystemService(
+ DevicePolicyManager.class);
+ ScreenshotNotificationsController controller = new ScreenshotNotificationsController(
+ Display.DEFAULT_DISPLAY, context, notificationManager, devicePolicyManager);
controller.notifyScreenshotError(R.string.screenshot_failed_to_save_unknown_text);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 03c3f7a..0158284 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -10,6 +10,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.res.R
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
import java.util.function.Consumer
import javax.inject.Inject
@@ -34,7 +36,8 @@
displayRepository: DisplayRepository,
@Application private val mainScope: CoroutineScope,
private val screenshotRequestProcessor: ScreenshotRequestProcessor,
- private val uiEventLogger: UiEventLogger
+ private val uiEventLogger: UiEventLogger,
+ private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory,
) {
private lateinit var displays: StateFlow<Set<Display>>
@@ -44,6 +47,7 @@
}
private val screenshotControllers = mutableMapOf<Int, ScreenshotController>()
+ private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>()
/**
* Executes the [ScreenshotRequest].
@@ -58,40 +62,68 @@
) {
val displayIds = getDisplaysToScreenshot(screenshotRequest.type)
val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
- screenshotRequest.oneForEachDisplay(displayIds).forEach { screenshotData: ScreenshotData ->
+ displayIds.forEach { displayId: Int ->
dispatchToController(
- screenshotData = screenshotData,
+ rawScreenshotData = ScreenshotData.fromRequest(screenshotRequest, displayId),
onSaved =
- if (screenshotData.displayId == Display.DEFAULT_DISPLAY) onSaved else { _ -> },
- callback = resultCallbackWrapper.createCallbackForId(screenshotData.displayId)
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ onSaved
+ } else { _ -> },
+ callback = resultCallbackWrapper.createCallbackForId(displayId)
)
}
}
- /** Creates a [ScreenshotData] for each display. */
- private suspend fun ScreenshotRequest.oneForEachDisplay(
- displayIds: List<Int>
- ): List<ScreenshotData> {
- return displayIds
- .map { displayId -> ScreenshotData.fromRequest(this, displayId) }
- .map { screenshotData: ScreenshotData ->
- screenshotRequestProcessor.process(screenshotData)
- }
- }
-
- private fun dispatchToController(
- screenshotData: ScreenshotData,
+ /** All logging should be triggered only by this method. */
+ private suspend fun dispatchToController(
+ rawScreenshotData: ScreenshotData,
onSaved: (Uri) -> Unit,
callback: RequestCallback
) {
+ // Let's wait before logging "screenshot requested", as we should log the processed
+ // ScreenshotData.
+ val screenshotData =
+ try {
+ screenshotRequestProcessor.process(rawScreenshotData)
+ } catch (e: RequestProcessorException) {
+ Log.e(TAG, "Failed to process screenshot request!", e)
+ logScreenshotRequested(rawScreenshotData)
+ onFailedScreenshotRequest(rawScreenshotData, callback)
+ return
+ }
+
+ logScreenshotRequested(screenshotData)
+ Log.d(TAG, "Screenshot request: $screenshotData")
+ try {
+ getScreenshotController(screenshotData.displayId)
+ .handleScreenshot(screenshotData, onSaved, callback)
+ } catch (e: IllegalStateException) {
+ Log.e(TAG, "Error while ScreenshotController was handling ScreenshotData!", e)
+ onFailedScreenshotRequest(screenshotData, callback)
+ return // After a failure log, nothing else should run.
+ }
+ }
+
+ /**
+ * This should be logged also in case of failed requests, before the [SCREENSHOT_CAPTURE_FAILED]
+ * event.
+ */
+ private fun logScreenshotRequested(screenshotData: ScreenshotData) {
uiEventLogger.log(
ScreenshotEvent.getScreenshotSource(screenshotData.source),
0,
screenshotData.packageNameString
)
- Log.d(TAG, "Screenshot request: $screenshotData")
- getScreenshotController(screenshotData.displayId)
- .handleScreenshot(screenshotData, onSaved, callback)
+ }
+
+ private fun onFailedScreenshotRequest(
+ screenshotData: ScreenshotData,
+ callback: RequestCallback
+ ) {
+ uiEventLogger.log(SCREENSHOT_CAPTURE_FAILED, 0, screenshotData.packageNameString)
+ getNotificationController(screenshotData.displayId)
+ .notifyScreenshotError(R.string.screenshot_failed_to_capture_text)
+ callback.reportError()
}
private fun getDisplaysToScreenshot(requestType: Int): List<Int> {
@@ -135,7 +167,15 @@
}
private fun getScreenshotController(id: Int): ScreenshotController {
- return screenshotControllers.computeIfAbsent(id) { screenshotControllerFactory.create(id) }
+ return screenshotControllers.computeIfAbsent(id) {
+ screenshotControllerFactory.create(id, /* showUIOnExternalDisplay= */ false)
+ }
+ }
+
+ private fun getNotificationController(id: Int): ScreenshotNotificationsController {
+ return notificationControllers.computeIfAbsent(id) {
+ screenshotNotificationControllerFactory.create(id)
+ }
}
/** For java compatibility only. see [executeScreenshots] */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 0be2265..0991c9a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -113,7 +113,8 @@
@Inject
public TakeScreenshotService(ScreenshotController.Factory screenshotControllerFactory,
UserManager userManager, DevicePolicyManager devicePolicyManager,
- UiEventLogger uiEventLogger, ScreenshotNotificationsController notificationsController,
+ UiEventLogger uiEventLogger,
+ ScreenshotNotificationsController.Factory notificationsControllerFactory,
Context context, @Background Executor bgExecutor, FeatureFlags featureFlags,
RequestProcessor processor, Provider<TakeScreenshotExecutor> takeScreenshotExecutor) {
if (DEBUG_SERVICE) {
@@ -123,7 +124,7 @@
mUserManager = userManager;
mDevicePolicyManager = devicePolicyManager;
mUiEventLogger = uiEventLogger;
- mNotificationsController = notificationsController;
+ mNotificationsController = notificationsControllerFactory.create(Display.DEFAULT_DISPLAY);
mContext = context;
mBgExecutor = bgExecutor;
mFeatureFlags = featureFlags;
@@ -132,7 +133,8 @@
if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
mScreenshot = null;
} else {
- mScreenshot = screenshotControllerFactory.create(Display.DEFAULT_DISPLAY);
+ mScreenshot = screenshotControllerFactory.create(
+ Display.DEFAULT_DISPLAY, /* showUIOnExternalDisplay= */ false);
}
}
@@ -245,15 +247,17 @@
Log.d(TAG, "Processing screenshot data");
- ScreenshotData screenshotData = ScreenshotData.fromRequest(
- request, Display.DEFAULT_DISPLAY);
+ if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
+ mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback);
+ return;
+ }
+ // TODO(b/295143676): Delete the following after the flag is released.
try {
- if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
- mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback);
- } else {
- mProcessor.processAsync(screenshotData, (data) ->
- dispatchToController(data, onSaved, callback));
- }
+ ScreenshotData screenshotData = ScreenshotData.fromRequest(
+ request, Display.DEFAULT_DISPLAY);
+ mProcessor.processAsync(screenshotData, (data) ->
+ dispatchToController(data, onSaved, callback));
+
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to process screenshot request!", e);
logFailedRequest(request);
@@ -263,6 +267,7 @@
}
}
+ // TODO(b/295143676): Delete this.
private void dispatchToController(ScreenshotData screenshot,
Consumer<Uri> uriConsumer, RequestCallback callback) {
mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0,
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index cf1fbe3..d6f1ed9 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -68,6 +68,10 @@
*/
@WeaklyReferencedCallback
interface Callback {
+ /**
+ * Notifies that the current user will be changed.
+ */
+ fun onBeforeUserSwitching(newUser: Int) {}
/**
* Same as {@link onUserChanging(Int, Context, Runnable)} but the callback will be
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 393a698..9f416bb 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -90,6 +90,7 @@
private val isBackgroundUserSwitchEnabled: Boolean get() =
featureFlagsProvider.get().isEnabled(Flags.USER_TRACKER_BACKGROUND_CALLBACKS)
+ @Deprecated("Use UserInteractor.getSelectedUserId()")
override var userId: Int by SynchronizedDelegate(context.userId)
protected set
@@ -226,6 +227,13 @@
protected open fun handleBeforeUserSwitching(newUserId: Int) {
Assert.isNotMainThread()
setUserIdInternal(newUserId)
+
+ val list = synchronized(callbacks) {
+ callbacks.toList()
+ }
+ list.forEach {
+ it.callback.get()?.onBeforeUserSwitching(newUserId)
+ }
}
@WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 6783afa..1ecb127 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -21,6 +21,7 @@
import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;
import android.app.Activity;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.Gravity;
@@ -35,8 +36,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -74,21 +75,26 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setWindowAttributes();
+ setContentView(R.layout.brightness_mirror_container);
+ setBrightnessDialogViewAttributes();
+ }
+ private void setWindowAttributes() {
final Window window = getWindow();
- window.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
+ window.setGravity(Gravity.TOP | Gravity.LEFT);
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.requestFeature(Window.FEATURE_NO_TITLE);
// Calling this creates the decor View, so setLayout takes proper effect
// (see Dialog#onWindowAttributesChanged)
window.getDecorView();
- window.setLayout(
- WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
+ window.setLayout(WRAP_CONTENT, WRAP_CONTENT);
getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false);
+ }
- setContentView(R.layout.brightness_mirror_container);
+ void setBrightnessDialogViewAttributes() {
FrameLayout frame = findViewById(R.id.brightness_mirror_container);
// The brightness mirror container is INVISIBLE by default.
frame.setVisibility(View.VISIBLE);
@@ -97,6 +103,14 @@
getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
lp.leftMargin = horizontalMargin;
lp.rightMargin = horizontalMargin;
+
+ int verticalMargin =
+ getResources().getDimensionPixelSize(
+ R.dimen.notification_guts_option_vertical_padding);
+
+ lp.topMargin = verticalMargin;
+ lp.bottomMargin = verticalMargin;
+
frame.setLayoutParams(lp);
Rect bounds = new Rect();
frame.addOnLayoutChangeListener(
@@ -113,6 +127,20 @@
frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
mBrightnessController = mBrightnessControllerFactory.create(controller);
+
+ Configuration configuration = getResources().getConfiguration();
+ int orientation = configuration.orientation;
+
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ lp.width = getWindowManager().getDefaultDisplay().getWidth() / 2
+ - lp.leftMargin * 2;
+ } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ lp.width = getWindowManager().getDefaultDisplay().getWidth()
+ - lp.leftMargin * 2;
+ }
+
+ frame.setLayoutParams(lp);
+
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ba0cf08..8dc97c0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -414,9 +414,7 @@
private int mDisplayLeftInset = 0; // in pixels
@VisibleForTesting
- KeyguardClockPositionAlgorithm
- mClockPositionAlgorithm =
- new KeyguardClockPositionAlgorithm();
+ KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
private final KeyguardClockPositionAlgorithm.Result
mClockPositionResult =
new KeyguardClockPositionAlgorithm.Result();
@@ -779,7 +777,8 @@
KeyguardViewConfigurator keyguardViewConfigurator,
KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
SplitShadeStateController splitShadeStateController,
- PowerInteractor powerInteractor) {
+ PowerInteractor powerInteractor,
+ KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm) {
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onKeyguardFadingAwayChanged() {
@@ -807,6 +806,7 @@
mKeyguardInteractor = keyguardInteractor;
mPowerInteractor = powerInteractor;
mKeyguardViewConfigurator = keyguardViewConfigurator;
+ mClockPositionAlgorithm = keyguardClockPositionAlgorithm;
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -1446,9 +1446,7 @@
mBarState);
}
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
- setKeyguardVisibility(mBarState, false);
- } else {
+ if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
setKeyguardBottomAreaVisibility(mBarState, false);
}
@@ -2358,14 +2356,6 @@
}
}
- private void setKeyguardVisibility(int statusBarState, boolean goingToFullShade) {
- mKeyguardInteractor.setKeyguardRootVisibility(
- statusBarState,
- goingToFullShade,
- mIsOcclusionTransitionRunning
- );
- }
-
@Deprecated
private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
mKeyguardBottomArea.animate().cancel();
@@ -4443,11 +4433,13 @@
&& statusBarState == KEYGUARD) {
// This means we're doing the screen off animation - position the keyguard status
// view where it'll be on AOD, so we can animate it in.
- mKeyguardStatusViewController.updatePosition(
- mClockPositionResult.clockX,
- mClockPositionResult.clockYFullyDozing,
- mClockPositionResult.clockScale,
- false /* animate */);
+ if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ mKeyguardStatusViewController.updatePosition(
+ mClockPositionResult.clockX,
+ mClockPositionResult.clockYFullyDozing,
+ mClockPositionResult.clockScale,
+ false /* animate */);
+ }
}
mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
@@ -4456,9 +4448,7 @@
goingToFullShade,
mBarState);
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
- setKeyguardVisibility(statusBarState, goingToFullShade);
- } else {
+ if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
}
@@ -4562,7 +4552,12 @@
public void showAodUi() {
setDozing(true /* dozing */, false /* animate */);
mStatusBarStateController.setUpcomingState(KEYGUARD);
- mStatusBarStateListener.onStateChanged(KEYGUARD);
+
+ if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ mStatusBarStateController.setState(KEYGUARD);
+ } else {
+ mStatusBarStateListener.onStateChanged(KEYGUARD);
+ }
mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
setExpandedFraction(1f);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 6f5e41f..0426388 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -47,7 +47,6 @@
import android.view.WindowManagerGlobal;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -72,6 +71,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import dagger.Lazy;
@@ -112,6 +112,7 @@
private final KeyguardBypassController mKeyguardBypassController;
private final Executor mBackgroundExecutor;
private final AuthController mAuthController;
+ private final Lazy<SelectedUserInteractor> mUserInteractor;
private final Lazy<ShadeInteractor> mShadeInteractorLazy;
private ViewGroup mWindowRootView;
private LayoutParams mLp;
@@ -157,7 +158,8 @@
AuthController authController,
ShadeExpansionStateManager shadeExpansionStateManager,
Lazy<ShadeInteractor> shadeInteractorLazy,
- ShadeWindowLogger logger) {
+ ShadeWindowLogger logger,
+ Lazy<SelectedUserInteractor> userInteractor) {
mContext = context;
mWindowRootViewComponentFactory = windowRootViewComponentFactory;
mWindowManager = windowManager;
@@ -174,6 +176,7 @@
mScreenOffAnimationController = screenOffAnimationController;
dumpManager.registerDumpable(this);
mAuthController = authController;
+ mUserInteractor = userInteractor;
mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
mLockScreenDisplayTimeout = context.getResources()
.getInteger(R.integer.config_lockScreenDisplayTimeout);
@@ -348,7 +351,7 @@
boolean onKeyguard = state.statusBarState == StatusBarState.KEYGUARD
&& !state.keyguardFadingAway && !state.keyguardGoingAway;
if (onKeyguard
- && mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
+ && mAuthController.isUdfpsEnrolled(mUserInteractor.get().getSelectedUserId())) {
// both max and min display refresh rate must be set to take effect:
mLpChanged.preferredMaxDisplayRefreshRate = mKeyguardPreferredRefreshRate;
mLpChanged.preferredMinDisplayRefreshRate = mKeyguardPreferredRefreshRate;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index d05dfe2..d869239 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -35,18 +35,20 @@
import com.android.keyguard.LockIconViewController;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.Dumpable;
+import com.android.systemui.FeatureFlags;
import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.back.domain.interactor.BackActionInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder;
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -55,7 +57,6 @@
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.log.BouncerLogger;
-import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
import com.android.systemui.statusbar.DragDownHelper;
@@ -75,6 +76,7 @@
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.time.SystemClock;
import java.io.PrintWriter;
@@ -104,8 +106,10 @@
private final PulsingGestureListener mPulsingGestureListener;
private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
private final NotificationInsetsController mNotificationInsetsController;
+ private final CommunalViewModel mCommunalViewModel;
private final boolean mIsTrackpadCommonEnabled;
private final FeatureFlags mFeatureFlags;
+ private final FeatureFlagsClassic mFeatureFlagsClassic;
private final SysUIKeyEventHandler mSysUIKeyEventHandler;
private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
@@ -128,8 +132,6 @@
private final CentralSurfaces mService;
private final DozeServiceHost mDozeServiceHost;
private final DozeScrimController mDozeScrimController;
- private final BackActionInteractor mBackActionInteractor;
- private final PowerInteractor mPowerInteractor;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private DragDownHelper mDragDownHelper;
private boolean mExpandingBelowNotch;
@@ -164,8 +166,6 @@
CentralSurfaces centralSurfaces,
DozeServiceHost dozeServiceHost,
DozeScrimController dozeScrimController,
- BackActionInteractor backActionInteractor,
- PowerInteractor powerInteractor,
NotificationShadeWindowController controller,
Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
@@ -180,14 +180,17 @@
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
KeyguardTransitionInteractor keyguardTransitionInteractor,
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
+ CommunalViewModel communalViewModel,
NotificationExpansionRepository notificationExpansionRepository,
+ FeatureFlagsClassic featureFlagsClassic,
FeatureFlags featureFlags,
SystemClock clock,
BouncerMessageInteractor bouncerMessageInteractor,
BouncerLogger bouncerLogger,
SysUIKeyEventHandler sysUIKeyEventHandler,
PrimaryBouncerInteractor primaryBouncerInteractor,
- AlternateBouncerInteractor alternateBouncerInteractor) {
+ AlternateBouncerInteractor alternateBouncerInteractor,
+ SelectedUserInteractor selectedUserInteractor) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
mStatusBarStateController = statusBarStateController;
@@ -200,20 +203,20 @@
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mStatusBarWindowStateController = statusBarWindowStateController;
mLockIconViewController = lockIconViewController;
- mBackActionInteractor = backActionInteractor;
mShadeLogger = shadeLogger;
mService = centralSurfaces;
mDozeServiceHost = dozeServiceHost;
mDozeScrimController = dozeScrimController;
- mPowerInteractor = powerInteractor;
mNotificationShadeWindowController = controller;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mAmbientState = ambientState;
mPulsingGestureListener = pulsingGestureListener;
mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
mNotificationInsetsController = notificationInsetsController;
- mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
+ mCommunalViewModel = communalViewModel;
+ mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON);
mFeatureFlags = featureFlags;
+ mFeatureFlagsClassic = featureFlagsClassic;
mSysUIKeyEventHandler = sysUIKeyEventHandler;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mAlternateBouncerInteractor = alternateBouncerInteractor;
@@ -229,7 +232,8 @@
messageAreaControllerFactory,
bouncerMessageInteractor,
bouncerLogger,
- featureFlags);
+ featureFlagsClassic,
+ selectedUserInteractor);
collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
mLockscreenToDreamingTransition);
@@ -239,7 +243,7 @@
this::setExpandAnimationRunning);
mClock = clock;
- if (featureFlags.isEnabled(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION)) {
+ if (featureFlagsClassic.isEnabled(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION)) {
unfoldTransitionProgressProvider.ifPresent(
progressProvider -> progressProvider.addCallback(
mDisableSubpixelTextTransitionListener));
@@ -268,7 +272,7 @@
mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
mPulsingGestureListener);
- if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+ if (mFeatureFlagsClassic.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
mDreamingWakeupGestureHandler = new GestureDetector(mView.getContext(),
mLockscreenHostedDreamGestureListener);
}
@@ -440,7 +444,7 @@
}
boolean bouncerShowing;
- if (mFeatureFlags.isEnabled(Flags.ALTERNATE_BOUNCER_VIEW)) {
+ if (mFeatureFlagsClassic.isEnabled(Flags.ALTERNATE_BOUNCER_VIEW)) {
bouncerShowing = mPrimaryBouncerInteractor.isBouncerShowing()
|| mAlternateBouncerInteractor.isVisibleState();
} else {
@@ -452,7 +456,7 @@
if (mDragDownHelper.isDragDownEnabled()) {
// This handles drag down over lockscreen
boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
if (result) {
mLastInterceptWasDragDownHelper = true;
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -484,7 +488,7 @@
MotionEvent cancellation = MotionEvent.obtain(ev);
cancellation.setAction(MotionEvent.ACTION_CANCEL);
mStackScrollLayout.onInterceptTouchEvent(cancellation);
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
mNotificationPanelViewController.handleExternalInterceptTouch(cancellation);
}
cancellation.recycle();
@@ -499,7 +503,7 @@
if (mStatusBarKeyguardViewManager.onTouch(ev)) {
return true;
}
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) {
// we still want to finish our drag down gesture when locking the screen
handled |= mDragDownHelper.onTouchEvent(ev) || handled;
@@ -564,8 +568,27 @@
mDepthController.onPanelExpansionChanged(currentState);
}
+ /**
+ * Sets up the communal hub UI if the {@link com.android.systemui.Flags#FLAG_COMMUNAL_HUB} flag
+ * is enabled.
+ *
+ * The layout lives in {@link R.id.communal_ui_container}.
+ */
+ public void setupCommunalHubLayout() {
+ if (!mFeatureFlags.communalHub() || !ComposeFacade.INSTANCE.isComposeAvailable()) {
+ return;
+ }
+
+ // Replace the placeholder view with the communal UI.
+ View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
+ int index = mView.indexOfChild(communalPlaceholder);
+ mView.removeView(communalPlaceholder);
+ mView.addView(ComposeFacade.INSTANCE.createCommunalContainer(mView.getContext(),
+ mCommunalViewModel), index);
+ }
+
private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
// Since NotificationStackScrollLayout is now a sibling of notification_panel, we need
// to also ask NotificationPanelViewController directly, in order to process swipe up
// events originating from notifications
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 3bbb2cf..3c68438 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -736,7 +736,11 @@
/** Returns whether touches from the notification panel should be disallowed */
public boolean disallowTouches() {
- return mQs.disallowPanelTouches();
+ if (mQs != null) {
+ return mQs.disallowPanelTouches();
+ } else {
+ return false;
+ }
}
void setListening(boolean listening) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 25bd8e7..cb95b25 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -38,7 +38,6 @@
import com.android.app.animation.Interpolators
import com.android.settingslib.Utils
import com.android.systemui.Dumpable
-import com.android.systemui.res.R
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
@@ -49,6 +48,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.ChipVisibilityListener
import com.android.systemui.qs.HeaderPrivacyIconsController
+import com.android.systemui.res.R
import com.android.systemui.shade.ShadeHeaderController.Companion.HEADER_TRANSITION_ID
import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
@@ -304,7 +304,8 @@
iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)
iconManager.setTint(
- Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
+ Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary),
+ Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimaryInverse),
)
carrierIconSlots =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index e487a6f..2f68476 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -34,8 +34,7 @@
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
-import com.android.systemui.user.domain.interactor.UserInteractor
-import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
@@ -71,7 +70,7 @@
keyguardTransitionInteractor: KeyguardTransitionInteractor,
powerInteractor: PowerInteractor,
userSetupRepository: UserSetupRepository,
- userInteractor: UserInteractor,
+ userSwitcherInteractor: UserSwitcherInteractor,
sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
repository: ShadeRepository,
) {
@@ -140,13 +139,6 @@
/** Whether either the shade or QS is fully expanded. */
val isAnyFullyExpanded: Flow<Boolean> = anyExpansion.map { it >= 1f }.distinctUntilChanged()
- /** Whether either the shade or QS is expanding from a fully collapsed state. */
- val isAnyExpanding: Flow<Boolean> =
- anyExpansion
- .pairwise(1f)
- .map { (prev, curr) -> curr > 0f && curr < 1f && prev < 1f }
- .distinctUntilChanged()
-
/**
* Whether either the shade or QS is partially or fully expanded, i.e. not fully collapsed. At
* this time, this is not simply a matter of checking if either value in shadeExpansion and
@@ -227,7 +219,7 @@
isDeviceProvisioned &&
// Disallow QS during setup if it's a simple user switcher. (The user intends to
// use the lock screen user switcher, QS is not needed.)
- (isUserSetup || !userInteractor.isSimpleUserSwitcher) &&
+ (isUserSetup || !userSwitcherInteractor.isSimpleUserSwitcher) &&
isShadeEnabled &&
disableFlags.isQuickSettingsEnabled() &&
!isDozing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
deleted file mode 100644
index 17b4e3b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2019 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.statusbar
-
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Point
-import android.graphics.Rect
-import android.renderscript.Allocation
-import android.renderscript.Element
-import android.renderscript.RenderScript
-import android.renderscript.ScriptIntrinsicBlur
-import android.util.Log
-import android.util.MathUtils
-import com.android.internal.graphics.ColorUtils
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.MediaNotificationProcessor
-import javax.inject.Inject
-
-private const val TAG = "MediaArtworkProcessor"
-private const val COLOR_ALPHA = (255 * 0.7f).toInt()
-private const val BLUR_RADIUS = 25f
-private const val DOWNSAMPLE = 6
-
-@SysUISingleton
-class MediaArtworkProcessor @Inject constructor() {
-
- private val mTmpSize = Point()
- private var mArtworkCache: Bitmap? = null
-
- fun processArtwork(context: Context, artwork: Bitmap): Bitmap? {
- if (mArtworkCache != null) {
- return mArtworkCache
- }
- val renderScript = RenderScript.create(context)
- val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript))
- var input: Allocation? = null
- var output: Allocation? = null
- var inBitmap: Bitmap? = null
- try {
- @Suppress("DEPRECATION")
- context.display?.getSize(mTmpSize)
- val rect = Rect(0, 0, artwork.width, artwork.height)
- MathUtils.fitRect(rect, Math.max(mTmpSize.x / DOWNSAMPLE, mTmpSize.y / DOWNSAMPLE))
- inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(),
- true /* filter */)
- // Render script blurs only support ARGB_8888, we need a conversion if we got a
- // different bitmap config.
- if (inBitmap.config != Bitmap.Config.ARGB_8888) {
- val oldIn = inBitmap
- inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */)
- oldIn.recycle()
- }
- val outBitmap = Bitmap.createBitmap(inBitmap?.width ?: 0, inBitmap?.height ?: 0,
- Bitmap.Config.ARGB_8888)
-
- input = Allocation.createFromBitmap(renderScript, inBitmap,
- Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE)
- output = Allocation.createFromBitmap(renderScript, outBitmap)
-
- blur.setRadius(BLUR_RADIUS)
- blur.setInput(input)
- blur.forEach(output)
- output.copyTo(outBitmap)
-
- val swatch = MediaNotificationProcessor.findBackgroundSwatch(artwork)
-
- val canvas = Canvas(outBitmap)
- canvas.drawColor(ColorUtils.setAlphaComponent(swatch.rgb, COLOR_ALPHA))
- return outBitmap
- } catch (ex: IllegalArgumentException) {
- Log.e(TAG, "error while processing artwork", ex)
- return null
- } finally {
- input?.destroy()
- output?.destroy()
- blur.destroy()
- inBitmap?.recycle()
- }
- }
-
- fun clearCache() {
- mArtworkCache?.recycle()
- mArtworkCache = null
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 2147510..f32f1c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -16,9 +16,16 @@
package com.android.systemui.statusbar;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.os.UserHandle.USER_NULL;
+import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
import android.app.ActivityOptions;
import android.app.KeyguardManager;
import android.app.Notification;
@@ -30,8 +37,10 @@
import android.content.IntentSender;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
+import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -41,14 +50,18 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.recents.OverviewProxyService;
@@ -63,7 +76,9 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -84,6 +99,11 @@
private final SecureSettings mSecureSettings;
private final Object mLock = new Object();
+ private static final Uri SHOW_LOCKSCREEN =
+ Settings.Secure.getUriFor(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+ private static final Uri SHOW_PRIVATE_LOCKSCREEN =
+ Settings.Secure.getUriFor(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy;
private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy;
private final DevicePolicyManager mDevicePolicyManager;
@@ -91,6 +111,23 @@
private final SparseBooleanArray mUsersWithSeparateWorkChallenge = new SparseBooleanArray();
private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
+
+ // The variables between mUsersDpcAllowingNotifications and
+ // mUsersUsersAllowingPrivateNotifications (inclusive) are written on a background thread
+ // and read on the main thread. Because the pipeline needs these values, adding locks would
+ // introduce too much jank. This means that some pipeline runs could get incorrect values, that
+ // would be fixed on the next pipeline run. We think this will be rare since a pipeline run
+ // would have to overlap with a DPM sync or a user changing a value in Settings, and we run the
+ // pipeline frequently enough that it should be corrected by the next time it matters for the
+ // user.
+ private final SparseBooleanArray mUsersDpcAllowingNotifications = new SparseBooleanArray();
+ private final SparseBooleanArray mUsersUsersAllowingNotifications = new SparseBooleanArray();
+ private boolean mKeyguardAllowingNotifications = true;
+ private final SparseBooleanArray mUsersDpcAllowingPrivateNotifications
+ = new SparseBooleanArray();
+ private final SparseBooleanArray mUsersUsersAllowingPrivateNotifications
+ = new SparseBooleanArray();
+
private final SparseBooleanArray mUsersInLockdownLatestResult = new SparseBooleanArray();
private final SparseBooleanArray mShouldHideNotifsLatestResult = new SparseBooleanArray();
private final UserManager mUserManager;
@@ -99,24 +136,39 @@
private final BroadcastDispatcher mBroadcastDispatcher;
private final NotificationClickNotifier mClickNotifier;
private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy;
+ private final FeatureFlagsClassic mFeatureFlags;
private boolean mShowLockscreenNotifications;
private LockPatternUtils mLockPatternUtils;
protected KeyguardManager mKeyguardManager;
private int mState = StatusBarState.SHADE;
private final ListenerSet<NotificationStateChangedListener> mNotifStateChangedListeners =
new ListenerSet<>();
+ private final Collection<Uri> mLockScreenUris = new ArrayList<>();
+
protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
- if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) &&
- isCurrentProfile(getSendingUserId())) {
- mUsersAllowingPrivateNotifications.clear();
- updateLockscreenNotificationSetting();
- // TODO(b/231976036): Consolidate pipeline invalidations related to this event
- // notifyNotificationStateChanged();
+ if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) {
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ boolean changed = updateDpcSettings(getSendingUserId());
+ if (mCurrentUserId == getSendingUserId()) {
+ changed |= updateLockscreenNotificationSetting();
+ }
+ if (changed) {
+ notifyNotificationStateChanged();
+ }
+ } else {
+ if (isCurrentProfile(getSendingUserId())) {
+ mUsersAllowingPrivateNotifications.clear();
+ updateLockscreenNotificationSetting();
+ // TODO(b/231976036): Consolidate pipeline invalidations related to this
+ // event
+ // notifyNotificationStateChanged();
+ }
+ }
}
}
};
@@ -136,6 +188,14 @@
updateCurrentProfilesCache();
break;
case Intent.ACTION_USER_ADDED:
+ updateCurrentProfilesCache();
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+ mBackgroundHandler.post(() -> {
+ initValuesForUser(userId);
+ });
+ }
+ break;
case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
updateCurrentProfilesCache();
@@ -193,6 +253,8 @@
protected final Context mContext;
private final Handler mMainHandler;
+ private final Handler mBackgroundHandler;
+ private final Executor mBackgroundExecutor;
protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
protected final SparseArray<UserInfo> mCurrentManagedProfiles = new SparseArray<>();
@@ -214,13 +276,18 @@
KeyguardManager keyguardManager,
StatusBarStateController statusBarStateController,
@Main Handler mainHandler,
+ @Background Handler backgroundHandler,
+ @Background Executor backgroundExecutor,
DeviceProvisionedController deviceProvisionedController,
KeyguardStateController keyguardStateController,
SecureSettings secureSettings,
DumpManager dumpManager,
- LockPatternUtils lockPatternUtils) {
+ LockPatternUtils lockPatternUtils,
+ FeatureFlagsClassic featureFlags) {
mContext = context;
mMainHandler = mainHandler;
+ mBackgroundHandler = backgroundHandler;
+ mBackgroundExecutor = backgroundExecutor;
mDevicePolicyManager = devicePolicyManager;
mUserManager = userManager;
mUserTracker = userTracker;
@@ -236,6 +303,10 @@
mDeviceProvisionedController = deviceProvisionedController;
mSecureSettings = secureSettings;
mKeyguardStateController = keyguardStateController;
+ mFeatureFlags = featureFlags;
+
+ mLockScreenUris.add(SHOW_LOCKSCREEN);
+ mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN);
dumpManager.registerDumpable(this);
}
@@ -243,16 +314,53 @@
public void setUpWithPresenter(NotificationPresenter presenter) {
mPresenter = presenter;
- mLockscreenSettingsObserver = new ContentObserver(mMainHandler) {
+ mLockscreenSettingsObserver = new ContentObserver(
+ mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
+ ? mBackgroundHandler
+ : mMainHandler) {
+
@Override
- public void onChange(boolean selfChange) {
- // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
- // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
- mUsersAllowingPrivateNotifications.clear();
- mUsersAllowingNotifications.clear();
- // ... and refresh all the notifications
- updateLockscreenNotificationSetting();
- notifyNotificationStateChanged();
+ public void onChange(boolean selfChange, Collection<Uri> uris, int flags) {
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ @SuppressLint("MissingPermission")
+ List<UserInfo> users = mUserManager.getUsers();
+ for (int i = users.size() - 1; i >= 0; i--) {
+ onChange(selfChange, uris, flags,users.get(i).getUserHandle());
+ }
+ } else {
+ // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
+ // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
+ mUsersAllowingPrivateNotifications.clear();
+ mUsersAllowingNotifications.clear();
+ // ... and refresh all the notifications
+ updateLockscreenNotificationSetting();
+ notifyNotificationStateChanged();
+ }
+ }
+
+ // Note: even though this is an override, this method is not called by the OS
+ // since we're not in system_server. We are using it internally for cases when
+ // we have a single user id available (e.g. from USER_ADDED).
+ @Override
+ public void onChange(boolean selfChange, Collection<Uri> uris,
+ int flags, UserHandle user) {
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ boolean changed = false;
+ for (Uri uri: uris) {
+ if (SHOW_LOCKSCREEN.equals(uri)) {
+ changed |= updateUserShowSettings(user.getIdentifier());
+ } else if (SHOW_PRIVATE_LOCKSCREEN.equals(uri)) {
+ changed |= updateUserShowPrivateSettings(user.getIdentifier());
+ }
+ }
+
+ if (mCurrentUserId == user.getIdentifier()) {
+ changed |= updateLockscreenNotificationSetting();
+ }
+ if (changed) {
+ notifyNotificationStateChanged();
+ }
+ }
}
};
@@ -268,23 +376,26 @@
};
mContext.getContentResolver().registerContentObserver(
- mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
+ SHOW_LOCKSCREEN, false,
mLockscreenSettingsObserver,
UserHandle.USER_ALL);
mContext.getContentResolver().registerContentObserver(
- mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+ SHOW_PRIVATE_LOCKSCREEN,
true,
mLockscreenSettingsObserver,
UserHandle.USER_ALL);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
- mSettingsObserver);
+ if (!mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
+ mSettingsObserver);
+ }
mBroadcastDispatcher.registerReceiver(mAllUsersReceiver,
new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- null /* handler */, UserHandle.ALL);
+ mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
+ ? mBackgroundExecutor : null, UserHandle.ALL);
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_ADDED);
@@ -305,7 +416,23 @@
mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late
updateCurrentProfilesCache();
- mSettingsObserver.onChange(false); // set up
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ // Set up
+ mBackgroundHandler.post(() -> {
+ @SuppressLint("MissingPermission") List<UserInfo> users = mUserManager.getUsers();
+ for (int i = users.size() - 1; i >= 0; i--) {
+ initValuesForUser(users.get(i).id);
+ }
+ });
+ } else {
+ mSettingsObserver.onChange(false); // set up
+ }
+ }
+
+ private void initValuesForUser(@UserIdInt int userId) {
+ mLockscreenSettingsObserver.onChange(
+ false, mLockScreenUris, 0, UserHandle.of(userId));
+ updateDpcSettings(userId);
}
public boolean shouldShowLockscreenNotifications() {
@@ -322,17 +449,75 @@
mShowLockscreenNotifications = show;
}
- protected void updateLockscreenNotificationSetting() {
- final boolean show = mSecureSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
- 1,
- mCurrentUserId) != 0;
- final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
- null /* admin */, mCurrentUserId);
- final boolean allowedByDpm = (dpmFlags
- & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+ protected boolean updateLockscreenNotificationSetting() {
+ boolean show;
+ boolean allowedByDpm;
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ show = mUsersUsersAllowingNotifications.get(mCurrentUserId)
+ && mKeyguardAllowingNotifications;
+ // If DPC never notified us about a user, that means they have no policy for the user,
+ // and they allow the behavior
+ allowedByDpm = mUsersDpcAllowingNotifications.get(mCurrentUserId, true);
+ } else {
+ show = mSecureSettings.getIntForUser(
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 1,
+ mCurrentUserId) != 0;
+ final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
+ null /* admin */, mCurrentUserId);
+ allowedByDpm = (dpmFlags
+ & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+ }
+
+ final boolean oldValue = mShowLockscreenNotifications;
setShowLockscreenNotifications(show && allowedByDpm);
+
+ return oldValue != mShowLockscreenNotifications;
+ }
+
+ @WorkerThread
+ protected boolean updateDpcSettings(int userId) {
+ boolean originalAllowLockscreen = mUsersDpcAllowingNotifications.get(userId);
+ boolean originalAllowPrivate = mUsersDpcAllowingPrivateNotifications.get(userId);
+ final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
+ null /* admin */, userId);
+ final boolean allowedLockscreen = (dpmFlags & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+ final boolean allowedPrivate = (dpmFlags & KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0;
+ mUsersDpcAllowingNotifications.put(userId, allowedLockscreen);
+ mUsersDpcAllowingPrivateNotifications.put(userId, allowedPrivate);
+ return (originalAllowLockscreen != allowedLockscreen)
+ || (originalAllowPrivate != allowedPrivate);
+ }
+
+ @WorkerThread
+ private boolean updateUserShowSettings(int userId) {
+ boolean originalAllowLockscreen = mUsersUsersAllowingNotifications.get(userId);
+ boolean newAllowLockscreen = mSecureSettings.getIntForUser(
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 1,
+ userId) != 0;
+ mUsersUsersAllowingNotifications.put(userId, newAllowLockscreen);
+ boolean keyguardChanged = updateGlobalKeyguardSettings();
+ return (newAllowLockscreen != originalAllowLockscreen) || keyguardChanged;
+ }
+
+ @WorkerThread
+ private boolean updateUserShowPrivateSettings(int userId) {
+ boolean originalValue = mUsersUsersAllowingPrivateNotifications.get(userId);
+ boolean newValue = mSecureSettings.getIntForUser(
+ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ 0,
+ userId) != 0;
+ mUsersUsersAllowingPrivateNotifications.put(userId, newValue);
+ return (newValue != originalValue);
+ }
+
+ @WorkerThread
+ private boolean updateGlobalKeyguardSettings() {
+ final boolean oldValue = mKeyguardAllowingNotifications;
+ mKeyguardAllowingNotifications = mKeyguardManager.getPrivateNotificationsAllowed();
+ return oldValue != mKeyguardAllowingNotifications;
}
/**
@@ -340,21 +525,41 @@
* when the lockscreen is in "public" (secure & locked) mode?
*/
public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
- if (userHandle == UserHandle.USER_ALL) {
- return true;
- }
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ if (userHandle == UserHandle.USER_ALL) {
+ userHandle = mCurrentUserId;
+ }
+ if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+ // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+ // default value before moving to 'released'
+ Log.wtf(TAG, "Asking for redact notifs setting too early", new Throwable());
+ updateUserShowPrivateSettings(userHandle);
+ }
+ if (mUsersDpcAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+ // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+ // default value before moving to 'released'
+ Log.wtf(TAG, "Asking for redact notifs dpm override too early", new Throwable());
+ updateDpcSettings(userHandle);
+ }
+ return mUsersUsersAllowingPrivateNotifications.get(userHandle)
+ && mUsersDpcAllowingPrivateNotifications.get(userHandle);
+ } else {
+ if (userHandle == UserHandle.USER_ALL) {
+ return true;
+ }
- if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
- final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
- final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
- DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
- final boolean allowed = allowedByUser && allowedByDpm;
- mUsersAllowingPrivateNotifications.append(userHandle, allowed);
- return allowed;
- }
+ if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+ final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
+ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
+ final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
+ KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+ final boolean allowed = allowedByUser && allowedByDpm;
+ mUsersAllowingPrivateNotifications.append(userHandle, allowed);
+ return allowed;
+ }
- return mUsersAllowingPrivateNotifications.get(userHandle);
+ return mUsersAllowingPrivateNotifications.get(userHandle);
+ }
}
/**
@@ -406,21 +611,44 @@
* "public" (secure & locked) mode?
*/
public boolean userAllowsNotificationsInPublic(int userHandle) {
- if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
- return true;
- }
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ // Unlike 'show private', settings does not show a copy of this setting for each
+ // profile, so it inherits from the parent user.
+ if (userHandle == UserHandle.USER_ALL || mCurrentManagedProfiles.contains(userHandle)) {
+ userHandle = mCurrentUserId;
+ }
+ if (mUsersUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+ // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+ // default value before moving to 'released'
+ Log.wtf(TAG, "Asking for show notifs setting too early", new Throwable());
+ updateUserShowSettings(userHandle);
+ }
+ if (mUsersDpcAllowingNotifications.indexOfKey(userHandle) < 0) {
+ // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+ // default value before moving to 'released'
+ Log.wtf(TAG, "Asking for show notifs dpm override too early", new Throwable());
+ updateDpcSettings(userHandle);
+ }
+ return mUsersUsersAllowingNotifications.get(userHandle)
+ && mUsersDpcAllowingNotifications.get(userHandle)
+ && mKeyguardAllowingNotifications;
+ } else {
+ if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
+ return true;
+ }
- if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
- final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
- final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
- DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
- final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed();
- final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem;
- mUsersAllowingNotifications.append(userHandle, allowed);
- return allowed;
+ if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+ final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
+ LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
+ final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
+ KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+ final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed();
+ final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem;
+ mUsersAllowingNotifications.append(userHandle, allowed);
+ return allowed;
+ }
+ return mUsersAllowingNotifications.get(userHandle);
}
- return mUsersAllowingNotifications.get(userHandle);
}
/** @return true if the entry needs redaction when on the lockscreen. */
@@ -451,9 +679,15 @@
return true;
}
NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key);
- return entry != null
- && entry.getRanking().getLockscreenVisibilityOverride()
- == Notification.VISIBILITY_PRIVATE;
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ return entry != null
+ && entry.getRanking().getChannel().getLockscreenVisibility()
+ == Notification.VISIBILITY_PRIVATE;
+ } else {
+ return entry != null
+ && entry.getRanking().getLockscreenVisibilityOverride()
+ == Notification.VISIBILITY_PRIVATE;
+ }
}
private void updateCurrentProfilesCache() {
@@ -491,20 +725,6 @@
}
/**
- * If any managed/work profiles are in public mode.
- */
- public boolean isAnyManagedProfilePublicMode() {
- synchronized (mLock) {
- for (int i = mCurrentManagedProfiles.size() - 1; i >= 0; i--) {
- if (isLockscreenPublicMode(mCurrentManagedProfiles.valueAt(i).id)) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
* Returns the current user id. This can change if the user is switched.
*/
public int getCurrentUserId() {
@@ -581,8 +801,16 @@
}
private void notifyNotificationStateChanged() {
- for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
- listener.onNotificationStateChanged();
+ if (!Looper.getMainLooper().isCurrentThread()) {
+ mMainHandler.post(() -> {
+ for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
+ listener.onNotificationStateChanged();
+ }
+ });
+ } else {
+ for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
+ listener.onNotificationStateChanged();
+ }
}
}
@@ -620,5 +848,15 @@
pw.println(mUsersInLockdownLatestResult);
pw.print(" mShouldHideNotifsLatestResult=");
pw.println(mShouldHideNotifsLatestResult);
+ pw.print(" mUsersDpcAllowingNotifications=");
+ pw.println(mUsersDpcAllowingNotifications);
+ pw.print(" mUsersUsersAllowingNotifications=");
+ pw.println(mUsersUsersAllowingNotifications);
+ pw.print(" mKeyguardAllowingNotifications=");
+ pw.println(mKeyguardAllowingNotifications);
+ pw.print(" mUsersDpcAllowingPrivateNotifications=");
+ pw.println(mUsersDpcAllowingPrivateNotifications);
+ pw.print(" mUsersUsersAllowingPrivateNotifications=");
+ pw.println(mUsersUsersAllowingPrivateNotifications);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 5bd40b8..389486f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -15,43 +15,29 @@
*/
package com.android.systemui.statusbar;
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_MEDIA_FAKE_ARTWORK;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.ENABLE_LOCKSCREEN_WALLPAPER;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.SHOW_LOCKSCREEN_MEDIA_ARTWORK;
-
-import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.WallpaperManager;
import android.content.Context;
-import android.graphics.Bitmap;
import android.graphics.Point;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManager;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
-import android.os.AsyncTask;
import android.os.Trace;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.widget.ImageView;
-import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dumpable;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.models.player.MediaData;
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
@@ -65,18 +51,11 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.LockscreenWallpaper;
import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.ScrimState;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.Utils;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import dagger.Lazy;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -85,7 +64,6 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
-import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -99,7 +77,6 @@
private final StatusBarStateController mStatusBarStateController;
private final SysuiColorExtractor mColorExtractor;
private final KeyguardStateController mKeyguardStateController;
- private final KeyguardBypassController mKeyguardBypassController;
private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>();
static {
@@ -117,9 +94,6 @@
private final NotifCollection mNotifCollection;
@Nullable
- private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
-
- @Nullable
private BiometricUnlockController mBiometricUnlockController;
@Nullable
private ScrimController mScrimController;
@@ -128,12 +102,8 @@
@VisibleForTesting
boolean mIsLockscreenLiveWallpaperEnabled;
- private final DelayableExecutor mMainExecutor;
-
private final Context mContext;
private final ArrayList<MediaListener> mMediaListeners;
- private final MediaArtworkProcessor mMediaArtworkProcessor;
- private final Set<AsyncTask<?, ?, ?>> mProcessArtworkTasks = new ArraySet<>();
protected NotificationPresenter mPresenter;
private MediaController mMediaController;
@@ -150,8 +120,6 @@
private List<String> mSmallerInternalDisplayUids;
private Display mCurrentDisplay;
- private LockscreenWallpaper.WallpaperDrawable mWallapperDrawable;
-
private final MediaController.Callback mMediaListener = new MediaController.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -173,7 +141,6 @@
if (DEBUG_MEDIA) {
Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
}
- mMediaArtworkProcessor.clearCache();
mMediaMetadata = metadata;
dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */);
}
@@ -184,13 +151,9 @@
*/
public NotificationMediaManager(
Context context,
- Lazy<NotificationShadeWindowController> notificationShadeWindowController,
NotificationVisibilityProvider visibilityProvider,
- MediaArtworkProcessor mediaArtworkProcessor,
- KeyguardBypassController keyguardBypassController,
NotifPipeline notifPipeline,
NotifCollection notifCollection,
- @Main DelayableExecutor mainExecutor,
MediaDataManager mediaDataManager,
StatusBarStateController statusBarStateController,
SysuiColorExtractor colorExtractor,
@@ -199,12 +162,8 @@
WallpaperManager wallpaperManager,
DisplayManager displayManager) {
mContext = context;
- mMediaArtworkProcessor = mediaArtworkProcessor;
- mKeyguardBypassController = keyguardBypassController;
mMediaListeners = new ArrayList<>();
- mNotificationShadeWindowController = notificationShadeWindowController;
mVisibilityProvider = visibilityProvider;
- mMainExecutor = mainExecutor;
mMediaDataManager = mediaDataManager;
mNotifPipeline = notifPipeline;
mNotifCollection = notifCollection;
@@ -476,7 +435,6 @@
}
private void clearCurrentMediaNotificationSession() {
- mMediaArtworkProcessor.clearCache();
mMediaMetadata = null;
if (mMediaController != null) {
if (DEBUG_MEDIA) {
@@ -494,9 +452,6 @@
public void onDisplayUpdated(Display display) {
Trace.beginSection("NotificationMediaManager#onDisplayUpdated");
mCurrentDisplay = display;
- if (mWallapperDrawable != null) {
- mWallapperDrawable.onDisplayUpdated(isOnSmallerInternalDisplays());
- }
Trace.endSection();
}
@@ -531,18 +486,13 @@
}
/**
- * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
+ * Update media state of lockscreen media views and controllers .
*/
- public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
+ public void updateMediaMetaData(boolean metaDataChanged) {
if (mIsLockscreenLiveWallpaperEnabled) return;
Trace.beginSection("CentralSurfaces#updateMediaMetaData");
- if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) {
- Trace.endSection();
- return;
- }
-
if (getBackDropView() == null) {
Trace.endSection();
return; // called too early
@@ -566,170 +516,14 @@
+ " state=" + mStatusBarStateController.getState());
}
- Bitmap artworkBitmap = null;
- if (mediaMetadata != null && !mKeyguardBypassController.getBypassEnabled()) {
- artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
- if (artworkBitmap == null) {
- artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
- }
- }
-
- // Process artwork on a background thread and send the resulting bitmap to
- // finishUpdateMediaMetaData.
- if (metaDataChanged) {
- for (AsyncTask<?, ?, ?> task : mProcessArtworkTasks) {
- task.cancel(true);
- }
- mProcessArtworkTasks.clear();
- }
- if (artworkBitmap != null && !Utils.useQsMediaPlayer(mContext)) {
- mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged,
- allowEnterAnimation).execute(artworkBitmap));
- } else {
- finishUpdateMediaMetaData(metaDataChanged, allowEnterAnimation, null);
+ mColorExtractor.setHasMediaArtwork(false);
+ if (mScrimController != null) {
+ mScrimController.setHasBackdrop(false);
}
Trace.endSection();
}
- private void finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation,
- @Nullable Bitmap bmp) {
- Drawable artworkDrawable = null;
- if (bmp != null) {
- artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp);
- }
- boolean hasMediaArtwork = artworkDrawable != null;
- boolean allowWhenShade = false;
- if (ENABLE_LOCKSCREEN_WALLPAPER && artworkDrawable == null) {
- Bitmap lockWallpaper =
- mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null;
- if (lockWallpaper != null) {
- artworkDrawable = new LockscreenWallpaper.WallpaperDrawable(
- mBackdropBack.getResources(), lockWallpaper, isOnSmallerInternalDisplays());
- // We're in the SHADE mode on the SIM screen - yet we still need to show
- // the lockscreen wallpaper in that mode.
- allowWhenShade = mStatusBarStateController.getState() == KEYGUARD;
- }
- }
-
- NotificationShadeWindowController windowController =
- mNotificationShadeWindowController.get();
- boolean hideBecauseOccluded = mKeyguardStateController.isOccluded();
-
- final boolean hasArtwork = artworkDrawable != null;
- mColorExtractor.setHasMediaArtwork(hasMediaArtwork);
- if (mScrimController != null) {
- mScrimController.setHasBackdrop(hasArtwork);
- }
-
- if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
- && (mStatusBarStateController.getState() != StatusBarState.SHADE || allowWhenShade)
- && mBiometricUnlockController != null && mBiometricUnlockController.getMode()
- != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
- && !hideBecauseOccluded) {
- // time to show some art!
- if (mBackdrop.getVisibility() != View.VISIBLE) {
- mBackdrop.setVisibility(View.VISIBLE);
- if (allowEnterAnimation) {
- mBackdrop.setAlpha(0);
- mBackdrop.animate().alpha(1f);
- } else {
- mBackdrop.animate().cancel();
- mBackdrop.setAlpha(1f);
- }
- if (windowController != null) {
- windowController.setBackdropShowing(true);
- }
- metaDataChanged = true;
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork");
- }
- }
- if (metaDataChanged) {
- if (mBackdropBack.getDrawable() != null) {
- Drawable drawable =
- mBackdropBack.getDrawable().getConstantState()
- .newDrawable(mBackdropFront.getResources()).mutate();
- mBackdropFront.setImageDrawable(drawable);
- mBackdropFront.setAlpha(1f);
- mBackdropFront.setVisibility(View.VISIBLE);
- } else {
- mBackdropFront.setVisibility(View.INVISIBLE);
- }
-
- if (DEBUG_MEDIA_FAKE_ARTWORK) {
- final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF);
- Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c));
- mBackdropBack.setBackgroundColor(0xFFFFFFFF);
- mBackdropBack.setImageDrawable(new ColorDrawable(c));
- } else {
- if (artworkDrawable instanceof LockscreenWallpaper.WallpaperDrawable) {
- mWallapperDrawable =
- (LockscreenWallpaper.WallpaperDrawable) artworkDrawable;
- }
- mBackdropBack.setImageDrawable(artworkDrawable);
- }
-
- if (mBackdropFront.getVisibility() == View.VISIBLE) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from "
- + mBackdropFront.getDrawable()
- + " to "
- + mBackdropBack.getDrawable());
- }
- mBackdropFront.animate()
- .setDuration(250)
- .alpha(0f).withEndAction(mHideBackdropFront);
- }
- }
- } else {
- // need to hide the album art, either because we are unlocked, on AOD
- // or because the metadata isn't there to support it
- if (mBackdrop.getVisibility() != View.GONE) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork");
- }
- boolean cannotAnimateDoze = mStatusBarStateController.isDozing()
- && !ScrimState.AOD.getAnimateChange();
- if (((mBiometricUnlockController != null && mBiometricUnlockController.getMode()
- == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
- || cannotAnimateDoze))
- || hideBecauseOccluded) {
- // We are unlocking directly - no animation!
- mBackdrop.setVisibility(View.GONE);
- mBackdropBack.setImageDrawable(null);
- if (windowController != null) {
- windowController.setBackdropShowing(false);
- }
- } else {
- if (windowController != null) {
- windowController.setBackdropShowing(false);
- }
- mBackdrop.animate()
- .alpha(0)
- .setInterpolator(Interpolators.ACCELERATE_DECELERATE)
- .setDuration(300)
- .setStartDelay(0)
- .withEndAction(() -> {
- mBackdrop.setVisibility(View.GONE);
- mBackdropFront.animate().cancel();
- mBackdropBack.setImageDrawable(null);
- mMainExecutor.execute(mHideBackdropFront);
- });
- if (mKeyguardStateController.isKeyguardFadingAway()) {
- mBackdrop.animate()
- .setDuration(
- mKeyguardStateController.getShortenedFadingAwayDuration())
- .setStartDelay(
- mKeyguardStateController.getKeyguardFadingAwayDelay())
- .setInterpolator(Interpolators.LINEAR)
- .start();
- }
- }
- }
- }
- }
-
public void setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack,
ScrimController scrimController, LockscreenWallpaper lockscreenWallpaper) {
mBackdrop = backdrop;
@@ -758,15 +552,6 @@
}
};
- private Bitmap processArtwork(Bitmap artwork) {
- return mMediaArtworkProcessor.processArtwork(mContext, artwork);
- }
-
- @MainThread
- private void removeTask(AsyncTask<?, ?, ?> task) {
- mProcessArtworkTasks.remove(task);
- }
-
// TODO(b/273443374): remove
public boolean isLockscreenWallpaperOnNotificationShade() {
return mBackdrop != null && mLockscreenWallpaper != null
@@ -780,52 +565,6 @@
return mBackdrop;
}
- /**
- * {@link AsyncTask} to prepare album art for use as backdrop on lock screen.
- */
- private static final class ProcessArtworkTask extends AsyncTask<Bitmap, Void, Bitmap> {
-
- private final WeakReference<NotificationMediaManager> mManagerRef;
- private final boolean mMetaDataChanged;
- private final boolean mAllowEnterAnimation;
-
- ProcessArtworkTask(NotificationMediaManager manager, boolean changed,
- boolean allowAnimation) {
- mManagerRef = new WeakReference<>(manager);
- mMetaDataChanged = changed;
- mAllowEnterAnimation = allowAnimation;
- }
-
- @Override
- protected Bitmap doInBackground(Bitmap... bitmaps) {
- NotificationMediaManager manager = mManagerRef.get();
- if (manager == null || bitmaps.length == 0 || isCancelled()) {
- return null;
- }
- return manager.processArtwork(bitmaps[0]);
- }
-
- @Override
- protected void onPostExecute(@Nullable Bitmap result) {
- NotificationMediaManager manager = mManagerRef.get();
- if (manager != null && !isCancelled()) {
- manager.removeTask(this);
- manager.finishUpdateMediaMetaData(mMetaDataChanged, mAllowEnterAnimation, result);
- }
- }
-
- @Override
- protected void onCancelled(Bitmap result) {
- if (result != null) {
- result.recycle();
- }
- NotificationMediaManager manager = mManagerRef.get();
- if (manager != null) {
- manager.removeTask(this);
- }
- }
- }
-
public interface MediaListener {
/**
* Called whenever there's new metadata or playback state.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index d4b6dfb..61ebcc0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui.statusbar;
+
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.KeyguardManager;
@@ -48,10 +49,10 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
@@ -535,7 +536,8 @@
public void cleanUpRemoteInputForUserRemoval(NotificationEntry entry) {
if (isRemoteInputActive(entry)) {
entry.mRemoteEditImeVisible = false;
- mRemoteInputController.removeRemoteInput(entry, null);
+ mRemoteInputController.removeRemoteInput(entry, null,
+ /* reason= */"RemoteInputManager#cleanUpRemoteInputForUserRemoval");
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index f8c049e..23b697e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -133,7 +133,7 @@
public void bind(AmbientState ambientState,
NotificationStackScrollLayoutController hostLayoutController) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mAmbientState = ambientState;
mHostLayoutController = hostLayoutController;
hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
@@ -143,7 +143,7 @@
public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout,
NotificationRoundnessManager roundnessManager) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
mAmbientState = ambientState;
mHostLayout = hostLayout;
mRoundnessManager = roundnessManager;
@@ -964,7 +964,7 @@
@Override
public void onStateChanged(int newState) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mStatusBarState = newState;
updateInteractiveness();
}
@@ -1022,17 +1022,17 @@
}
public void setController(NotificationShelfController notificationShelfController) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mController = notificationShelfController;
}
public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
mCanModifyColorOfNotifications = canModifyColorOfNotifications;
}
public void setCanInteract(boolean canInteract) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
mCanInteract = canInteract;
updateInteractiveness();
}
@@ -1050,7 +1050,7 @@
}
public void requestRoundnessResetFor(ExpandableView child) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
child.requestRoundnessReset(SHELF_SCROLL);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index d5e4902..ffde8c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -31,6 +31,8 @@
import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.util.DumpUtilsKt;
+import com.google.errorprone.annotations.CompileTimeConstant;
+
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Objects;
@@ -69,7 +71,8 @@
* @param entry the entry for which a remote input is now active.
* @param token a token identifying the view that is managing the remote input
*/
- public void addRemoteInput(NotificationEntry entry, Object token) {
+ public void addRemoteInput(NotificationEntry entry, Object token,
+ @CompileTimeConstant String reason) {
Objects.requireNonNull(entry);
Objects.requireNonNull(token);
boolean isActive = isRemoteInputActive(entry);
@@ -77,7 +80,9 @@
entry /* contains */, null /* remove */, token /* removeToken */);
mLogger.logAddRemoteInput(entry.getKey()/* entryKey */,
isActive /* isRemoteInputAlreadyActive */,
- found /* isRemoteInputFound */);
+ found /* isRemoteInputFound */,
+ reason /* reason */,
+ entry.getNotificationStyle()/* notificationStyle */);
if (!found) {
mOpen.add(new Pair<>(new WeakReference<>(entry), token));
}
@@ -96,7 +101,8 @@
* the entry is only removed if the token matches the last added token for this
* entry. If null, the entry is removed regardless.
*/
- public void removeRemoteInput(NotificationEntry entry, Object token) {
+ public void removeRemoteInput(NotificationEntry entry, Object token,
+ @CompileTimeConstant String reason) {
Objects.requireNonNull(entry);
if (entry.mRemoteEditImeVisible && entry.mRemoteEditImeAnimatingAway) {
mLogger.logRemoveRemoteInput(
@@ -104,19 +110,35 @@
true /* remoteEditImeVisible */,
true /* remoteEditImeAnimatingAway */,
isRemoteInputActive(entry) /* isRemoteInputActiveForEntry */,
- isRemoteInputActive() /* isRemoteInputActive */);
+ isRemoteInputActive() /* isRemoteInputActive */,
+ reason /* reason */,
+ entry.getNotificationStyle()/* notificationStyle */);
return;
}
+
// If the view is being removed, this may be called even though we're not active
boolean remoteInputActiveForEntry = isRemoteInputActive(entry);
+ boolean remoteInputActive = isRemoteInputActive();
mLogger.logRemoveRemoteInput(
entry.getKey() /* entryKey */,
entry.mRemoteEditImeVisible /* remoteEditImeVisible */,
entry.mRemoteEditImeAnimatingAway /* remoteEditImeAnimatingAway */,
remoteInputActiveForEntry /* isRemoteInputActiveForEntry */,
- isRemoteInputActive()/* isRemoteInputActive */);
+ remoteInputActive/* isRemoteInputActive */,
+ reason/* reason */,
+ entry.getNotificationStyle()/* notificationStyle */);
- if (!remoteInputActiveForEntry) return;
+ if (!remoteInputActiveForEntry) {
+ if (mLastAppliedRemoteInputActive != null
+ && mLastAppliedRemoteInputActive
+ && !remoteInputActive) {
+ mLogger.logRemoteInputApplySkipped(
+ entry.getKey() /* entryKey */,
+ reason/* reason */,
+ entry.getNotificationStyle()/* notificationStyle */);
+ }
+ return;
+ }
pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */, token);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
index 1196211..595ab70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
@@ -21,6 +21,21 @@
public interface StatusIconDisplayable extends DarkReceiver {
String getSlot();
void setStaticDrawableColor(int color);
+
+ /**
+ * For a layer drawable, or one that has a background, {@code tintColor} should be used as the
+ * background tint for the container, while {@code contrastColor} can be used as the foreground
+ * drawable's tint so that it is visible on the background. Essentially, tintColor should apply
+ * to the portion of the icon that borders the underlying window content (status bar's
+ * background), and the contrastColor only need be used to distinguish from the tintColor.
+ *
+ * Defaults to calling {@link #setStaticDrawableColor(int)} with only the tint color, so modern
+ * callers can just call this method and still get the default behavior.
+ */
+ default void setStaticDrawableColor(int tintColor, int contrastColor) {
+ setStaticDrawableColor(tintColor);
+ }
+
void setDecorColor(int color);
/** Sets the visible state that this displayable should be. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 7f5829d..125c8efe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -31,7 +31,6 @@
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
@@ -45,12 +44,10 @@
import com.android.systemui.shade.carrier.ShadeCarrierGroupController;
import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.MediaArtworkProcessor;
import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -61,7 +58,6 @@
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -71,7 +67,6 @@
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
import dagger.Binds;
import dagger.Lazy;
@@ -122,13 +117,9 @@
@Provides
static NotificationMediaManager provideNotificationMediaManager(
Context context,
- Lazy<NotificationShadeWindowController> notificationShadeWindowController,
NotificationVisibilityProvider visibilityProvider,
- MediaArtworkProcessor mediaArtworkProcessor,
- KeyguardBypassController keyguardBypassController,
NotifPipeline notifPipeline,
NotifCollection notifCollection,
- @Main DelayableExecutor mainExecutor,
MediaDataManager mediaDataManager,
StatusBarStateController statusBarStateController,
SysuiColorExtractor colorExtractor,
@@ -138,13 +129,9 @@
DisplayManager displayManager) {
return new NotificationMediaManager(
context,
- notificationShadeWindowController,
visibilityProvider,
- mediaArtworkProcessor,
- keyguardBypassController,
notifPipeline,
notifCollection,
- mainExecutor,
mediaDataManager,
statusBarStateController,
colorExtractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
deleted file mode 100644
index 732c115..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2017 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.statusbar.notification;
-
-import android.graphics.Bitmap;
-import android.graphics.Color;
-
-import androidx.palette.graphics.Palette;
-
-import java.util.List;
-
-/**
- * A gutted class that now contains only a color extraction utility used by the
- * MediaArtworkProcessor, which has otherwise supplanted this.
- *
- * TODO(b/182926117): move this into MediaArtworkProcessor.kt
- */
-public class MediaNotificationProcessor {
-
- /**
- * The population fraction to select a white or black color as the background over a color.
- */
- private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f;
- private static final float BLACK_MAX_LIGHTNESS = 0.08f;
- private static final float WHITE_MIN_LIGHTNESS = 0.90f;
- private static final int RESIZE_BITMAP_AREA = 150 * 150;
-
- private MediaNotificationProcessor() {
- }
-
- /**
- * Finds an appropriate background swatch from media artwork.
- *
- * @param artwork Media artwork
- * @return Swatch that should be used as the background of the media notification.
- */
- public static Palette.Swatch findBackgroundSwatch(Bitmap artwork) {
- return findBackgroundSwatch(generateArtworkPaletteBuilder(artwork).generate());
- }
-
- /**
- * Finds an appropriate background swatch from the palette of media artwork.
- *
- * @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder}
- * @return Swatch that should be used as the background of the media notification.
- */
- public static Palette.Swatch findBackgroundSwatch(Palette palette) {
- // by default we use the dominant palette
- Palette.Swatch dominantSwatch = palette.getDominantSwatch();
- if (dominantSwatch == null) {
- return new Palette.Swatch(Color.WHITE, 100);
- }
-
- if (!isWhiteOrBlack(dominantSwatch.getHsl())) {
- return dominantSwatch;
- }
- // Oh well, we selected black or white. Lets look at the second color!
- List<Palette.Swatch> swatches = palette.getSwatches();
- float highestNonWhitePopulation = -1;
- Palette.Swatch second = null;
- for (Palette.Swatch swatch : swatches) {
- if (swatch != dominantSwatch
- && swatch.getPopulation() > highestNonWhitePopulation
- && !isWhiteOrBlack(swatch.getHsl())) {
- second = swatch;
- highestNonWhitePopulation = swatch.getPopulation();
- }
- }
- if (second == null) {
- return dominantSwatch;
- }
- if (dominantSwatch.getPopulation() / highestNonWhitePopulation
- > POPULATION_FRACTION_FOR_WHITE_OR_BLACK) {
- // The dominant swatch is very dominant, lets take it!
- // We're not filtering on white or black
- return dominantSwatch;
- } else {
- return second;
- }
- }
-
- /**
- * Generate a palette builder for media artwork.
- *
- * For producing a smooth background transition, the palette is extracted from only the left
- * side of the artwork.
- *
- * @param artwork Media artwork
- * @return Builder that generates the {@link Palette} for the media artwork.
- */
- public static Palette.Builder generateArtworkPaletteBuilder(Bitmap artwork) {
- // for the background we only take the left side of the image to ensure
- // a smooth transition
- return Palette.from(artwork)
- .setRegion(0, 0, artwork.getWidth() / 2, artwork.getHeight())
- .clearFilters() // we want all colors, red / white / black ones too!
- .resizeBitmapArea(RESIZE_BITMAP_AREA);
- }
-
- private static boolean isWhiteOrBlack(float[] hsl) {
- return isBlack(hsl) || isWhite(hsl);
- }
-
- /**
- * @return true if the color represents a color which is close to black.
- */
- private static boolean isBlack(float[] hslColor) {
- return hslColor[2] <= BLACK_MAX_LIGHTNESS;
- }
-
- /**
- * @return true if the color represents a color which is close to white.
- */
- private static boolean isWhite(float[] hslColor) {
- return hslColor[2] >= WHITE_MIN_LIGHTNESS;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 57d20246..8957f29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -114,18 +114,21 @@
|| previousAnimator.getAnimatedFraction() == 0)) {
animator.setStartDelay(properties.delay);
}
- if (listener != null) {
- animator.addListener(listener);
- }
// remove the tag when the animation is finished
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- view.setTag(animatorTag, null);
- view.setTag(animationStartTag, null);
- view.setTag(animationEndTag, null);
+ Animator existing = (Animator) view.getTag(animatorTag);
+ if (existing == animation) {
+ view.setTag(animatorTag, null);
+ view.setTag(animationStartTag, null);
+ view.setTag(animationEndTag, null);
+ }
}
});
+ if (listener != null) {
+ animator.addListener(listener);
+ }
ViewState.startAnimator(animator, listener);
view.setTag(animatorTag, animator);
view.setTag(animationStartTag, currentValue);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
index 39b999c..23f87ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
@@ -32,17 +32,24 @@
fun logAddRemoteInput(
entryKey: String,
isRemoteInputAlreadyActive: Boolean,
- isRemoteInputFound: Boolean
+ isRemoteInputFound: Boolean,
+ reason: String,
+ notificationStyle: String
) =
logBuffer.log(
TAG,
DEBUG,
{
str1 = entryKey
+ str2 = reason
+ str3 = notificationStyle
bool1 = isRemoteInputAlreadyActive
bool2 = isRemoteInputFound
},
- { "addRemoteInput entry: $str1, isAlreadyActive: $bool1, isFound:$bool2" }
+ {
+ "addRemoteInput reason:$str2 entry: $str1, style:$str3" +
+ ", isAlreadyActive: $bool1, isFound:$bool2"
+ }
)
/** logs removeRemoteInput invocation of [RemoteInputController] */
@@ -52,25 +59,45 @@
remoteEditImeVisible: Boolean,
remoteEditImeAnimatingAway: Boolean,
isRemoteInputActiveForEntry: Boolean,
- isRemoteInputActive: Boolean
+ isRemoteInputActive: Boolean,
+ reason: String,
+ notificationStyle: String
) =
logBuffer.log(
TAG,
DEBUG,
{
str1 = entryKey
+ str2 = reason
+ str3 = notificationStyle
bool1 = remoteEditImeVisible
bool2 = remoteEditImeAnimatingAway
bool3 = isRemoteInputActiveForEntry
bool4 = isRemoteInputActive
},
{
- "removeRemoteInput entry: $str1, remoteEditImeVisible: $bool1" +
+ "removeRemoteInput reason: $str2 entry: $str1" +
+ ", style: $str3, remoteEditImeVisible: $bool1" +
", remoteEditImeAnimatingAway: $bool2, isRemoteInputActiveForEntry: $bool3" +
", isRemoteInputActive: $bool4"
}
)
+ fun logRemoteInputApplySkipped(entryKey: String, reason: String, notificationStyle: String) =
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = entryKey
+ str2 = reason
+ str3 = notificationStyle
+ },
+ {
+ "removeRemoteInput[apply is skipped] reason: $str2" +
+ "for entry: $str1, style: $str3 "
+ }
+ )
+
private companion object {
private const val TAG = "RemoteInputControllerLog"
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 54bb2140..cfe9fbe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -983,6 +983,19 @@
return mExpandAnimationRunning;
}
+ /**
+ * @return NotificationStyle
+ */
+ public String getNotificationStyle() {
+ if (isSummaryWithChildren()) {
+ return "summary";
+ }
+
+ final Class<? extends Notification.Style> style =
+ getSbn().getNotification().getNotificationStyle();
+ return style == null ? "nostyle" : style.getSimpleName();
+ }
+
/** Information about a suggestion that is being edited. */
public static class EditedSuggestionInfo {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index 9ba1f7a..380cdad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -30,6 +30,7 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import dagger.Binds
import dagger.Module
import javax.inject.Inject
@@ -52,7 +53,8 @@
private val lockscreenUserManager: NotificationLockscreenUserManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val statusBarStateController: StatusBarStateController,
- private val keyguardStateController: KeyguardStateController
+ private val keyguardStateController: KeyguardStateController,
+ private val selectedUserInteractor: SelectedUserInteractor,
) : Invalidator("SensitiveContentInvalidator"),
SensitiveContentCoordinator,
DynamicPrivacyController.Listener,
@@ -67,10 +69,10 @@
override fun onDynamicPrivacyChanged(): Unit = invalidateList("onDynamicPrivacyChanged")
override fun onBeforeRenderList(entries: List<ListEntry>) {
- if (keyguardStateController.isKeyguardGoingAway() ||
- statusBarStateController.getState() == StatusBarState.KEYGUARD &&
+ if (keyguardStateController.isKeyguardGoingAway ||
+ statusBarStateController.state == StatusBarState.KEYGUARD &&
keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
- KeyguardUpdateMonitor.getCurrentUser())) {
+ selectedUserInteractor.getSelectedUserId())) {
// don't update yet if:
// - the keyguard is currently going away
// - LS is about to be dismissed by a biometric that bypasses LS (avoid notif flash)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
index 50efbb5..eb5c1fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
@@ -167,17 +167,16 @@
NotificationShelfViewBinderWrapperControllerImpl.unsupported
override fun setShelfIcons(icons: NotificationIconContainer) {
- if (shelfRefactor.expectEnabled()) {
- NotificationIconContainerViewBinder.bind(
- icons,
- shelfIconsViewModel,
- configurationController,
- dozeParameters,
- featureFlags,
- screenOffAnimationController,
- )
- shelfIcons = icons
- }
+ if (shelfRefactor.isUnexpectedlyInLegacyMode()) return
+ NotificationIconContainerViewBinder.bind(
+ icons,
+ shelfIconsViewModel,
+ configurationController,
+ dozeParameters,
+ featureFlags,
+ screenOffAnimationController,
+ )
+ shelfIcons = icons
}
override fun onDensityOrFontScaleChanged(context: Context) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index b2c32cd..db7f46e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -1,6 +1,5 @@
package com.android.systemui.statusbar.notification.interruption
-import android.app.Notification
import android.app.Notification.VISIBILITY_SECRET
import android.content.Context
import android.database.ContentObserver
@@ -14,6 +13,8 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -78,7 +79,8 @@
private val statusBarStateController: SysuiStatusBarStateController,
private val userTracker: UserTracker,
private val secureSettings: SecureSettings,
- private val globalSettings: GlobalSettings
+ private val globalSettings: GlobalSettings,
+ private val featureFlags: FeatureFlagsClassic
) : CoreStartable, KeyguardNotificationVisibilityProvider {
private val showSilentNotifsUri =
secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)
@@ -201,7 +203,7 @@
// device isn't public, no need to check public-related settings, so allow
!lockscreenUserManager.isLockscreenPublicMode(user) -> false
// entry is meant to be secret on the lockscreen, disallow
- entry.ranking.lockscreenVisibilityOverride == Notification.VISIBILITY_SECRET -> true
+ isRankingVisibilitySecret(entry) -> true
// disallow if user disallows notifications in public
else -> !lockscreenUserManager.userAllowsNotificationsInPublic(user)
}
@@ -215,6 +217,17 @@
}
}
+ private fun isRankingVisibilitySecret(entry: NotificationEntry): Boolean {
+ return if (featureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting
+ // info, and NotificationLockscreenUserManagerImpl is already listening for updates
+ // to those
+ entry.ranking.channel.lockscreenVisibility == VISIBILITY_SECRET
+ } else {
+ entry.ranking.lockscreenVisibilityOverride == VISIBILITY_SECRET
+ }
+ }
+
override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run {
println("isLockedOrLocking=$isLockedOrLocking")
withIncreasedIndent {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9340b85..11c65e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1901,16 +1901,7 @@
return traceTag;
}
- if (isSummaryWithChildren()) {
- return traceTag + "(summary)";
- }
- Class<? extends Notification.Style> style =
- getEntry().getSbn().getNotification().getNotificationStyle();
- if (style == null) {
- return traceTag + "(nostyle)";
- } else {
- return traceTag + "(" + style.getSimpleName() + ")";
- }
+ return traceTag + "(" + getEntry().getNotificationStyle() + ")";
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index a27a305..60e75ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1363,12 +1363,12 @@
result.mController.setPendingIntent(existingPendingIntent);
}
if (result.mController.updatePendingIntentFromActions(actions)) {
- if (!result.mView.isActive()) {
- result.mView.focus();
+ if (!result.mController.isActive()) {
+ result.mController.focus();
}
} else {
- if (result.mView.isActive()) {
- result.mView.close();
+ if (result.mController.isActive()) {
+ result.mController.close();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 79f8f22..dba93d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -2735,7 +2735,7 @@
* @param listener callback for notification removed
*/
public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mOnNotificationRemovedListener = listener;
}
@@ -4982,12 +4982,12 @@
@Nullable
public ExpandableView getShelf() {
- if (!mShelfRefactor.expectEnabled()) return null;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return null;
return mShelf;
}
public void setShelf(NotificationShelf shelf) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
int index = -1;
if (mShelf != null) {
index = indexOfChild(mShelf);
@@ -5001,7 +5001,7 @@
}
public void setShelfController(NotificationShelfController notificationShelfController) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
int index = -1;
if (mShelf != null) {
index = indexOfChild(mShelf);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 5020780..6a70815 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1431,7 +1431,7 @@
}
public void setShelfController(NotificationShelfController notificationShelfController) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mView.setShelfController(notificationShelfController);
}
@@ -1644,12 +1644,12 @@
}
public void setShelf(NotificationShelf shelf) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
mView.setShelf(shelf);
}
public int getShelfHeight() {
- if (!mShelfRefactor.expectEnabled()) {
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) {
return 0;
}
ExpandableView shelf = mView.getShelf();
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 f750fed..1229cb9 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
@@ -31,6 +31,7 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
/** View-model for the shared notification container, used by both the shade and keyguard spaces */
class SharedNotificationContainerViewModel
@@ -45,8 +46,8 @@
) {
private val statesForConstrainedNotifications =
setOf(
- KeyguardState.LOCKSCREEN,
KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN,
KeyguardState.DOZING,
KeyguardState.ALTERNATE_BOUNCER,
KeyguardState.PRIMARY_BOUNCER
@@ -68,8 +69,17 @@
/** If the user is visually on one of the unoccluded lockscreen states. */
val isOnLockscreen: Flow<Boolean> =
- keyguardTransitionInteractor.finishedKeyguardState
- .map { statesForConstrainedNotifications.contains(it) }
+ combine(
+ keyguardTransitionInteractor.finishedKeyguardState.map {
+ statesForConstrainedNotifications.contains(it)
+ },
+ keyguardTransitionInteractor
+ .transitionValue(KeyguardState.LOCKSCREEN)
+ .onStart { emit(0f) }
+ .map { it > 0 }
+ ) { constrainedNotificationState, transitioningToOrFromLockscreen ->
+ constrainedNotificationState || transitioningToOrFromLockscreen
+ }
.distinctUntilChanged()
/** Are we purely on the keyguard without the shade/qs? */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 2809cad..59f10ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -17,8 +17,6 @@
package com.android.systemui.statusbar.phone;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
-import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
-import static com.android.systemui.keyguard.WakefulnessLifecycle.UNKNOWN_LAST_WAKE_TIME;
import android.annotation.IntDef;
import android.content.res.Resources;
@@ -30,7 +28,6 @@
import android.os.Handler;
import android.os.PowerManager;
import android.os.Trace;
-import android.view.HapticFeedbackConstants;
import androidx.annotation.Nullable;
@@ -47,22 +44,27 @@
import com.android.keyguard.KeyguardViewController;
import com.android.keyguard.logging.BiometricUnlockLogger;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.time.SystemClock;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -73,12 +75,14 @@
import javax.inject.Inject;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
/**
* Controller which coordinates all the biometric unlocking actions with the UI.
*/
+@ExperimentalCoroutinesApi
@SysUISingleton
public class BiometricUnlockController extends KeyguardUpdateMonitorCallback implements Dumpable {
- private static final long RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS = 400L;
private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
@@ -172,9 +176,12 @@
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final LatencyTracker mLatencyTracker;
private final VibratorHelper mVibratorHelper;
+ private final BiometricUnlockInteractor mBiometricUnlockInteractor;
private final BiometricUnlockLogger mLogger;
private final SystemClock mSystemClock;
private final boolean mOrderUnlockAndWake;
+ private final Lazy<SelectedUserInteractor> mSelectedUserInteractor;
+ private final DeviceEntryHapticsInteractor mHapticsInteractor;
private long mLastFpFailureUptimeMillis;
private int mNumConsecutiveFpFailures;
@@ -284,7 +291,10 @@
ScreenOffAnimationController screenOffAnimationController,
VibratorHelper vibrator,
SystemClock systemClock,
- FeatureFlags featureFlags
+ FeatureFlags featureFlags,
+ DeviceEntryHapticsInteractor hapticsInteractor,
+ Lazy<SelectedUserInteractor> selectedUserInteractor,
+ BiometricUnlockInteractor biometricUnlockInteractor
) {
mPowerManager = powerManager;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -293,6 +303,7 @@
mLatencyTracker = latencyTracker;
mWakefulnessLifecycle = wakefulnessLifecycle;
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+ mBiometricUnlockInteractor = biometricUnlockInteractor;
mNotificationShadeWindowController = notificationShadeWindowController;
mDozeScrimController = dozeScrimController;
@@ -314,6 +325,8 @@
mFeatureFlags = featureFlags;
mOrderUnlockAndWake = resources.getBoolean(
com.android.internal.R.bool.config_orderUnlockAndWake);
+ mHapticsInteractor = hapticsInteractor;
+ mSelectedUserInteractor = selectedUserInteractor;
dumpManager.registerDumpable(this);
}
@@ -434,7 +447,7 @@
if (mode == MODE_WAKE_AND_UNLOCK
|| mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING
|| mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) {
- vibrateSuccess(biometricSourceType);
+ mHapticsInteractor.vibrateSuccess();
onBiometricUnlockedWithKeyguardDismissal(biometricSourceType);
}
startWakeAndUnlock(mode);
@@ -498,8 +511,7 @@
case MODE_WAKE_AND_UNLOCK:
if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) {
Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING");
- mMediaManager.updateMediaMetaData(false /* metaDataChanged */,
- true /* allowEnterAnimation */);
+ mMediaManager.updateMediaMetaData(false /* metaDataChanged */);
} else if (mMode == MODE_WAKE_AND_UNLOCK){
Trace.beginSection("MODE_WAKE_AND_UNLOCK");
} else {
@@ -523,6 +535,7 @@
for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) {
listener.onModeChanged(mode);
}
+ mBiometricUnlockInteractor.setBiometricUnlockState(mode);
}
private void onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType biometricSourceType) {
@@ -535,7 +548,8 @@
return mPendingAuthenticated != null
&& mUpdateMonitor
.isUnlockingWithBiometricAllowed(mPendingAuthenticated.isStrongBiometric)
- && mPendingAuthenticated.userId == KeyguardUpdateMonitor.getCurrentUser();
+ && mPendingAuthenticated.userId
+ == mSelectedUserInteractor.get().getSelectedUserId();
}
public @WakeAndUnlockMode int getMode() {
@@ -599,11 +613,11 @@
// if unlocking isn't allowed, log more information about why unlocking may not
// have been allowed
final int strongAuthFlags = mUpdateMonitor.getStrongAuthTracker().getStrongAuthForUser(
- KeyguardUpdateMonitor.getCurrentUser());
+ mSelectedUserInteractor.get().getSelectedUserId());
final boolean nonStrongBiometricAllowed =
mUpdateMonitor.getStrongAuthTracker()
.isNonStrongBiometricAllowedAfterIdleTimeout(
- KeyguardUpdateMonitor.getCurrentUser());
+ mSelectedUserInteractor.get().getSelectedUserId());
mLogger.logCalculateModeForFingerprintUnlockingNotAllowed(strongBiometric,
strongAuthFlags, nonStrongBiometricAllowed, deviceInteractive, keyguardShowing);
@@ -669,11 +683,11 @@
// if unlocking isn't allowed, log more information about why unlocking may not
// have been allowed
final int strongAuthFlags = mUpdateMonitor.getStrongAuthTracker().getStrongAuthForUser(
- KeyguardUpdateMonitor.getCurrentUser());
+ mSelectedUserInteractor.get().getSelectedUserId());
final boolean nonStrongBiometricAllowed =
mUpdateMonitor.getStrongAuthTracker()
.isNonStrongBiometricAllowedAfterIdleTimeout(
- KeyguardUpdateMonitor.getCurrentUser());
+ mSelectedUserInteractor.get().getSelectedUserId());
mLogger.logCalculateModeForPassiveAuthUnlockingNotAllowed(
strongBiometric, strongAuthFlags, nonStrongBiometricAllowed,
@@ -721,9 +735,9 @@
// Suppress all face auth errors if fingerprint can be used to authenticate
if ((biometricSourceType == BiometricSourceType.FACE
&& !mUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- KeyguardUpdateMonitor.getCurrentUser()))
+ mSelectedUserInteractor.get().getSelectedUserId()))
|| (biometricSourceType == BiometricSourceType.FINGERPRINT)) {
- vibrateError(biometricSourceType);
+ mHapticsInteractor.vibrateError();
}
cleanup();
@@ -750,45 +764,6 @@
cleanup();
}
- // these haptics are for device-entry only
- private void vibrateSuccess(BiometricSourceType type) {
- if (mAuthController.isSfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())
- && lastWakeupFromPowerButtonWithinHapticThreshold()) {
- mLogger.d("Skip auth success haptic. Power button was recently pressed.");
- return;
- }
- if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- mVibratorHelper.performHapticFeedback(
- mKeyguardViewController.getViewRootImpl().getView(),
- HapticFeedbackConstants.CONFIRM
- );
- } else {
- mVibratorHelper.vibrateAuthSuccess(
- getClass().getSimpleName() + ", type =" + type + "device-entry::success");
- }
- }
-
- private boolean lastWakeupFromPowerButtonWithinHapticThreshold() {
- final boolean lastWakeupFromPowerButton = mWakefulnessLifecycle.getLastWakeReason()
- == PowerManager.WAKE_REASON_POWER_BUTTON;
- return lastWakeupFromPowerButton
- && mWakefulnessLifecycle.getLastWakeTime() != UNKNOWN_LAST_WAKE_TIME
- && mSystemClock.uptimeMillis() - mWakefulnessLifecycle.getLastWakeTime()
- < RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS;
- }
-
- private void vibrateError(BiometricSourceType type) {
- if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- mVibratorHelper.performHapticFeedback(
- mKeyguardViewController.getViewRootImpl().getView(),
- HapticFeedbackConstants.REJECT
- );
- } else {
- mVibratorHelper.vibrateAuthError(
- getClass().getSimpleName() + ", type =" + type + "device-entry::error");
- }
- }
-
private void cleanup() {
releaseBiometricWakeLock();
}
@@ -818,6 +793,7 @@
for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) {
listener.onResetMode();
}
+ mBiometricUnlockInteractor.setBiometricUnlockState(MODE_NONE);
mNumConsecutiveFpFailures = 0;
mLastFpFailureUptimeMillis = 0;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 9fb6c1b..8d9fd12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1525,6 +1525,7 @@
// regressions, we'll continue standing up the root view in CentralSurfaces.
mNotificationShadeWindowController.fetchWindowRootView();
getNotificationShadeWindowViewController().setupExpandedStatusBar();
+ getNotificationShadeWindowViewController().setupCommunalHubLayout();
mShadeController.setNotificationShadeWindowViewController(
getNotificationShadeWindowViewController());
mBackActionInteractor.setup(mQsController, mShadeSurface);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index 1576aa2..6f992ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -47,6 +47,11 @@
private final ArrayMap<Object, DarkReceiver> mReceivers = new ArrayMap<>();
private int mIconTint = DEFAULT_ICON_TINT;
+ private int mContrastTint = DEFAULT_INVERSE_ICON_TINT;
+
+ private int mDarkModeContrastColor = DEFAULT_ICON_TINT;
+ private int mLightModeContrastColor = DEFAULT_INVERSE_ICON_TINT;
+
private float mDarkIntensity;
private int mDarkModeIconColorSingleTone;
private int mLightModeIconColorSingleTone;
@@ -83,6 +88,7 @@
public void addDarkReceiver(DarkReceiver receiver) {
mReceivers.put(receiver, receiver);
receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
+ receiver.onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);
}
public void addDarkReceiver(ImageView imageView) {
@@ -90,6 +96,7 @@
ColorStateList.valueOf(getTint(mTintAreas, imageView, mIconTint)));
mReceivers.put(imageView, receiver);
receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
+ receiver.onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);
}
public void removeDarkReceiver(DarkReceiver object) {
@@ -102,6 +109,7 @@
public void applyDark(DarkReceiver object) {
mReceivers.get(object).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
+ mReceivers.get(object).onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);
}
/**
@@ -125,8 +133,13 @@
@Override
public void applyDarkIntensity(float darkIntensity) {
mDarkIntensity = darkIntensity;
- mIconTint = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
+ ArgbEvaluator evaluator = ArgbEvaluator.getInstance();
+
+ mIconTint = (int) evaluator.evaluate(darkIntensity,
mLightModeIconColorSingleTone, mDarkModeIconColorSingleTone);
+ mContrastTint = (int) evaluator
+ .evaluate(darkIntensity, mLightModeContrastColor, mDarkModeContrastColor);
+
applyIconTint();
}
@@ -139,6 +152,7 @@
mDarkChangeFlow.setValue(new DarkChange(mTintAreas, mDarkIntensity, mIconTint));
for (int i = 0; i < mReceivers.size(); i++) {
mReceivers.valueAt(i).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
+ mReceivers.valueAt(i).onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);
}
}
@@ -146,6 +160,16 @@
public void dump(PrintWriter pw, String[] args) {
pw.println("DarkIconDispatcher: ");
pw.println(" mIconTint: 0x" + Integer.toHexString(mIconTint));
+ pw.println(" mContrastTint: 0x" + Integer.toHexString(mContrastTint));
+
+ pw.println(" mDarkModeIconColorSingleTone: 0x"
+ + Integer.toHexString(mDarkModeIconColorSingleTone));
+ pw.println(" mLightModeIconColorSingleTone: 0x"
+ + Integer.toHexString(mLightModeIconColorSingleTone));
+
+ pw.println(" mDarkModeContrastColor: 0x" + Integer.toHexString(mDarkModeContrastColor));
+ pw.println(" mLightModeContrastColor: 0x" + Integer.toHexString(mLightModeContrastColor));
+
pw.println(" mDarkIntensity: " + mDarkIntensity + "f");
pw.println(" mTintAreas: " + mTintAreas);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index de9854a..5deb08a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -28,10 +28,10 @@
import android.widget.LinearLayout;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.res.R;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger;
@@ -54,6 +54,7 @@
private ModernStatusBarWifiView mModernWifiView;
private boolean mDemoMode;
private int mColor;
+ private int mContrastColor;
private final MobileIconsViewModel mMobileIconsViewModel;
private final StatusBarLocation mLocation;
@@ -68,6 +69,7 @@
mStatusIcons = statusIcons;
mIconSize = iconSize;
mColor = DarkIconDispatcher.DEFAULT_ICON_TINT;
+ mContrastColor = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT;
mMobileIconsViewModel = mobileIconsViewModel;
mLocation = location;
@@ -89,15 +91,17 @@
((ViewGroup) getParent()).removeView(this);
}
- public void setColor(int color) {
+ /** Set the tint colors */
+ public void setColor(int color, int contrastColor) {
mColor = color;
+ mContrastColor = contrastColor;
updateColors();
}
private void updateColors() {
for (int i = 0; i < getChildCount(); i++) {
StatusIconDisplayable child = (StatusIconDisplayable) getChildAt(i);
- child.setStaticDrawableColor(mColor);
+ child.setStaticDrawableColor(mColor, mContrastColor);
child.setDecorColor(mColor);
}
}
@@ -223,7 +227,7 @@
StatusBarIconView v = new StatusBarIconView(getContext(), slot, null, false);
v.setTag(slot);
v.set(icon);
- v.setStaticDrawableColor(mColor);
+ v.setStaticDrawableColor(mColor, mContrastColor);
v.setDecorColor(mColor);
addView(v, 0, createLayoutParams());
}
@@ -269,7 +273,7 @@
}
mModernWifiView = view;
- mModernWifiView.setStaticDrawableColor(mColor);
+ mModernWifiView.setStaticDrawableColor(mColor, mContrastColor);
addView(view, viewIndex, createLayoutParams());
}
@@ -305,14 +309,20 @@
}
@Override
- public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
- setColor(DarkIconDispatcher.getTint(areas, mStatusIcons, tint));
+ public void onDarkChangedWithContrast(ArrayList<Rect> areas, int tint, int contrastTint) {
+ setColor(tint, contrastTint);
if (mModernWifiView != null) {
- mModernWifiView.onDarkChanged(areas, darkIntensity, tint);
+ mModernWifiView.onDarkChangedWithContrast(areas, tint, contrastTint);
}
+
for (ModernStatusBarMobileView view : mModernMobileViews) {
- view.onDarkChanged(areas, darkIntensity, tint);
+ view.onDarkChangedWithContrast(areas, tint, contrastTint);
}
}
+
+ @Override
+ public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+ // not needed
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 7730f7d9..a11cbc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -37,14 +37,15 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -55,9 +56,7 @@
import com.android.systemui.unfold.SysUIUnfoldComponent;
import java.io.PrintWriter;
-import java.util.HashSet;
import java.util.Optional;
-import java.util.Set;
import javax.inject.Inject;
@@ -83,12 +82,11 @@
private final Resources mResources;
private final BatteryController mBatteryController;
private final ScreenOffAnimationController mScreenOffAnimationController;
+ private final DozeInteractor mDozeInteractor;
private final FoldAodAnimationController mFoldAodAnimationController;
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private final UserTracker mUserTracker;
- private final Set<Callback> mCallbacks = new HashSet<>();
-
private boolean mDozeAlwaysOn;
private boolean mControlScreenOffAnimation;
private boolean mIsQuickPickupEnabled;
@@ -131,7 +129,8 @@
KeyguardUpdateMonitor keyguardUpdateMonitor,
ConfigurationController configurationController,
StatusBarStateController statusBarStateController,
- UserTracker userTracker) {
+ UserTracker userTracker,
+ DozeInteractor dozeInteractor) {
mResources = resources;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
mAlwaysOnPolicy = alwaysOnDisplayPolicy;
@@ -144,6 +143,7 @@
mScreenOffAnimationController = screenOffAnimationController;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mUserTracker = userTracker;
+ mDozeInteractor = dozeInteractor;
keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
tunerService.addTunable(
@@ -406,20 +406,6 @@
return mResources.getStringArray(R.array.doze_brightness_sensor_name_posture_mapping);
}
- /**
- * Callback to listen for DozeParameter changes.
- */
- public void addCallback(Callback callback) {
- mCallbacks.add(callback);
- }
-
- /**
- * Remove callback that listens for DozeParameter changes.
- */
- public void removeCallback(Callback callback) {
- mCallbacks.remove(callback);
- }
-
@Override
public void onTuningChanged(String key, String newValue) {
mDozeAlwaysOn = mAmbientDisplayConfiguration.alwaysOnEnabled(mUserTracker.getUserId());
@@ -465,10 +451,9 @@
}
private void dispatchAlwaysOnEvent() {
- for (Callback callback : mCallbacks) {
- callback.onAlwaysOnChange();
- }
mScreenOffAnimationController.onAlwaysOnChanged(getAlwaysOn());
+ mDozeInteractor.setAodAvailable(getAlwaysOn());
+
}
private boolean getPostureSpecificBool(
@@ -485,14 +470,6 @@
return bool;
}
- /** Callbacks for doze parameter related information */
- public interface Callback {
- /**
- * Invoked when the value of getAlwaysOn may have changed.
- */
- void onAlwaysOnChange();
- }
-
private final class SettingsObserver extends ContentObserver {
private final Uri mQuickPickupGesture =
Settings.Secure.getUriFor(Settings.Secure.DOZE_QUICK_PICKUP_GESTURE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index fb5a530..0a03af7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -26,14 +26,20 @@
import com.android.app.animation.Interpolators;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.keyguard.KeyguardStatusView;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.core.Logger;
+import com.android.systemui.log.dagger.KeyguardClockLog;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
+import javax.inject.Inject;
+
/**
* Utility class to calculate the clock position and top padding of notifications on Keyguard.
*/
public class KeyguardClockPositionAlgorithm {
+ private static final String TAG = "KeyguardClockPositionAlgorithm";
/**
* Margin between the bottom of the status view and the notification shade.
@@ -147,6 +153,13 @@
*/
private boolean mIsClockTopAligned;
+ private Logger mLogger;
+
+ @Inject
+ public KeyguardClockPositionAlgorithm(@KeyguardClockLog LogBuffer logBuffer) {
+ mLogger = new Logger(logBuffer, TAG);
+ }
+
/**
* Refreshes the dimension values.
*/
@@ -306,6 +319,20 @@
+ fullyDarkBurnInOffset
+ shift;
mCurrentBurnInOffsetY = MathUtils.lerp(0, fullyDarkBurnInOffset, darkAmount);
+ final String inputs = "panelExpansion: " + panelExpansion + " darkAmount: " + darkAmount;
+ final String outputs = "clockY: " + clockY
+ + " burnInPreventionOffsetY: " + burnInPreventionOffsetY
+ + " fullyDarkBurnInOffset: " + fullyDarkBurnInOffset
+ + " shift: " + shift
+ + " mOverStretchAmount: " + mOverStretchAmount
+ + " mCurrentBurnInOffsetY: " + mCurrentBurnInOffsetY;
+ mLogger.i(msg -> {
+ return msg.getStr1() + " -> " + msg.getStr2();
+ }, msg -> {
+ msg.setStr1(inputs);
+ msg.setStr2(outputs);
+ return kotlin.Unit.INSTANCE;
+ });
return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index bde5c32..109e77e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -31,6 +31,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.Assert
import com.android.systemui.util.sensors.AsyncSensorManager
import java.io.PrintWriter
@@ -48,7 +49,8 @@
private val asyncSensorManager: AsyncSensorManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
- private val dumpManager: DumpManager
+ private val dumpManager: DumpManager,
+ private val selectedUserInteractor: SelectedUserInteractor,
) : Dumpable, CoreStartable {
private val pickupSensor = asyncSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE)
@@ -115,7 +117,7 @@
val onKeyguard = keyguardUpdateMonitor.isKeyguardVisible &&
!statusBarStateController.isDozing
- val userId = KeyguardUpdateMonitor.getCurrentUser()
+ val userId = selectedUserInteractor.getSelectedUserId()
val isFaceEnabled = keyguardUpdateMonitor.isFaceAuthEnabledForUser(userId)
val shouldListen = (onKeyguard || bouncerVisible) && isFaceEnabled
if (shouldListen != isListening) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 58126ae..8a64a50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -436,10 +436,14 @@
private void updateIconsAndTextColors(StatusBarIconController.TintedIconManager iconManager) {
@ColorInt int textColor = Utils.getColorAttrDefaultColor(mContext,
R.attr.wallpaperTextColor);
+ float luminance = Color.luminance(textColor);
@ColorInt int iconColor = Utils.getColorStateListDefaultColor(mContext,
- Color.luminance(textColor) < 0.5
+ luminance < 0.5
? com.android.settingslib.R.color.dark_mode_icon_color_single_tone
: com.android.settingslib.R.color.light_mode_icon_color_single_tone);
+ @ColorInt int contrastColor = luminance < 0.5
+ ? DarkIconDispatcherImpl.DEFAULT_ICON_TINT
+ : DarkIconDispatcherImpl.DEFAULT_INVERSE_ICON_TINT;
float intensity = textColor == Color.WHITE ? 0 : 1;
mCarrierLabel.setTextColor(iconColor);
@@ -451,7 +455,7 @@
}
if (iconManager != null) {
- iconManager.setTint(iconColor);
+ iconManager.setTint(iconColor, contrastColor);
}
mDarkChange.setValue(new DarkChange(mEmptyTintRect, intensity, iconColor));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index 5553270..f9856b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -205,14 +205,13 @@
}
public void setupShelf(NotificationShelfController notificationShelfController) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mShelfIcons = notificationShelfController.getShelfIcons();
}
public void setShelfIcons(NotificationIconContainer icons) {
- if (mShelfRefactor.expectEnabled()) {
- mShelfIcons = icons;
- }
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
+ mShelfIcons = icons;
}
public void onDensityOrFontScaleChanged(@NotNull Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 92c786f..00fd9fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -263,8 +263,7 @@
if (result.success) {
mCached = true;
mCache = result.bitmap;
- mMediaManager.updateMediaMetaData(
- true /* metaDataChanged */, true /* allowEnterAnimation */);
+ mMediaManager.updateMediaMetaData(true /* metaDataChanged */);
}
mLoader = null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index ffeb1a8..9ae4195 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -31,11 +31,11 @@
import androidx.annotation.VisibleForTesting;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusIconDisplayable;
@@ -248,7 +248,10 @@
*
*/
class TintedIconManager extends IconManager {
+ // The main tint, used as the foreground in non layer drawables
private int mColor;
+ // To be used as the main tint in drawables that wish to have a layer
+ private int mForegroundColor;
public TintedIconManager(
ViewGroup group,
@@ -268,26 +271,41 @@
protected void onIconAdded(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
- view.setStaticDrawableColor(mColor);
+ view.setStaticDrawableColor(mColor, mForegroundColor);
view.setDecorColor(mColor);
}
- public void setTint(int color) {
- mColor = color;
+ /**
+ * Most icons are a single layer, and tintColor will be used as the tint in those cases.
+ * For icons that have a background, foregroundColor becomes the contrasting tint used
+ * for the foreground.
+ *
+ * @param tintColor the main tint to use for the icons in the group
+ * @param foregroundColor used as the main tint for layer-ish drawables where tintColor is
+ * being used as the background
+ */
+ public void setTint(int tintColor, int foregroundColor) {
+ mColor = tintColor;
+ mForegroundColor = foregroundColor;
+
for (int i = 0; i < mGroup.getChildCount(); i++) {
View child = mGroup.getChildAt(i);
if (child instanceof StatusIconDisplayable) {
StatusIconDisplayable icon = (StatusIconDisplayable) child;
- icon.setStaticDrawableColor(mColor);
+ icon.setStaticDrawableColor(mColor, mForegroundColor);
icon.setDecorColor(mColor);
}
}
+
+ if (mDemoStatusIcons != null) {
+ mDemoStatusIcons.setColor(tintColor, foregroundColor);
+ }
}
@Override
protected DemoStatusIcons createDemoStatusIcons() {
DemoStatusIcons icons = super.createDemoStatusIcons();
- icons.setColor(mColor);
+ icons.setColor(mColor, mForegroundColor);
return icons;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 3adf338..90fddd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -92,6 +92,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import dagger.Lazy;
@@ -313,6 +314,7 @@
private final KeyguardUpdateMonitor mKeyguardUpdateManager;
private final LatencyTracker mLatencyTracker;
private final KeyguardSecurityModel mKeyguardSecurityModel;
+ private final SelectedUserInteractor mSelectedUserInteractor;
@Nullable private KeyguardBypassController mBypassController;
@Nullable private OccludingAppBiometricUI mOccludingAppBiometricUI;
@@ -370,7 +372,8 @@
KeyguardTransitionInteractor keyguardTransitionInteractor,
@Main CoroutineDispatcher mainDispatcher,
Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor,
- Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy
+ Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy,
+ SelectedUserInteractor selectedUserInteractor
) {
mContext = context;
mViewMediatorCallback = callback;
@@ -403,6 +406,7 @@
mMainDispatcher = mainDispatcher;
mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
mKeyguardDismissActionInteractor = keyguardDismissActionInteractorLazy;
+ mSelectedUserInteractor = selectedUserInteractor;
}
KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -961,7 +965,7 @@
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
}
if (isShowing) {
- mMediaManager.updateMediaMetaData(false, animate && !isOccluded);
+ mMediaManager.updateMediaMetaData(false);
}
mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
@@ -1142,7 +1146,8 @@
*/
public boolean isSecure() {
return mKeyguardSecurityModel.getSecurityMode(
- KeyguardUpdateMonitor.getCurrentUser()) != KeyguardSecurityModel.SecurityMode.None;
+ mSelectedUserInteractor.getSelectedUserId())
+ != KeyguardSecurityModel.SecurityMode.None;
}
/**
@@ -1690,7 +1695,7 @@
*/
public boolean needsFullscreenBouncer() {
KeyguardSecurityModel.SecurityMode mode = mKeyguardSecurityModel.getSecurityMode(
- KeyguardUpdateMonitor.getCurrentUser());
+ mSelectedUserInteractor.getSelectedUserId());
return mode == KeyguardSecurityModel.SecurityMode.SimPin
|| mode == KeyguardSecurityModel.SecurityMode.SimPuk;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 2d14f6b..57a8e6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -221,7 +221,7 @@
@Override
public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
- mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation);
+ mMediaManager.updateMediaMetaData(metaDataChanged);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
new file mode 100644
index 0000000..85fd2af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.statusbar.phone
+
+import android.app.Dialog
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.os.Bundle
+import android.view.Gravity
+import android.view.WindowManager
+import com.android.systemui.res.R
+
+/** A dialog shown as a bottom sheet. */
+open class SystemUIBottomSheetDialog(
+ context: Context,
+ theme: Int = R.style.Theme_SystemUI_Dialog,
+) : Dialog(context, theme) {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ window?.apply {
+ setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+ addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+
+ setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+ setGravity(Gravity.BOTTOM)
+ val edgeToEdgeHorizontally =
+ context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
+ if (edgeToEdgeHorizontally) {
+ decorView.setPadding(0, 0, 0, 0)
+ setLayout(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.WRAP_CONTENT
+ )
+
+ val lp = attributes
+ lp.fitInsetsSides = 0
+ lp.horizontalMargin = 0f
+ attributes = lp
+ }
+ }
+ setCanceledOnTouchOutside(true)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 2c15e27..de37170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -34,6 +34,8 @@
import com.android.systemui.util.TraceUtils
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
/**
* When to show the keyguard (AOD) view. This should be once the light reveal scrim is barely
@@ -65,7 +67,8 @@
private val notifShadeWindowControllerLazy: dagger.Lazy<NotificationShadeWindowController>,
private val interactionJankMonitor: InteractionJankMonitor,
private val powerManager: PowerManager,
- private val handler: Handler = Handler()
+ private val handler: Handler = Handler(),
+ private val featureFlags: FeatureFlags,
) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
private lateinit var centralSurfaces: CentralSurfaces
private lateinit var shadeViewController: ShadeViewController
@@ -285,7 +288,11 @@
// up, with unpredictable consequences.
if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) &&
shouldAnimateInKeyguard) {
- aodUiAnimationPlaying = true
+ if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ // Tracking this state should no longer be relevant, as the isInteractive
+ // check covers it
+ aodUiAnimationPlaying = true
+ }
// Show AOD. That'll cause the KeyguardVisibilityHelper to call
// #animateInKeyguard.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index aacdc63..3522b9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -190,6 +190,24 @@
fun logOnSimStateChanged() {
buffer.log(TAG, LogLevel.INFO, "onSimStateChanged")
}
+
+ fun logPrioritizedNetworkAvailable(netId: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { int1 = netId },
+ { "Found prioritized network (nedId=$int1)" },
+ )
+ }
+
+ fun logPrioritizedNetworkLost(netId: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { int1 = netId },
+ { "Lost prioritized network (nedId=$int1)" },
+ )
+ }
}
private const val TAG = "MobileInputLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index a89b1b2..679426d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -131,6 +131,12 @@
*/
val isAllowedDuringAirplaneMode: StateFlow<Boolean>
+ /**
+ * True if this network has NET_CAPABILITIY_PRIORITIZE_LATENCY, and can be considered to be a
+ * network slice
+ */
+ val hasPrioritizedNetworkCapabilities: StateFlow<Boolean>
+
companion object {
/** The default number of levels to use for [numberOfLevels]. */
const val DEFAULT_NUM_LEVELS = 4
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index c576b82..caa9d1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -191,6 +191,8 @@
override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
+ override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
+
/**
* Process a new demo mobile event. Note that [resolvedNetworkType] must be passed in separately
* from the event, due to the requirement to reverse the mobile mappings lookup in the top-level
@@ -225,6 +227,7 @@
_resolvedNetworkType.value = resolvedNetworkType
isAllowedDuringAirplaneMode.value = false
+ hasPrioritizedNetworkCapabilities.value = event.slice
}
fun processCarrierMergedEvent(event: FakeWifiEventModel.CarrierMerged) {
@@ -250,6 +253,7 @@
_isGsm.value = false
_carrierNetworkChangeActive.value = false
isAllowedDuringAirplaneMode.value = true
+ hasPrioritizedNetworkCapabilities.value = false
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
index d4ddb85..4cd877e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
@@ -76,6 +76,7 @@
val carrierNetworkChange = getString("carriernetworkchange") == "show"
val roaming = getString("roam") == "show"
val name = getString("networkname") ?: "demo mode"
+ val slice = getString("slice").toBoolean()
return Mobile(
level = level,
@@ -87,6 +88,7 @@
carrierNetworkChange = carrierNetworkChange,
roaming = roaming,
name = name,
+ slice = slice,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
index 8b03f71..0aa95f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
@@ -36,6 +36,7 @@
val carrierNetworkChange: Boolean,
val roaming: Boolean,
val name: String,
+ val slice: Boolean = false,
) : FakeNetworkEventModel
data class MobileDisabled(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index 28be3be..27edd1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -174,6 +174,13 @@
*/
override val isAllowedDuringAirplaneMode = MutableStateFlow(true).asStateFlow()
+ /**
+ * It's not currently considered possible that a carrier merged network can have these
+ * prioritized capabilities. If we need to track them, we can add the same check as is in
+ * [MobileConnectionRepositoryImpl].
+ */
+ override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false).asStateFlow()
+
override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index ee11c06..6b61921 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -309,6 +309,15 @@
activeRepo.value.isAllowedDuringAirplaneMode.value,
)
+ override val hasPrioritizedNetworkCapabilities =
+ activeRepo
+ .flatMapLatest { it.hasPrioritizedNetworkCapabilities }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.hasPrioritizedNetworkCapabilities.value,
+ )
+
class Factory
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index dc50990..760dd7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -21,6 +21,11 @@
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
@@ -91,6 +96,7 @@
subscriptionModel: StateFlow<SubscriptionModel?>,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
+ connectivityManager: ConnectivityManager,
private val telephonyManager: TelephonyManager,
systemUiCarrierConfig: SystemUiCarrierConfig,
broadcastDispatcher: BroadcastDispatcher,
@@ -374,11 +380,50 @@
/** Typical mobile connections aren't available during airplane mode. */
override val isAllowedDuringAirplaneMode = MutableStateFlow(false).asStateFlow()
+ /**
+ * Currently, a network with NET_CAPABILITY_PRIORITIZE_LATENCY is the only type of network that
+ * we consider to be a "network slice". _PRIORITIZE_BANDWIDTH may be added in the future. Any of
+ * these capabilities that are used here must also be represented in the
+ * self_certified_network_capabilities.xml config file
+ */
+ @SuppressLint("WrongConstant")
+ private val networkSliceRequest =
+ NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+ .setSubscriptionIds(setOf(subId))
+ .build()
+
+ @SuppressLint("MissingPermission")
+ override val hasPrioritizedNetworkCapabilities: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ // Our network callback listens only for this.subId && net_cap_prioritize_latency
+ // therefore our state is a simple mapping of whether or not that network exists
+ val callback =
+ object : NetworkCallback() {
+ override fun onAvailable(network: Network) {
+ logger.logPrioritizedNetworkAvailable(network.netId)
+ trySend(true)
+ }
+
+ override fun onLost(network: Network) {
+ logger.logPrioritizedNetworkLost(network.netId)
+ trySend(false)
+ }
+ }
+
+ connectivityManager.registerNetworkCallback(networkSliceRequest, callback)
+
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ }
+ .flowOn(bgDispatcher)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
class Factory
@Inject
constructor(
private val context: Context,
private val broadcastDispatcher: BroadcastDispatcher,
+ private val connectivityManager: ConnectivityManager,
private val telephonyManager: TelephonyManager,
private val logger: MobileInputLogger,
private val carrierConfigRepository: CarrierConfigRepository,
@@ -399,6 +444,7 @@
subscriptionModel,
defaultNetworkName,
networkNameSeparator,
+ connectivityManager,
telephonyManager.createForSubscriptionId(subId),
carrierConfigRepository.getOrCreateConfigForSubId(subId),
broadcastDispatcher,
@@ -421,11 +467,17 @@
*/
sealed interface CallbackEvent {
data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent
+
data class OnDataActivity(val direction: Int) : CallbackEvent
+
data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent
+
data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent
+
data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent
+
data class OnServiceStateChanged(val serviceState: ServiceState) : CallbackEvent
+
data class OnSignalStrengthChanged(val signalStrength: SignalStrength) : CallbackEvent
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 4bf297c..fe49c07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -76,6 +76,9 @@
/** Observable for RAT type (network type) indicator */
val networkTypeIconGroup: StateFlow<NetworkTypeIconModel>
+ /** Whether or not to show the slice attribution */
+ val showSliceAttribution: StateFlow<Boolean>
+
/**
* Provider name for this network connection. The name can be one of 3 values:
* 1. The default network name, if one is configured
@@ -238,6 +241,9 @@
DefaultIcon(defaultMobileIconGroup.value),
)
+ override val showSliceAttribution: StateFlow<Boolean> =
+ connectionRepository.hasPrioritizedNetworkCapabilities
+
override val isRoaming: StateFlow<Boolean> =
combine(
connectionRepository.carrierNetworkChangeActive,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index f0470ca..b93e443 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -16,11 +16,13 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.binder
+import android.annotation.ColorInt
import android.content.res.ColorStateList
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
+import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.Space
import androidx.core.view.isVisible
@@ -28,10 +30,11 @@
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.settingslib.graph.SignalDrawable
-import com.android.systemui.res.R
import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
@@ -43,6 +46,11 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
+private data class Colors(
+ @ColorInt val tint: Int,
+ @ColorInt val contrast: Int,
+)
+
object MobileIconBinder {
/** Binds the view to the view-model, continuing to update the former based on the latter */
@JvmStatic
@@ -57,6 +65,7 @@
val activityIn = view.requireViewById<ImageView>(R.id.mobile_in)
val activityOut = view.requireViewById<ImageView>(R.id.mobile_out)
val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type)
+ val networkTypeContainer = view.requireViewById<FrameLayout>(R.id.mobile_type_container)
val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming)
@@ -70,7 +79,13 @@
@StatusBarIconView.VisibleState
val visibilityState: MutableStateFlow<Int> = MutableStateFlow(initialVisibilityState)
- val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+ val iconTint: MutableStateFlow<Colors> =
+ MutableStateFlow(
+ Colors(
+ tint = DarkIconDispatcher.DEFAULT_ICON_TINT,
+ contrast = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT
+ )
+ )
val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
var isCollecting = false
@@ -139,7 +154,26 @@
dataTypeId,
)
dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
- networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
+ networkTypeContainer.visibility =
+ if (dataTypeId != null) VISIBLE else GONE
+ }
+ }
+
+ // Set the network type background
+ launch {
+ viewModel.networkTypeBackground.collect { background ->
+ networkTypeContainer.setBackgroundResource(background?.res ?: 0)
+
+ // Tint will invert when this bit changes
+ if (background?.res != null) {
+ networkTypeContainer.backgroundTintList =
+ ColorStateList.valueOf(iconTint.value.tint)
+ networkTypeView.imageTintList =
+ ColorStateList.valueOf(iconTint.value.contrast)
+ } else {
+ networkTypeView.imageTintList =
+ ColorStateList.valueOf(iconTint.value.tint)
+ }
}
}
@@ -164,14 +198,24 @@
// Set the tint
launch {
- iconTint.collect { tint ->
- val tintList = ColorStateList.valueOf(tint)
- iconView.imageTintList = tintList
- networkTypeView.imageTintList = tintList
- roamingView.imageTintList = tintList
- activityIn.imageTintList = tintList
- activityOut.imageTintList = tintList
- dotView.setDecorColor(tint)
+ iconTint.collect { colors ->
+ val tint = ColorStateList.valueOf(colors.tint)
+ val contrast = ColorStateList.valueOf(colors.contrast)
+
+ iconView.imageTintList = tint
+
+ // If the bg is visible, tint it and use the contrast for the fg
+ if (viewModel.networkTypeBackground.value != null) {
+ networkTypeContainer.backgroundTintList = tint
+ networkTypeView.imageTintList = contrast
+ } else {
+ networkTypeView.imageTintList = tint
+ }
+
+ roamingView.imageTintList = tint
+ activityIn.imageTintList = tint
+ activityOut.imageTintList = tint
+ dotView.setDecorColor(colors.tint)
}
}
@@ -196,8 +240,8 @@
visibilityState.value = state
}
- override fun onIconTintChanged(newTint: Int) {
- iconTint.value = newTint
+ override fun onIconTintChanged(newTint: Int, contrastTint: Int) {
+ iconTint.value = Colors(tint = newTint, contrast = contrastTint)
}
override fun onDecorTintChanged(newTint: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index dfabeea..d88c9ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -19,7 +19,10 @@
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags.NEW_NETWORK_SLICE_UI
import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.res.R
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
@@ -47,6 +50,8 @@
val roaming: Flow<Boolean>
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
val networkTypeIcon: Flow<Icon.Resource?>
+ /** The slice attribution. Drawn as a background layer */
+ val networkTypeBackground: StateFlow<Icon.Resource?>
val activityInVisible: Flow<Boolean>
val activityOutVisible: Flow<Boolean>
val activityContainerVisible: Flow<Boolean>
@@ -67,12 +72,12 @@
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
-class MobileIconViewModel
-constructor(
+class MobileIconViewModel(
override val subscriptionId: Int,
iconInteractor: MobileIconInteractor,
airplaneModeInteractor: AirplaneModeInteractor,
constants: ConnectivityConstants,
+ flags: FeatureFlagsClassic,
scope: CoroutineScope,
) : MobileIconViewModelCommon {
override val isVisible: StateFlow<Boolean> =
@@ -152,6 +157,20 @@
.distinctUntilChanged()
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
+ override val networkTypeBackground =
+ if (!flags.isEnabled(NEW_NETWORK_SLICE_UI)) {
+ flowOf(null)
+ } else {
+ iconInteractor.showSliceAttribution.map {
+ if (it) {
+ Icon.Resource(R.drawable.mobile_network_type_background, null)
+ } else {
+ null
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
override val roaming: StateFlow<Boolean> =
iconInteractor.isRoaming
.logDiffsForTable(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 0f55910..be843ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -19,6 +19,7 @@
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
@@ -54,6 +55,7 @@
private val interactor: MobileIconsInteractor,
private val airplaneModeInteractor: AirplaneModeInteractor,
private val constants: ConnectivityConstants,
+ private val flags: FeatureFlagsClassic,
@Application private val scope: CoroutineScope,
) {
@VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
@@ -113,6 +115,7 @@
interactor.getMobileConnectionInteractorForSubId(subId),
airplaneModeInteractor,
constants,
+ flags,
scope,
)
.also { mobileIconSubIdCache[subId] = it }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
index 81f8683..790596e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
@@ -32,8 +32,8 @@
/** Notifies that the visibility state has changed. */
fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
- /** Notifies that the icon tint has been updated. */
- fun onIconTintChanged(newTint: Int)
+ /** Notifies that the icon tint has been updated. Includes a contrast for layered drawables */
+ fun onIconTintChanged(newTint: Int, contrastTint: Int)
/** Notifies that the decor tint has been updated (used only for the dot). */
fun onDecorTintChanged(newTint: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index fe69d81..3b87bed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -20,8 +20,8 @@
import android.graphics.Rect
import android.util.AttributeSet
import android.view.Gravity
-import com.android.systemui.res.R
import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.res.R
import com.android.systemui.statusbar.BaseStatusBarFrameLayout
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
@@ -51,13 +51,23 @@
override fun getSlot() = slot
override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+ // nop
+ }
+
+ override fun onDarkChangedWithContrast(areas: ArrayList<Rect>, tint: Int, contrastTint: Int) {
val newTint = DarkIconDispatcher.getTint(areas, this, tint)
- binding.onIconTintChanged(newTint)
+ val contrast = DarkIconDispatcher.getInverseTint(areas, this, contrastTint)
+
+ binding.onIconTintChanged(newTint, contrast)
binding.onDecorTintChanged(newTint)
}
override fun setStaticDrawableColor(color: Int) {
- binding.onIconTintChanged(color)
+ // nop
+ }
+
+ override fun setStaticDrawableColor(color: Int, foregroundColor: Int) {
+ binding.onIconTintChanged(color, foregroundColor)
}
override fun setDecorColor(color: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
index e9e52a2..1670dd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
@@ -104,7 +104,7 @@
val callback =
object : WifiPickerTracker.WifiPickerTrackerCallback {
override fun onWifiEntriesChanged() {
- val connectedEntry = wifiPickerTracker?.connectedWifiEntry
+ val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
logOnWifiEntriesChanged(connectedEntry)
val secondaryNetworks =
@@ -217,6 +217,21 @@
.stateIn(scope, SharingStarted.Eagerly, emptyList())
/**
+ * [WifiPickerTracker.getConnectedWifiEntry] stores a [MergedCarrierEntry] separately from the
+ * [WifiEntry] for the primary connection. Therefore, we have to prefer the carrier-merged entry
+ * if it exists, falling back on the connected entry if null
+ */
+ private val WifiPickerTracker?.mergedOrPrimaryConnection: WifiEntry?
+ get() {
+ val mergedEntry: MergedCarrierEntry? = this?.mergedCarrierEntry
+ return if (mergedEntry != null && mergedEntry.isDefaultNetwork) {
+ mergedEntry
+ } else {
+ this?.connectedWifiEntry
+ }
+ }
+
+ /**
* Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the
* primary network. Returns an inactive network if it's not primary.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index a9ac51d..6005527 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -23,9 +23,9 @@
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.res.R
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
@@ -165,7 +165,7 @@
visibilityState.value = state
}
- override fun onIconTintChanged(newTint: Int) {
+ override fun onIconTintChanged(newTint: Int, contrastTint: Int /* unused */) {
iconTint.value = newTint
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 62e2381..b614b6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -38,6 +38,7 @@
import com.android.systemui.res.R;
import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.user.UserSwitchDialogController;
@@ -148,6 +149,7 @@
DozeParameters dozeParameters,
ScreenOffAnimationController screenOffAnimationController,
UserSwitchDialogController userSwitchDialogController,
+ FeatureFlags featureFlags,
UiEventLogger uiEventLogger) {
super(view);
if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
@@ -160,7 +162,8 @@
mStatusBarStateController = statusBarStateController;
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
keyguardStateController, dozeParameters,
- screenOffAnimationController, /* animateYPos= */ false, /* logBuffer= */ null);
+ screenOffAnimationController, /* animateYPos= */ false,
+ featureFlags, /* logBuffer= */ null);
mUserSwitchDialogController = userSwitchDialogController;
mUiEventLogger = uiEventLogger;
}
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 1c88289..c624518 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -37,11 +37,12 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import dagger.Lazy;
@@ -64,6 +65,7 @@
private final Context mContext;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final LockPatternUtils mLockPatternUtils;
+ private final SelectedUserInteractor mUserInteractor;
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
new UpdateMonitorCallback();
private final Lazy<KeyguardUnlockAnimationController> mUnlockAnimationControllerLazy;
@@ -120,11 +122,13 @@
Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
KeyguardUpdateMonitorLogger logger,
DumpManager dumpManager,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ SelectedUserInteractor userInteractor) {
mContext = context;
mLogger = logger;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
+ mUserInteractor = userInteractor;
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mUnlockAnimationControllerLazy = keyguardUnlockAnimationController;
mFeatureFlags = featureFlags;
@@ -250,7 +254,7 @@
@VisibleForTesting
void update(boolean updateAlways) {
Trace.beginSection("KeyguardStateController#update");
- int user = KeyguardUpdateMonitor.getCurrentUser();
+ int user = mUserInteractor.getSelectedUserId();
boolean secure = mLockPatternUtils.isSecure(user);
boolean canDismissLockScreen = !secure || mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)
|| (Build.IS_DEBUGGABLE && DEBUG_AUTH_WITH_ADB && mDebugUnlocked);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index bb074ac..dfe2686 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -38,10 +38,11 @@
import com.android.keyguard.KeyguardVisibilityHelper;
import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -160,6 +161,7 @@
KeyguardStateController keyguardStateController,
SysuiStatusBarStateController statusBarStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
+ FeatureFlags featureFlags,
DozeParameters dozeParameters,
ScreenOffAnimationController screenOffAnimationController) {
super(keyguardUserSwitcherView);
@@ -174,7 +176,8 @@
mUserSwitcherController, this);
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
keyguardStateController, dozeParameters,
- screenOffAnimationController, /* animateYPos= */ false, /* logBuffer= */ null);
+ screenOffAnimationController, /* animateYPos= */ false,
+ featureFlags, /* logBuffer= */ null);
mBackground = new KeyguardUserSwitcherScrim(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 53fed3d..ceed81a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -305,7 +305,8 @@
&& editTextRootWindowInsets.isVisible(WindowInsets.Type.ime());
if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) {
// Pass null to ensure all inputs are cleared for this entry b/227115380
- mController.removeRemoteInput(mEntry, null);
+ mController.removeRemoteInput(mEntry, null,
+ /* reason= */"RemoteInputView$WindowInsetAnimation#onEnd");
}
}
}
@@ -426,7 +427,7 @@
@VisibleForTesting
void onDefocus(boolean animate, boolean logClose, @Nullable Runnable doAfterDefocus) {
- mController.removeRemoteInput(mEntry, mToken);
+ mController.removeRemoteInput(mEntry, mToken, /* reason= */"RemoteInputView#onDefocus");
mEntry.remoteInputText = mEditText.getText();
// During removal, we get reattached and lose focus. Not hiding in that
@@ -536,7 +537,8 @@
if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) {
return;
}
- mController.removeRemoteInput(mEntry, mToken);
+ mController.removeRemoteInput(mEntry, mToken,
+ /* reason= */"RemoteInputView#onDetachedFromWindow");
mController.removeSpinning(mEntry.getKey(), mToken);
}
@@ -655,7 +657,7 @@
mEditText.setText(mEntry.remoteInputText);
mEditText.setSelection(mEditText.length());
mEditText.requestFocus();
- mController.addRemoteInput(mEntry, mToken);
+ mController.addRemoteInput(mEntry, mToken, "RemoteInputView#focus");
setAttachment(mEntry.remoteInputAttachment);
updateSendButton();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index a50fd6f..6c0d433 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -255,7 +255,8 @@
entry.lastRemoteInputSent = SystemClock.elapsedRealtime()
entry.mRemoteEditImeAnimatingAway = true
remoteInputController.addSpinning(entry.key, view.mToken)
- remoteInputController.removeRemoteInput(entry, view.mToken)
+ remoteInputController.removeRemoteInput(entry, view.mToken,
+ /* reason= */ "RemoteInputViewController#sendRemoteInput")
remoteInputController.remoteInputSent(entry)
entry.setHasSentReply()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
index f88339a..7829d6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
@@ -27,7 +27,7 @@
import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.user.domain.interactor.GuestUserInteractor
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
import dagger.Lazy
import java.io.PrintWriter
@@ -41,7 +41,7 @@
@Inject
constructor(
@Application private val applicationContext: Context,
- private val userInteractorLazy: Lazy<UserInteractor>,
+ private val userSwitcherInteractorLazy: Lazy<UserSwitcherInteractor>,
private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
private val activityStarter: ActivityStarter,
@@ -53,26 +53,29 @@
fun onUserSwitched()
}
- private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
+ private val mUserSwitcherInteractor: UserSwitcherInteractor by lazy {
+ userSwitcherInteractorLazy.get()
+ }
private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
- private val callbackCompatMap = mutableMapOf<UserSwitchCallback, UserInteractor.UserCallback>()
+ private val callbackCompatMap =
+ mutableMapOf<UserSwitchCallback, UserSwitcherInteractor.UserCallback>()
/** The current list of [UserRecord]. */
val users: ArrayList<UserRecord>
- get() = userInteractor.userRecords.value
+ get() = mUserSwitcherInteractor.userRecords.value
/** Whether the user switcher experience should use the simple experience. */
val isSimpleUserSwitcher: Boolean
- get() = userInteractor.isSimpleUserSwitcher
+ get() = mUserSwitcherInteractor.isSimpleUserSwitcher
val isUserSwitcherEnabled: Boolean
- get() = userInteractor.isUserSwitcherEnabled
+ get() = mUserSwitcherInteractor.isUserSwitcherEnabled
/** The [UserRecord] of the current user or `null` when none. */
val currentUserRecord: UserRecord?
- get() = userInteractor.selectedUserRecord.value
+ get() = mUserSwitcherInteractor.selectedUserRecord.value
/** The name of the current user of the device or `null`, when none is selected. */
val currentUserName: String?
@@ -81,8 +84,8 @@
LegacyUserUiHelper.getUserRecordName(
context = applicationContext,
record = it,
- isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
- isGuestUserResetting = userInteractor.isGuestUserResetting,
+ isGuestUserAutoCreated = mUserSwitcherInteractor.isGuestUserAutoCreated,
+ isGuestUserResetting = mUserSwitcherInteractor.isGuestUserResetting,
)
}
@@ -98,21 +101,21 @@
* @param dialogShower An optional [DialogShower] in case we need to show dialogs.
*/
fun onUserSelected(userId: Int, dialogShower: DialogShower?) {
- userInteractor.selectUser(userId, dialogShower)
+ mUserSwitcherInteractor.selectUser(userId, dialogShower)
}
/** Whether the guest user is configured to always be present on the device. */
val isGuestUserAutoCreated: Boolean
- get() = userInteractor.isGuestUserAutoCreated
+ get() = mUserSwitcherInteractor.isGuestUserAutoCreated
/** Whether the guest user is currently being reset. */
val isGuestUserResetting: Boolean
- get() = userInteractor.isGuestUserResetting
+ get() = mUserSwitcherInteractor.isGuestUserResetting
/** Registers an adapter to notify when the users change. */
fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
- userInteractor.addCallback(
- object : UserInteractor.UserCallback {
+ mUserSwitcherInteractor.addCallback(
+ object : UserSwitcherInteractor.UserCallback {
override fun isEvictable(): Boolean {
return adapter.get() == null
}
@@ -129,7 +132,7 @@
record: UserRecord,
dialogShower: DialogShower?,
) {
- userInteractor.onRecordSelected(record, dialogShower)
+ mUserSwitcherInteractor.onRecordSelected(record, dialogShower)
}
/**
@@ -152,7 +155,7 @@
* `UserHandle.USER_NULL`, then switch immediately to the newly created guest user.
*/
fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
- userInteractor.removeGuestUser(
+ mUserSwitcherInteractor.removeGuestUser(
guestUserId = guestUserId,
targetUserId = targetUserId,
)
@@ -168,7 +171,7 @@
* only if its ephemeral, else keep guest
*/
fun exitGuestUser(guestUserId: Int, targetUserId: Int, forceRemoveGuestOnExit: Boolean) {
- userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
+ mUserSwitcherInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
}
/**
@@ -194,31 +197,31 @@
* The pictures are only loaded if they have not been loaded yet.
*/
fun refreshUsers() {
- userInteractor.refreshUsers()
+ mUserSwitcherInteractor.refreshUsers()
}
/** Adds a subscriber to when user switches. */
fun addUserSwitchCallback(callback: UserSwitchCallback) {
val interactorCallback =
- object : UserInteractor.UserCallback {
+ object : UserSwitcherInteractor.UserCallback {
override fun onUserStateChanged() {
callback.onUserSwitched()
}
}
callbackCompatMap[callback] = interactorCallback
- userInteractor.addCallback(interactorCallback)
+ mUserSwitcherInteractor.addCallback(interactorCallback)
}
/** Removes a previously-added subscriber. */
fun removeUserSwitchCallback(callback: UserSwitchCallback) {
val interactorCallback = callbackCompatMap.remove(callback)
if (interactorCallback != null) {
- userInteractor.removeCallback(interactorCallback)
+ mUserSwitcherInteractor.removeCallback(interactorCallback)
}
}
fun dump(pw: PrintWriter, args: Array<out String>) {
- userInteractor.dump(pw)
+ mUserSwitcherInteractor.dump(pw)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
index dc7fadd..12387893 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
@@ -21,7 +21,6 @@
import android.os.Handler
import android.os.UserManager
import android.provider.Settings.Global.USER_SWITCHER_ENABLED
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -30,7 +29,6 @@
import com.android.systemui.qs.SettingObserver
import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
import com.android.systemui.res.R
-import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.util.settings.GlobalSettings
@@ -61,10 +59,10 @@
@Background private val bgHandler: Handler,
@Background private val bgDispatcher: CoroutineDispatcher,
private val userManager: UserManager,
- private val userTracker: UserTracker,
private val userSwitcherController: UserSwitcherController,
private val userInfoController: UserInfoController,
private val globalSetting: GlobalSettings,
+ private val userRepository: UserRepository,
) : UserSwitcherRepository {
private val showUserSwitcherForSingleUser =
context.resources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)
@@ -80,7 +78,7 @@
globalSetting,
bgHandler,
USER_SWITCHER_ENABLED,
- userTracker.userId,
+ userRepository.getSelectedUserInfo().id,
) {
override fun handleValueChanged(value: Int, observedChange: Boolean) {
if (observedChange) {
@@ -147,7 +145,7 @@
private suspend fun isGuestUser(): Boolean {
return withContext(bgDispatcher) {
- userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
+ userManager.isGuestUser(userRepository.getSelectedUserInfo().id)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
new file mode 100644
index 0000000..0e693d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
@@ -0,0 +1,38 @@
+package com.android.systemui.user.domain.interactor
+
+import android.annotation.UserIdInt
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+
+/** Encapsulates business logic to interact the selected user */
+@SysUISingleton
+class SelectedUserInteractor
+@Inject
+constructor(
+ private val repository: UserRepository,
+ private val flags: FeatureFlagsClassic,
+) {
+
+ /**
+ * Returns the ID of the currently-selected user.
+ *
+ * @param bypassFlag this will ignore the feature flag and get the data from the repository
+ * instead. This is used for refactored methods that were previously pointing to `userTracker`
+ * and therefore should not be routed back to KeyguardUpdateMonitor when flag is disabled.
+ * KeyguardUpdateMonitor.getCurrentUser() is deprecated and will be removed soon (together
+ * with this flag).
+ */
+ @UserIdInt
+ @JvmOverloads
+ fun getSelectedUserId(bypassFlag: Boolean = false): Int {
+ if (bypassFlag || flags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+ return repository.getSelectedUserInfo().id
+ } else {
+ return KeyguardUpdateMonitor.getCurrentUser()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index dbc3bf3..e0d205f 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -83,9 +83,9 @@
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
-/** Encapsulates business logic to interact with user data and systems. */
+/** Encapsulates business logic to for the user switcher. */
@SysUISingleton
-class UserInteractor
+class UserSwitcherInteractor
@Inject
constructor(
@Application private val applicationContext: Context,
@@ -383,10 +383,6 @@
pw.println("isGuestUserAutoCreated=$isGuestUserAutoCreated")
}
- fun onDeviceBootCompleted() {
- guestUserInteractor.onDeviceBootCompleted()
- }
-
/** Switches to the user or executes the action represented by the given record. */
fun onRecordSelected(
record: UserRecord,
@@ -535,12 +531,6 @@
}
}
- /** Returns the ID of the currently-selected user. */
- @UserIdInt
- fun getSelectedUserId(): Int {
- return repository.getSelectedUserInfo().id
- }
-
private fun showDialog(request: ShowDialogRequestModel) {
_dialogShowRequests.value = request
}
@@ -664,7 +654,6 @@
// Connect to the new secondary user's service (purely to ensure that a persistent
// SystemUI application is created for that user)
-
if (userId != Process.myUserHandle().identifier) {
applicationContext.startServiceAsUser(
intent,
@@ -826,6 +815,6 @@
}
companion object {
- private const val TAG = "UserInteractor"
+ private const val TAG = "UserSwitcherInteractor"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index 0930cb8..922dc05 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -33,7 +33,7 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.tiles.UserDetailView
import com.android.systemui.user.UserSwitchFullscreenDialog
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
import com.android.systemui.user.domain.model.ShowDialogRequestModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import dagger.Lazy
@@ -53,7 +53,7 @@
private val falsingManager: Lazy<FalsingManager>,
private val broadcastSender: Lazy<BroadcastSender>,
private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>,
- private val interactor: Lazy<UserInteractor>,
+ private val interactor: Lazy<UserSwitcherInteractor>,
private val userDetailAdapterProvider: Provider<UserDetailView.Adapter>,
private val eventLogger: Lazy<UiEventLogger>,
private val activityStarter: Lazy<ActivityStarter>,
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
index 78edad7..2c425b19 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
@@ -17,12 +17,10 @@
package com.android.systemui.user.ui.viewmodel
-import android.content.Context
import android.graphics.drawable.Drawable
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Text
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -33,8 +31,7 @@
class StatusBarUserChipViewModel
@Inject
constructor(
- @Application private val context: Context,
- interactor: UserInteractor,
+ interactor: UserSwitcherInteractor,
) {
/** Whether the status bar chip ui should be available */
val chipEnabled: Boolean = interactor.isStatusBarUserChipEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 20f0fa8c..4089889 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -20,9 +20,8 @@
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.ui.drawable.CircularDrawable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.res.R
import com.android.systemui.user.domain.interactor.GuestUserInteractor
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.shared.model.UserModel
@@ -38,17 +37,17 @@
class UserSwitcherViewModel
@Inject
constructor(
- private val userInteractor: UserInteractor,
+ private val userSwitcherInteractor: UserSwitcherInteractor,
private val guestUserInteractor: GuestUserInteractor,
) {
/** The currently selected user. */
val selectedUser: Flow<UserViewModel> =
- userInteractor.selectedUser.map { user -> toViewModel(user) }
+ userSwitcherInteractor.selectedUser.map { user -> toViewModel(user) }
/** On-device users. */
val users: Flow<List<UserViewModel>> =
- userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
+ userSwitcherInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
/** The maximum number of columns that the user selection grid should use. */
val maximumUserColumns: Flow<Int> = users.map { getMaxUserSwitcherItemColumns(it.size) }
@@ -61,7 +60,9 @@
val isMenuVisible: Flow<Boolean> = _isMenuVisible
/** The user action menu. */
val menu: Flow<List<UserActionViewModel>> =
- userInteractor.actions.map { actions -> actions.map { action -> toViewModel(action) } }
+ userSwitcherInteractor.actions.map { actions ->
+ actions.map { action -> toViewModel(action) }
+ }
/** Whether the button to open the user action menu is visible. */
val isOpenMenuButtonVisible: Flow<Boolean> = menu.map { it.isNotEmpty() }
@@ -175,7 +176,7 @@
isTablet = true,
),
onClicked = {
- userInteractor.executeAction(action = model)
+ userSwitcherInteractor.executeAction(action = model)
// We don't finish because we want to show a dialog over the full-screen UI and
// that dialog can be dismissed in case the user changes their mind and decides not
// to add a user.
@@ -195,7 +196,7 @@
null
} else {
{
- userInteractor.selectUser(model.id)
+ userSwitcherInteractor.selectUser(model.id)
userSwitched.value = true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index 83ff789..b3834f5 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -269,3 +269,108 @@
crossinline getValue: () -> T,
): StateFlow<T> =
changedSignals.map { getValue() }.stateIn(this, SharingStarted.Eagerly, getValue())
+
+inline fun <T1, T2, T3, T4, T5, T6, R> combine(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R
+): Flow<R> {
+ return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) {
+ args: Array<*> ->
+ @Suppress("UNCHECKED_CAST")
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6
+ )
+ }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ flow7: Flow<T7>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
+): Flow<R> {
+ return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) {
+ args: Array<*> ->
+ @Suppress("UNCHECKED_CAST")
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6,
+ args[6] as T7
+ )
+ }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ flow7: Flow<T7>,
+ flow8: Flow<T8>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R
+): Flow<R> {
+ return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) {
+ args: Array<*> ->
+ @Suppress("UNCHECKED_CAST")
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6,
+ args[6] as T7,
+ args[7] as T8
+ )
+ }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> combine(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ flow7: Flow<T7>,
+ flow8: Flow<T8>,
+ flow9: Flow<T9>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9) -> R
+): Flow<R> {
+ return kotlinx.coroutines.flow.combine(
+ flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8, flow9
+ ) { args: Array<*> ->
+ @Suppress("UNCHECKED_CAST")
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6,
+ args[6] as T7,
+ args[6] as T8,
+ args[6] as T9,
+ )
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index 9b06a37..d566725 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -191,7 +191,7 @@
}
@Override
- protected boolean initDataInjectionImpl(boolean enable) {
+ protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {
throw new UnsupportedOperationException("not implemented");
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java
index 2d1e622..50d1547 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Events.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java
@@ -234,6 +234,10 @@
VOLUME_DIALOG_SLIDER(150),
@UiEvent(doc = "The audio stream was set to silent via slider")
VOLUME_DIALOG_SLIDER_TO_ZERO(151),
+ @UiEvent(doc = "ODI captions was clicked")
+ VOLUME_DIALOG_ODI_CAPTIONS_CLICKED(1503),
+ @UiEvent(doc = "ODI captions tooltip dismiss was clicked")
+ VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED(1504),
@UiEvent(doc = "The audio volume was adjusted to silent via key")
VOLUME_KEY_TO_ZERO(152),
@UiEvent(doc = "The audio volume was adjusted to non-silent via key")
@@ -362,6 +366,10 @@
if (tag == EVENT_SETTINGS_CLICK) {
sLegacyLogger.action(MetricsEvent.ACTION_VOLUME_SETTINGS);
sUiEventLogger.log(VolumeDialogEvent.VOLUME_DIALOG_SETTINGS_CLICK);
+ } else if (tag == EVENT_ODI_CAPTIONS_CLICK) {
+ sUiEventLogger.log(VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_CLICKED);
+ } else if (tag == EVENT_ODI_CAPTIONS_TOOLTIP_CLICK) {
+ sUiEventLogger.log(VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED);
}
return sb.toString();
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 929b91c..0ff308e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -120,7 +120,6 @@
import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
import com.android.systemui.Prefs;
-import com.android.systemui.res.R;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -129,6 +128,7 @@
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.plugins.VolumeDialogController.StreamState;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -287,7 +287,7 @@
private boolean mIsAnimatingDismiss = false;
private boolean mHasSeenODICaptionsTooltip;
private ViewStub mODICaptionsTooltipViewStub;
- private View mODICaptionsTooltipView = null;
+ @VisibleForTesting View mODICaptionsTooltipView = null;
private final boolean mUseBackgroundBlur;
private Consumer<Boolean> mCrossWindowBlurEnabledListener;
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 9dca013..aea3030 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -109,6 +109,7 @@
private WallpaperManager mWallpaperManager;
private final WallpaperLocalColorExtractor mWallpaperLocalColorExtractor;
private SurfaceHolder mSurfaceHolder;
+ private boolean mDrawn = false;
@VisibleForTesting
static final int MIN_SURFACE_WIDTH = 128;
@VisibleForTesting
@@ -239,6 +240,7 @@
private void drawFrameSynchronized() {
synchronized (mLock) {
+ if (mDrawn) return;
drawFrameInternal();
}
}
@@ -276,6 +278,7 @@
Rect dest = mSurfaceHolder.getSurfaceFrame();
try {
canvas.drawBitmap(bitmap, null, dest, null);
+ mDrawn = true;
} finally {
surface.unlockCanvasAndPost(canvas);
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 897c4da..1e801ae 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -33,6 +33,7 @@
import android.graphics.Rect;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
+import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
@@ -66,6 +67,7 @@
import com.android.wm.shell.sysui.ShellInterface;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -371,6 +373,13 @@
@Override
public void dump(PrintWriter pw, String[] args) {
+ Log.d(TAG, "Dumping with args: " + String.join(", ", args));
+
+ // Strip out the SysUI "dependency" arg before sending to WMShell
+ if (args[0].equals("dependency")) {
+ args = Arrays.copyOfRange(args, 1, args.length);
+ }
+
// Handle commands if provided
if (mShell.handleCommand(args, pw)) {
return;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
index 81fef7a..b31f630a4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
@@ -35,9 +35,12 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
+import java.io.PrintWriter
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -45,26 +48,21 @@
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.io.PrintWriter
@SmallTest
class ActiveUnlockConfigTest : SysuiTestCase() {
private lateinit var secureSettings: FakeSettings
- @Mock
- private lateinit var contentResolver: ContentResolver
- @Mock
- private lateinit var handler: Handler
- @Mock
- private lateinit var dumpManager: DumpManager
- @Mock
- private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var contentResolver: ContentResolver
+ @Mock private lateinit var handler: Handler
+ @Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var mockPrintWriter: PrintWriter
- @Captor
- private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
+ @Captor private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
private lateinit var activeUnlockConfig: ActiveUnlockConfig
private var currentUser: Int = 0
@@ -73,14 +71,16 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- currentUser = KeyguardUpdateMonitor.getCurrentUser()
+ whenever(selectedUserInteractor.getSelectedUserId()).thenReturn(currentUser)
secureSettings = FakeSettings()
- activeUnlockConfig = ActiveUnlockConfig(
+ activeUnlockConfig =
+ ActiveUnlockConfig(
handler,
secureSettings,
contentResolver,
+ selectedUserInteractor,
dumpManager
- )
+ )
}
@Test
@@ -92,8 +92,9 @@
fun onWakeupSettingChanged() {
// GIVEN no active unlock settings enabled
assertFalse(
- activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)
+ activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE
+ )
)
// WHEN unlock on wake is allowed
@@ -102,16 +103,19 @@
// THEN active unlock triggers allowed on: wake, unlock-intent, and biometric failure
assertTrue(
- activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)
+ activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE
+ )
)
assertTrue(
- activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)
+ activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+ )
)
assertTrue(
- activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL)
+ activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL
+ )
)
}
@@ -119,8 +123,9 @@
fun onUnlockIntentSettingChanged() {
// GIVEN no active unlock settings enabled
assertFalse(
- activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)
+ activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+ )
)
// WHEN unlock on biometric failed is allowed
@@ -128,12 +133,21 @@
updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT))
// THEN active unlock triggers allowed on: biometric failure ONLY
- assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE))
- assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
- assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
+ assertFalse(
+ activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE
+ )
+ )
+ assertTrue(
+ activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+ )
+ )
+ assertTrue(
+ activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL
+ )
+ )
}
@Test
@@ -141,24 +155,39 @@
// GIVEN no active unlock settings enabled and triggering unlock intent on biometric
// enrollment setting is disabled (empty string is disabled, null would use the default)
secureSettings.putStringForUser(
- ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, "", currentUser)
- updateSetting(secureSettings.getUriFor(
- ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
- ))
- assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ "",
+ currentUser
+ )
+ updateSetting(
+ secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+ )
+ assertFalse(
+ activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL
+ )
+ )
// WHEN unlock on biometric failed is allowed
secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser)
updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
// THEN active unlock triggers allowed on: biometric failure ONLY
- assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE))
- assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
- assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
+ assertFalse(
+ activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE
+ )
+ )
+ assertFalse(
+ activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+ )
+ )
+ assertTrue(
+ activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL
+ )
+ )
}
@Test
@@ -168,16 +197,21 @@
updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
// WHEN face error timeout (3), allow trigger active unlock
- secureSettings.putStringForUser(
- ACTIVE_UNLOCK_ON_FACE_ERRORS, "3", currentUser)
+ secureSettings.putStringForUser(ACTIVE_UNLOCK_ON_FACE_ERRORS, "3", currentUser)
updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS))
// THEN active unlock triggers allowed on error TIMEOUT
- assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
- BiometricFaceConstants.FACE_ERROR_TIMEOUT))
+ assertTrue(
+ activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
+ BiometricFaceConstants.FACE_ERROR_TIMEOUT
+ )
+ )
- assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
- BiometricFaceConstants.FACE_ERROR_CANCELED))
+ assertFalse(
+ activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
+ BiometricFaceConstants.FACE_ERROR_CANCELED
+ )
+ )
}
@Test
@@ -189,21 +223,34 @@
// WHEN face acquiredMsg DARK_GLASSESand MOUTH_COVERING are allowed to trigger
secureSettings.putStringForUser(
ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
- "${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" +
- "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}",
- currentUser)
+ "${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" +
+ "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}",
+ currentUser
+ )
updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO))
// THEN active unlock triggers allowed on acquired messages DARK_GLASSES & MOUTH_COVERING
- assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
- BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED))
- assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
- BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED))
+ assertTrue(
+ activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+ BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED
+ )
+ )
+ assertTrue(
+ activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+ BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED
+ )
+ )
- assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
- BiometricFaceConstants.FACE_ACQUIRED_GOOD))
- assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
- BiometricFaceConstants.FACE_ACQUIRED_NOT_DETECTED))
+ assertFalse(
+ activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+ BiometricFaceConstants.FACE_ACQUIRED_GOOD
+ )
+ )
+ assertFalse(
+ activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+ BiometricFaceConstants.FACE_ACQUIRED_NOT_DETECTED
+ )
+ )
}
@Test
@@ -221,14 +268,19 @@
secureSettings.putStringForUser(
ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
- "${ActiveUnlockConfig.BiometricType.NONE.intValue}", currentUser)
- updateSetting(secureSettings.getUriFor(
- ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
- ))
+ "${ActiveUnlockConfig.BiometricType.NONE.intValue}",
+ currentUser
+ )
+ updateSetting(
+ secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+ )
// THEN active unlock triggers allowed on unlock intent
- assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
+ assertTrue(
+ activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+ )
+ )
}
@Test
@@ -245,33 +297,43 @@
// WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs
// are enrolled
secureSettings.putStringForUser(
- ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
- "${ActiveUnlockConfig.BiometricType.ANY_FACE.intValue}" +
- "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}",
- currentUser)
- updateSetting(secureSettings.getUriFor(
- ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
- ))
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ "${ActiveUnlockConfig.BiometricType.ANY_FACE.intValue}" +
+ "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}",
+ currentUser
+ )
+ updateSetting(
+ secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+ )
// THEN active unlock triggers NOT allowed on unlock intent
- assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
+ assertFalse(
+ activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+ )
+ )
// WHEN fingerprint ONLY enrolled
`when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false)
`when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true)
// THEN active unlock triggers allowed on unlock intent
- assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
+ assertTrue(
+ activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+ )
+ )
// WHEN face ONLY enrolled
`when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
`when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false)
// THEN active unlock triggers allowed on unlock intent
- assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
+ assertTrue(
+ activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+ )
+ )
}
@Test
@@ -280,7 +342,8 @@
secureSettings.putIntForUser(
ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
PowerManager.WAKE_REASON_LIFT,
- currentUser)
+ currentUser
+ )
updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
// THEN only WAKE_REASON_LIFT is considered an unlock intent
@@ -299,16 +362,18 @@
secureSettings.putStringForUser(
ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
PowerManager.WAKE_REASON_LIFT.toString() +
- "|" +
- PowerManager.WAKE_REASON_TAP.toString(),
+ "|" +
+ PowerManager.WAKE_REASON_TAP.toString(),
currentUser
)
updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
// THEN WAKE_REASON_LIFT and WAKE_REASON TAP are considered an unlock intent
for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
- if (wakeReason == PowerManager.WAKE_REASON_LIFT ||
- wakeReason == PowerManager.WAKE_REASON_TAP) {
+ if (
+ wakeReason == PowerManager.WAKE_REASON_LIFT ||
+ wakeReason == PowerManager.WAKE_REASON_TAP
+ ) {
assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
} else {
assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
@@ -316,26 +381,36 @@
}
assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_LIFT))
assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_TAP))
- assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(
- PowerManager.WAKE_REASON_UNFOLD_DEVICE))
+ assertFalse(
+ activeUnlockConfig.isWakeupConsideredUnlockIntent(
+ PowerManager.WAKE_REASON_UNFOLD_DEVICE
+ )
+ )
}
@Test
fun isWakeupConsideredUnlockIntent_emptyValues() {
// GIVEN lift and tap are considered an unlock intent
- secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, " ",
- currentUser)
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
+ " ",
+ currentUser
+ )
updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
// THEN no wake up gestures are considered an unlock intent
for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
}
- assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(
- PowerManager.WAKE_REASON_LIFT))
+ assertFalse(
+ activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_LIFT)
+ )
assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_TAP))
- assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(
- PowerManager.WAKE_REASON_UNFOLD_DEVICE))
+ assertFalse(
+ activeUnlockConfig.isWakeupConsideredUnlockIntent(
+ PowerManager.WAKE_REASON_UNFOLD_DEVICE
+ )
+ )
}
@Test
@@ -343,11 +418,12 @@
verifyRegisterSettingObserver()
// GIVEN lift is considered an unlock intent
- secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
- PowerManager.WAKE_REASON_LIFT.toString(), currentUser)
- updateSetting(secureSettings.getUriFor(
- ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD
- ))
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
+ PowerManager.WAKE_REASON_LIFT.toString(),
+ currentUser
+ )
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD))
// THEN only WAKE_REASON_LIFT is considered an unlock intent
for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
@@ -364,11 +440,12 @@
verifyRegisterSettingObserver()
// GIVEN lift and tap are considered an unlock intent
- secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
- " ", currentUser)
- updateSetting(secureSettings.getUriFor(
- ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD
- ))
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
+ " ",
+ currentUser
+ )
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD))
// THEN no wake up gestures are considered an unlock intent
for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
@@ -381,20 +458,21 @@
verifyRegisterSettingObserver()
// GIVEN lift and tap are considered an unlock intent
- secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD,
PowerManager.WAKE_REASON_LIFT.toString() +
- "|" +
- PowerManager.WAKE_REASON_TAP.toString(),
+ "|" +
+ PowerManager.WAKE_REASON_TAP.toString(),
currentUser
)
- updateSetting(secureSettings.getUriFor(
- ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD
- ))
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD))
// THEN WAKE_REASON_LIFT and WAKE_REASON TAP are considered an unlock intent
for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
- if (wakeReason == PowerManager.WAKE_REASON_LIFT ||
- wakeReason == PowerManager.WAKE_REASON_TAP) {
+ if (
+ wakeReason == PowerManager.WAKE_REASON_LIFT ||
+ wakeReason == PowerManager.WAKE_REASON_TAP
+ ) {
assertTrue(activeUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason))
} else {
assertFalse(activeUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason))
@@ -405,13 +483,16 @@
@Test
fun dump_onUnlockIntentWhenBiometricEnrolled_invalidNum_noArrayOutOfBoundsException() {
// GIVEN an invalid input (-1)
- secureSettings.putStringForUser(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
- "-1", currentUser)
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ "-1",
+ currentUser
+ )
// WHEN the setting updates
- updateSetting(secureSettings.getUriFor(
- ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
- ))
+ updateSetting(
+ secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+ )
// THEN no exception thrown
activeUnlockConfig.dump(mockPrintWriter, emptyArray())
@@ -419,12 +500,7 @@
private fun updateSetting(uri: Uri) {
verifyRegisterSettingObserver()
- settingsObserverCaptor.value.onChange(
- false,
- listOf(uri),
- 0,
- 0 /* flags */
- )
+ settingsObserverCaptor.value.onChange(false, listOf(uri), 0, 0 /* flags */)
}
private fun verifyRegisterSettingObserver() {
@@ -433,19 +509,21 @@
verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS))
verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO))
- verifyRegisterSettingObserver(secureSettings.getUriFor(
- ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
- ))
- verifyRegisterSettingObserver(secureSettings.getUriFor(
- ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS
- ))
+ verifyRegisterSettingObserver(
+ secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+ )
+ verifyRegisterSettingObserver(
+ secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)
+ )
}
private fun verifyRegisterSettingObserver(uri: Uri) {
- verify(contentResolver).registerContentObserver(
+ verify(contentResolver)
+ .registerContentObserver(
eq(uri),
eq(false),
capture(settingsObserverCaptor),
- eq(UserHandle.USER_ALL))
+ eq(UserHandle.USER_ALL)
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index d506584..cdd0eb0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -50,6 +50,7 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import org.junit.After;
import org.junit.Before;
@@ -64,7 +65,7 @@
@SmallTest
public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
- private static final int TARGET_USER_ID = KeyguardUpdateMonitor.getCurrentUser();
+ private static final int TARGET_USER_ID = 0;
private AdminSecondaryLockScreenController mTestController;
private ComponentName mComponentName;
@@ -80,12 +81,15 @@
private KeyguardSecurityCallback mKeyguardCallback;
@Mock
private KeyguardUpdateMonitor mUpdateMonitor;
+ @Mock
+ private SelectedUserInteractor mSelectedUserInteractor;
private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID);
mKeyguardSecurityContainer = spy(new KeyguardSecurityContainer(mContext));
mKeyguardSecurityContainer.setId(View.generateViewId());
@@ -106,7 +110,8 @@
new Binder())).getSurfacePackage();
mTestController = new AdminSecondaryLockScreenController.Factory(
- mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler)
+ mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler,
+ mSelectedUserInteractor)
.create(mKeyguardCallback);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 6afa525..4d8768f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -25,9 +25,11 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.log.LogBuffer
import com.android.systemui.plugins.ClockAnimations
import com.android.systemui.plugins.ClockController
@@ -47,8 +49,8 @@
import java.util.TimeZone
import java.util.concurrent.Executor
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.yield
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -90,10 +92,10 @@
@Mock private lateinit var smallClockEvents: ClockFaceEvents
@Mock private lateinit var largeClockEvents: ClockFaceEvents
@Mock private lateinit var parentView: View
- @Mock private lateinit var transitionRepository: KeyguardTransitionRepository
private lateinit var repository: FakeKeyguardRepository
@Mock private lateinit var smallLogBuffer: LogBuffer
@Mock private lateinit var largeLogBuffer: LogBuffer
+ @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
private lateinit var underTest: ClockEventController
@Before
@@ -125,17 +127,13 @@
withDeps.featureFlags.apply {
set(Flags.REGION_SAMPLING, false)
- set(Flags.DOZING_MIGRATION_1, false)
+ set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false)
set(Flags.FACE_AUTH_REFACTOR, false)
}
underTest =
ClockEventController(
withDeps.keyguardInteractor,
- KeyguardTransitionInteractorFactory.create(
- scope = TestScope().backgroundScope,
- featureFlags = withDeps.featureFlags,
- )
- .keyguardTransitionInteractor,
+ keyguardTransitionInteractor,
broadcastDispatcher,
batteryController,
keyguardUpdateMonitor,
@@ -316,6 +314,68 @@
}
@Test
+ fun listenForDozeAmountTransition_updatesClockDozeAmount() =
+ runBlocking(IMMEDIATE) {
+ val transitionStep = MutableStateFlow(TransitionStep())
+ whenever(keyguardTransitionInteractor.dozeAmountTransition).thenReturn(transitionStep)
+
+ val job = underTest.listenForDozeAmountTransition(this)
+ transitionStep.value =
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 0.4f
+ )
+ yield()
+
+ verify(animations, times(2)).doze(0.4f)
+
+ job.cancel()
+ }
+
+ @Test
+ fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() =
+ runBlocking(IMMEDIATE) {
+ val transitionStep = MutableStateFlow(TransitionStep())
+ whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD))
+ .thenReturn(transitionStep)
+
+ val job = underTest.listenForAnyStateToAodTransition(this)
+ transitionStep.value =
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ )
+ yield()
+
+ verify(animations, times(2)).doze(1f)
+
+ job.cancel()
+ }
+
+ @Test
+ fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() =
+ runBlocking(IMMEDIATE) {
+ val transitionStep = MutableStateFlow(TransitionStep())
+ whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD))
+ .thenReturn(transitionStep)
+
+ val job = underTest.listenForAnyStateToAodTransition(this)
+ transitionStep.value =
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ )
+ yield()
+
+ verify(animations, never()).doze(1f)
+
+ job.cancel()
+ }
+
+ @Test
fun unregisterListeners_validate() =
runBlocking(IMMEDIATE) {
underTest.unregisterListeners()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
index 30fed0b..c61b11a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.shade.ShadeController
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -59,6 +60,7 @@
@Mock lateinit var metricsLogger: MetricsLogger
@Mock lateinit var lockPatternUtils: LockPatternUtils
@Mock lateinit var packageManager: PackageManager
+ @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
val fakeSystemClock = FakeSystemClock()
val mainExecutor = FakeExecutor(fakeSystemClock)
val backgroundExecutor = FakeExecutor(fakeSystemClock)
@@ -79,7 +81,8 @@
metricsLogger,
lockPatternUtils,
mainExecutor,
- backgroundExecutor
+ backgroundExecutor,
+ mSelectedUserInteractor,
)
context.setMockPackageManager(packageManager)
Mockito.`when`(emergencyButton.context).thenReturn(context)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 42f65f6..7f20d9a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -37,12 +37,13 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import org.junit.Before;
import org.junit.Test;
@@ -81,6 +82,8 @@
private EmergencyButtonController mEmergencyButtonController;
private FakeFeatureFlags mFeatureFlags;
+ @Mock
+ private SelectedUserInteractor mSelectedUserInteractor;
private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
@Before
@@ -105,7 +108,7 @@
return new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector,
- mEmergencyButtonController, mFeatureFlags) {
+ mEmergencyButtonController, mFeatureFlags, mSelectedUserInteractor) {
@Override
void resetState() {
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
index 91b544b..634dac1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
@@ -25,6 +25,7 @@
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.SessionTracker
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,8 +35,8 @@
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -50,6 +51,8 @@
lateinit var sessionTracker: SessionTracker
@Mock
lateinit var sessionId: InstanceId
+ @Mock
+ lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Captor
lateinit var updateMonitorCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -65,7 +68,8 @@
keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger(
uiEventLogger,
keyguardUpdateMonitor,
- sessionTracker)
+ sessionTracker,
+ mSelectedUserInteractor)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index d8a2c5f..3fbcf6d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -24,12 +24,13 @@
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.mockito.whenever
import org.junit.Before
@@ -62,6 +63,7 @@
@Mock lateinit var mainExecutor: DelayableExecutor
@Mock lateinit var falsingCollector: FalsingCollector
@Mock lateinit var keyguardViewController: KeyguardViewController
+ @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
@Mock
private lateinit var mKeyguardMessageAreaController:
@@ -106,7 +108,8 @@
falsingCollector,
keyguardViewController,
postureController,
- fakeFeatureFlags
+ fakeFeatureFlags,
+ mSelectedUserInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index dc1618d..74c9225 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -24,15 +24,16 @@
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -75,6 +76,9 @@
KeyguardMessageAreaController.Factory
@Mock
+ private lateinit var mSelectedUserInteractor: SelectedUserInteractor
+
+ @Mock
private lateinit var mKeyguardMessageAreaController:
KeyguardMessageAreaController<BouncerKeyguardMessageArea>
@@ -108,7 +112,8 @@
mEmergencyButtonController,
mKeyguardMessageAreaControllerFactory,
mPostureController,
- fakeFeatureFlags
+ fakeFeatureFlags,
+ mSelectedUserInteractor
)
mKeyguardPatternView.onAttachedToWindow()
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 4a24e4a..d41c249 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -34,10 +34,10 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingCollectorFake;
-import com.android.systemui.classifier.SingleTapClassifier;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.res.R;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import org.junit.Before;
import org.junit.Test;
@@ -76,11 +76,11 @@
private EmergencyButtonController mEmergencyButtonController;
private FalsingCollector mFalsingCollector = new FalsingCollectorFake();
@Mock
- private SingleTapClassifier mSingleTapClassifier;
- @Mock
private View mDeleteButton;
@Mock
private View mOkButton;
+ @Mock
+ private SelectedUserInteractor mSelectedUserInteractor;
private NumPadKey[] mButtons = new NumPadKey[]{};
private KeyguardPinBasedInputViewController mKeyguardPinViewController;
@@ -108,7 +108,8 @@
mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
- mEmergencyButtonController, mFalsingCollector, featureFlags) {
+ mEmergencyButtonController, mFalsingCollector, featureFlags,
+ mSelectedUserInteractor) {
@Override
public void onResume(int reason) {
super.onResume(reason);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 9df4dd4..80d45bc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -25,15 +25,16 @@
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -84,6 +85,7 @@
@Mock private val mEmergencyButtonController: EmergencyButtonController? = null
private val falsingCollector: FalsingCollector = FalsingCollectorFake()
@Mock lateinit var postureController: DevicePostureController
+ @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Mock lateinit var featureFlags: FeatureFlags
@Mock lateinit var passwordTextView: PasswordTextView
@@ -133,7 +135,8 @@
mEmergencyButtonController,
falsingCollector,
postureController,
- featureFlags
+ featureFlags,
+ mSelectedUserInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 20d4eb9..fda4133 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -63,7 +63,7 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argThat
@@ -136,7 +136,7 @@
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
@Mock private lateinit var audioManager: AudioManager
- @Mock private lateinit var userInteractor: UserInteractor
+ @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock private lateinit var postureController: DevicePostureController
@@ -215,10 +215,10 @@
null,
keyguardViewController,
postureController,
- featureFlags
+ featureFlags,
+ mSelectedUserInteractor,
)
- whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
sceneTestUtils = SceneTestUtils(this)
sceneInteractor = sceneTestUtils.sceneInteractor()
keyguardTransitionInteractor =
@@ -260,7 +260,7 @@
mock(),
mock(),
{ JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
- userInteractor,
+ mSelectedUserInteractor,
deviceProvisionedController,
faceAuthAccessibilityDelegate,
keyguardTransitionInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 4290b8b..94c3bde 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -24,11 +24,12 @@
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import org.junit.Before
import org.junit.Test
@@ -58,6 +59,7 @@
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var emergencyButtonController: EmergencyButtonController
+ @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Mock
private lateinit var keyguardMessageAreaController:
KeyguardMessageAreaController<BouncerKeyguardMessageArea>
@@ -90,6 +92,7 @@
falsingCollector,
emergencyButtonController,
fakeFeatureFlags,
+ mSelectedUserInteractor
)
underTest.init()
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index 31ee641..7b1f302 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -24,11 +24,12 @@
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import org.junit.Before
import org.junit.Test
@@ -54,6 +55,7 @@
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var emergencyButtonController: EmergencyButtonController
+ @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Mock
private lateinit var keyguardMessageAreaController:
KeyguardMessageAreaController<BouncerKeyguardMessageArea>
@@ -89,6 +91,7 @@
falsingCollector,
emergencyButtonController,
fakeFeatureFlags,
+ mSelectedUserInteractor,
)
underTest.init()
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 3b8e02f..22c75d8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -31,6 +33,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.power.data.repository.FakePowerRepository;
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
import com.android.systemui.statusbar.notification.AnimatableProperty;
@@ -60,6 +63,7 @@
@Mock protected FeatureFlags mFeatureFlags;
@Mock protected InteractionJankMonitor mInteractionJankMonitor;
@Mock protected ViewTreeObserver mViewTreeObserver;
+ @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Mock protected DumpManager mDumpManager;
protected FakeKeyguardRepository mFakeKeyguardRepository;
protected FakePowerRepository mFakePowerRepository;
@@ -90,6 +94,7 @@
mFeatureFlags,
mInteractionJankMonitor,
deps.getKeyguardInteractor(),
+ mKeyguardTransitionInteractor,
mDumpManager,
PowerInteractorFactory.create(
mFakePowerRepository
@@ -105,8 +110,8 @@
};
when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
-
when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch);
+ when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow());
}
protected void givenViewAttached() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 47be236..aabdcb7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -38,7 +38,6 @@
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
-import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
@@ -82,7 +81,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
@@ -157,8 +155,8 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Assert;
@@ -240,8 +238,6 @@
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
- private SecureSettings mSecureSettings;
- @Mock
private TelephonyManager mTelephonyManager;
@Mock
private SensorPrivacyManager mSensorPrivacyManager;
@@ -278,18 +274,17 @@
@Mock
private UsbPortStatus mUsbPortStatus;
@Mock
- private Uri mURI;
- @Mock
private TaskStackChangeListeners mTaskStackChangeListeners;
@Mock
private IActivityTaskManager mActivityTaskManager;
@Mock
private WakefulnessLifecycle mWakefulness;
+ @Mock
+ private SelectedUserInteractor mSelectedUserInteractor;
private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
private final int mCurrentUserId = 100;
- private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
@Captor
private ArgumentCaptor<IBiometricEnabledOnKeyguardCallback>
@@ -337,8 +332,8 @@
.startMocking();
ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
.when(SubscriptionManager::getDefaultSubscriptionId);
- KeyguardUpdateMonitor.setCurrentUser(mCurrentUserId);
- when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
+ when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mCurrentUserId);
+ when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mCurrentUserId);
mContext.getOrCreateTestableResources().addOverride(
com.android.systemui.res.R.integer.config_face_auth_supported_posture,
@@ -351,13 +346,11 @@
mContext.getOrCreateTestableResources().addOverride(com.android.systemui.res
.R.array.config_fingerprint_listen_on_occluding_activity_packages,
- new String[]{ PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY });
+ new String[]{PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY});
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
- when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
-
final ContentResolver contentResolver = mContext.getContentResolver();
ExtendedMockito.spyOn(contentResolver);
doNothing().when(contentResolver)
@@ -1005,11 +998,14 @@
@Test
public void trustAgentHasTrust() {
// WHEN user has trust
- mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null);
+ mKeyguardUpdateMonitor.onTrustChanged(true, true,
+ mSelectedUserInteractor.getSelectedUserId(), 0, null);
// THEN user is considered as "having trust" and bouncer can be skipped
- Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
- Assert.assertTrue(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()));
+ Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
+ mSelectedUserInteractor.getSelectedUserId()));
+ Assert.assertTrue(mKeyguardUpdateMonitor.getUserCanSkipBouncer(
+ mSelectedUserInteractor.getSelectedUserId()));
}
@Test
@@ -1027,15 +1023,19 @@
@Test
public void trustAgentHasTrust_fingerprintLockout() {
// GIVEN user has trust
- mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null);
- Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
+ mKeyguardUpdateMonitor.onTrustChanged(true, true,
+ mSelectedUserInteractor.getSelectedUserId(), 0, null);
+ Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
+ mSelectedUserInteractor.getSelectedUserId()));
// WHEN fingerprint is lock out
fingerprintErrorTemporaryLockOut();
// THEN user is NOT considered as "having trust" and bouncer cannot be skipped
- Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
- Assert.assertFalse(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()));
+ Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(
+ mSelectedUserInteractor.getSelectedUserId()));
+ Assert.assertFalse(mKeyguardUpdateMonitor.getUserCanSkipBouncer(
+ mSelectedUserInteractor.getSelectedUserId()));
}
@Test
@@ -1217,7 +1217,7 @@
mTestableLooper.processAllMessages();
lockscreenBypassIsAllowed();
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
- KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
+ mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */,
new ArrayList<>());
keyguardIsVisible();
verifyFaceAuthenticateCall();
@@ -1249,7 +1249,7 @@
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
- KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
+ mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */, new ArrayList<>());
keyguardIsVisible();
verifyFaceAuthenticateNeverCalled();
}
@@ -1287,7 +1287,7 @@
public void testOnFaceAuthenticated_skipsFaceWhenAuthenticated() {
// test whether face will be skipped if authenticated, so the value of isClass3Biometric
// doesn't matter here
- mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(),
+ mKeyguardUpdateMonitor.onFaceAuthenticated(mSelectedUserInteractor.getSelectedUserId(),
true /* isClass3Biometric */);
setKeyguardBouncerVisibility(true);
mTestableLooper.processAllMessages();
@@ -1333,7 +1333,7 @@
@Test
public void testGetUserCanSkipBouncer_whenFace() {
- int user = KeyguardUpdateMonitor.getCurrentUser();
+ int user = mSelectedUserInteractor.getSelectedUserId();
mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isClass3Biometric */);
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
}
@@ -1342,14 +1342,14 @@
public void testGetUserCanSkipBouncer_whenFace_nonStrongAndDisallowed() {
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */))
.thenReturn(false);
- int user = KeyguardUpdateMonitor.getCurrentUser();
+ int user = mSelectedUserInteractor.getSelectedUserId();
mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isClass3Biometric */);
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
}
@Test
public void testGetUserCanSkipBouncer_whenFingerprint() {
- int user = KeyguardUpdateMonitor.getCurrentUser();
+ int user = mSelectedUserInteractor.getSelectedUserId();
mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isClass3Biometric */);
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
}
@@ -1358,7 +1358,7 @@
public void testGetUserCanSkipBouncer_whenFingerprint_nonStrongAndDisallowed() {
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */))
.thenReturn(false);
- int user = KeyguardUpdateMonitor.getCurrentUser();
+ int user = mSelectedUserInteractor.getSelectedUserId();
mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isClass3Biometric */);
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
}
@@ -1372,7 +1372,8 @@
assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(1);
assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(1);
- mKeyguardUpdateMonitor.handleUserSwitching(10 /* user */, () -> {});
+ mKeyguardUpdateMonitor.handleUserSwitching(10 /* user */, () -> {
+ });
assertThat(mKeyguardUpdateMonitor.mUserFingerprintAuthenticated.size()).isEqualTo(0);
assertThat(mKeyguardUpdateMonitor.mUserFaceAuthenticated.size()).isEqualTo(0);
}
@@ -1446,7 +1447,7 @@
@Test
public void testGetUserCanSkipBouncer_whenTrust() {
- int user = KeyguardUpdateMonitor.getCurrentUser();
+ int user = mSelectedUserInteractor.getSelectedUserId();
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
user, 0 /* flags */, new ArrayList<>());
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
@@ -1493,7 +1494,8 @@
@Test
public void testIsUserUnlocked() {
// mUserManager will report the user as unlocked on @Before
- assertThat(mKeyguardUpdateMonitor.isUserUnlocked(KeyguardUpdateMonitor.getCurrentUser()))
+ assertThat(
+ mKeyguardUpdateMonitor.isUserUnlocked(mSelectedUserInteractor.getSelectedUserId()))
.isTrue();
// Invalid user should not be unlocked.
int randomUser = 99;
@@ -1502,7 +1504,7 @@
@Test
public void testTrustUsuallyManaged_whenTrustChanges() {
- int user = KeyguardUpdateMonitor.getCurrentUser();
+ int user = mSelectedUserInteractor.getSelectedUserId();
when(mTrustManager.isTrustUsuallyManaged(eq(user))).thenReturn(true);
mKeyguardUpdateMonitor.onTrustManagedChanged(false /* managed */, user);
assertThat(mKeyguardUpdateMonitor.isTrustUsuallyManaged(user)).isTrue();
@@ -1510,7 +1512,7 @@
@Test
public void testTrustUsuallyManaged_resetWhenUserIsRemoved() {
- int user = KeyguardUpdateMonitor.getCurrentUser();
+ int user = mSelectedUserInteractor.getSelectedUserId();
when(mTrustManager.isTrustUsuallyManaged(eq(user))).thenReturn(true);
mKeyguardUpdateMonitor.onTrustManagedChanged(false /* managed */, user);
assertThat(mKeyguardUpdateMonitor.isTrustUsuallyManaged(user)).isTrue();
@@ -1521,9 +1523,9 @@
@Test
public void testSecondaryLockscreenRequirement() {
- KeyguardUpdateMonitor.setCurrentUser(UserHandle.myUserId());
+ when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(UserHandle.myUserId());
when(mUserTracker.getUserId()).thenReturn(UserHandle.myUserId());
- int user = KeyguardUpdateMonitor.getCurrentUser();
+ int user = mSelectedUserInteractor.getSelectedUserId();
String packageName = "fake.test.package";
String cls = "FakeService";
ServiceInfo serviceInfo = new ServiceInfo();
@@ -1680,7 +1682,7 @@
mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
// WHEN user loses smart unlock trust
- when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+ when(mStrongAuthTracker.getStrongAuthForUser(mSelectedUserInteractor.getSelectedUserId()))
.thenReturn(SOME_AUTH_REQUIRED_AFTER_USER_REQUEST);
// THEN we should still listen for udfps
@@ -1724,7 +1726,7 @@
// WHEN trust is enabled (ie: via smartlock)
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
- KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
+ mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */, new ArrayList<>());
// THEN we shouldn't listen for udfps
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
@@ -1738,7 +1740,7 @@
// WHEN face authenticated
mKeyguardUpdateMonitor.onFaceAuthenticated(
- KeyguardUpdateMonitor.getCurrentUser(), false);
+ mSelectedUserInteractor.getSelectedUserId(), false);
// THEN we shouldn't listen for udfps
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
@@ -1825,7 +1827,7 @@
public void testShowTrustGrantedMessage_onTrustGranted() {
// WHEN trust is enabled (ie: via some trust agent) with a trustGranted string
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
- KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
+ mSelectedUserInteractor.getSelectedUserId(), 0 /* flags */,
Arrays.asList("Unlocked by wearable"));
// THEN the showTrustGrantedMessage should be called with the first message
@@ -2256,7 +2258,7 @@
mKeyguardUpdateMonitor.onTrustChanged(
true /* enabled */,
true /* newlyUnlocked */,
- getCurrentUser() /* userId */,
+ mSelectedUserInteractor.getSelectedUserId() /* userId */,
TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
null /* trustGrantedMessages */);
@@ -2281,7 +2283,7 @@
mKeyguardUpdateMonitor.onTrustChanged(
true /* enabled */,
true /* newlyUnlocked */,
- getCurrentUser() /* userId */,
+ mSelectedUserInteractor.getSelectedUserId() /* userId */,
TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
null /* trustGrantedMessages */);
@@ -2333,7 +2335,7 @@
mKeyguardUpdateMonitor.onTrustChanged(
true /* enabled */,
true /* newlyUnlocked */,
- getCurrentUser() /* userId */,
+ mSelectedUserInteractor.getSelectedUserId() /* userId */,
TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD
| TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
null /* trustGrantedMessages */);
@@ -2362,7 +2364,7 @@
mKeyguardUpdateMonitor.onTrustChanged(
true /* enabled */,
true /* newlyUnlocked */,
- getCurrentUser() /* userId, not the current userId */,
+ mSelectedUserInteractor.getSelectedUserId() /* userId, not the current userId */,
TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER /* flags */,
null /* trustGrantedMessages */);
@@ -2388,7 +2390,7 @@
mKeyguardUpdateMonitor.onTrustChanged(
true /* enabled */,
true /* newlyUnlocked */,
- getCurrentUser() /* userId, not the current userId */,
+ mSelectedUserInteractor.getSelectedUserId() /* userId, not the current userId */,
TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER
| TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
null /* trustGrantedMessages */);
@@ -2423,7 +2425,8 @@
// WHEN strong auth changes and device is in user lockdown
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
userDeviceLockDown();
- mKeyguardUpdateMonitor.notifyStrongAuthAllowedChanged(getCurrentUser());
+ mKeyguardUpdateMonitor.notifyStrongAuthAllowedChanged(
+ mSelectedUserInteractor.getSelectedUserId());
mTestableLooper.processAllMessages();
// THEN face and fingerprint listening are cancelled
@@ -2451,7 +2454,8 @@
// WHEN non-strong biometric allowed changes
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
- mKeyguardUpdateMonitor.notifyNonStrongBiometricAllowedChanged(getCurrentUser());
+ mKeyguardUpdateMonitor.notifyNonStrongBiometricAllowedChanged(
+ mSelectedUserInteractor.getSelectedUserId());
mTestableLooper.processAllMessages();
// THEN face and fingerprint listening are cancelled
@@ -2516,13 +2520,15 @@
keyguardIsVisible();
keyguardNotGoingAway();
statusBarShadeIsNotLocked();
- when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+ when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+ true);
// WHEN the assistant is visible
mKeyguardUpdateMonitor.setAssistantVisible(true);
// THEN request unlock with keyguard dismissal
- verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ verify(mTrustManager).reportUserRequestedUnlock(
+ eq(mSelectedUserInteractor.getSelectedUserId()),
eq(true));
}
@@ -2531,7 +2537,8 @@
throws RemoteException {
// GIVEN shouldTriggerActiveUnlock
bouncerFullyVisible();
- when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+ when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+ true);
// GIVEN active unlock triggers on biometric failures
when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2542,7 +2549,8 @@
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback.onAuthenticationFailed();
// ALWAYS request unlock with a keyguard dismissal
- verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ verify(mTrustManager).reportUserRequestedUnlock(
+ eq(mSelectedUserInteractor.getSelectedUserId()),
eq(true));
}
@@ -2554,7 +2562,8 @@
keyguardIsVisible();
keyguardNotGoingAway();
statusBarShadeIsNotLocked();
- when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+ when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+ true);
// GIVEN active unlock triggers on biometric failures
when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2566,7 +2575,8 @@
mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
// THEN request unlock with NO keyguard dismissal
- verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ verify(mTrustManager).reportUserRequestedUnlock(
+ eq(mSelectedUserInteractor.getSelectedUserId()),
eq(false));
}
@@ -2578,7 +2588,8 @@
keyguardIsVisible();
keyguardNotGoingAway();
statusBarShadeIsNotLocked();
- when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+ when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+ true);
// GIVEN active unlock triggers on biometric failures
when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2590,7 +2601,8 @@
mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
// THEN request unlock with a keyguard dismissal
- verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ verify(mTrustManager).reportUserRequestedUnlock(
+ eq(mSelectedUserInteractor.getSelectedUserId()),
eq(true));
}
@@ -2600,7 +2612,8 @@
// GIVEN shouldTriggerActiveUnlock
when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
lockscreenBypassIsNotAllowed();
- when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+ when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+ true);
// GIVEN active unlock triggers on biometric failures
when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2612,7 +2625,8 @@
mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
// THEN request unlock with a keyguard dismissal
- verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ verify(mTrustManager).reportUserRequestedUnlock(
+ eq(mSelectedUserInteractor.getSelectedUserId()),
eq(true));
}
@@ -2701,7 +2715,8 @@
throws RemoteException {
// GIVEN shouldTriggerActiveUnlock
keyguardIsVisible();
- when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+ when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+ true);
// GIVEN active unlock triggers on wakeup
when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2717,7 +2732,8 @@
mTestableLooper.processAllMessages();
// THEN request unlock with a keyguard dismissal
- verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ verify(mTrustManager).reportUserRequestedUnlock(
+ eq(mSelectedUserInteractor.getSelectedUserId()),
eq(true));
}
@@ -2726,7 +2742,8 @@
throws RemoteException {
// GIVEN shouldTriggerActiveUnlock on wake from UNFOLD_DEVICE
keyguardIsVisible();
- when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+ when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+ true);
// GIVEN active unlock triggers on wakeup
when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2742,7 +2759,8 @@
mTestableLooper.processAllMessages();
// THEN request unlock WITHOUT a keyguard dismissal
- verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ verify(mTrustManager).reportUserRequestedUnlock(
+ eq(mSelectedUserInteractor.getSelectedUserId()),
eq(false));
}
@@ -2751,7 +2769,8 @@
throws RemoteException {
// GIVEN shouldTriggerActiveUnlock
keyguardIsVisible();
- when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+ when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+ true);
// GIVEN active unlock triggers on wakeup
when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2767,7 +2786,8 @@
mTestableLooper.processAllMessages();
// THEN request unlock with a keyguard dismissal
- verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ verify(mTrustManager).reportUserRequestedUnlock(
+ eq(mSelectedUserInteractor.getSelectedUserId()),
eq(true));
}
@@ -2777,7 +2797,8 @@
throws RemoteException {
// GIVEN shouldTriggerActiveUnlock on wake from UNFOLD_DEVICE
keyguardIsVisible();
- when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+ when(mLockPatternUtils.isSecure(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+ true);
// GIVEN active unlock triggers on wakeup
when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -2793,7 +2814,8 @@
mTestableLooper.processAllMessages();
// THEN request unlock WITHOUT a keyguard dismissal
- verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ verify(mTrustManager).reportUserRequestedUnlock(
+ eq(mSelectedUserInteractor.getSelectedUserId()),
eq(false));
}
@@ -2857,27 +2879,29 @@
assertThat(captor.getValue().getWakeReason())
.isEqualTo(PowerManager.WAKE_REASON_POWER_BUTTON);
}
+
@Test
public void testFingerprintSensorProperties() throws RemoteException {
mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
new ArrayList<>());
assertThat(mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(
- KeyguardUpdateMonitor.getCurrentUser())).isFalse();
+ mSelectedUserInteractor.getSelectedUserId())).isFalse();
mFingerprintAuthenticatorsRegisteredCallback
.onAllAuthenticatorsRegistered(mFingerprintSensorProperties);
verifyFingerprintAuthenticateCall();
assertThat(mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(
- KeyguardUpdateMonitor.getCurrentUser())).isTrue();
+ mSelectedUserInteractor.getSelectedUserId())).isTrue();
}
+
@Test
public void testFaceSensorProperties() throws RemoteException {
mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(new ArrayList<>());
assertThat(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(
- KeyguardUpdateMonitor.getCurrentUser())).isFalse();
+ mSelectedUserInteractor.getSelectedUserId())).isFalse();
mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
biometricsEnabledForCurrentUser();
@@ -2885,7 +2909,7 @@
verifyFaceAuthenticateNeverCalled();
verifyFaceDetectNeverCalled();
assertThat(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(
- KeyguardUpdateMonitor.getCurrentUser())).isTrue();
+ mSelectedUserInteractor.getSelectedUserId())).isTrue();
}
@Test
@@ -2917,13 +2941,13 @@
mKeyguardUpdateMonitor.onTrustChanged(
true /* enabled */,
true /* newlyUnlocked */,
- getCurrentUser() /* userId */,
+ mSelectedUserInteractor.getSelectedUserId() /* userId */,
TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
null /* trustGrantedMessages */);
// THEN onTrustChanged is called FIRST
final InOrder inOrder = Mockito.inOrder(callback);
- inOrder.verify(callback).onTrustChanged(eq(getCurrentUser()));
+ inOrder.verify(callback).onTrustChanged(eq(mSelectedUserInteractor.getSelectedUserId()));
// AND THEN onTrustGrantedForCurrentUser callback called
inOrder.verify(callback).onTrustGrantedForCurrentUser(
@@ -3144,7 +3168,8 @@
private void mockCanBypassLockscreen(boolean canBypass) {
// force update the isFaceEnrolled cache:
- mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(getCurrentUser());
+ mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(
+ mSelectedUserInteractor.getSelectedUserId());
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
when(mKeyguardBypassController.canBypass()).thenReturn(canBypass);
@@ -3235,22 +3260,23 @@
private void biometricsNotDisabledThroughDevicePolicyManager() {
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null,
- KeyguardUpdateMonitor.getCurrentUser())).thenReturn(0);
+ mSelectedUserInteractor.getSelectedUserId())).thenReturn(0);
}
private void biometricsEnabledForCurrentUser() throws RemoteException {
- mBiometricEnabledOnKeyguardCallback.onChanged(true, KeyguardUpdateMonitor.getCurrentUser());
+ mBiometricEnabledOnKeyguardCallback.onChanged(true,
+ mSelectedUserInteractor.getSelectedUserId());
}
private void biometricsDisabledForCurrentUser() throws RemoteException {
mBiometricEnabledOnKeyguardCallback.onChanged(
false,
- KeyguardUpdateMonitor.getCurrentUser()
+ mSelectedUserInteractor.getSelectedUserId()
);
}
private void primaryAuthRequiredEncrypted() {
- when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+ when(mStrongAuthTracker.getStrongAuthForUser(mSelectedUserInteractor.getSelectedUserId()))
.thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
}
@@ -3261,7 +3287,7 @@
}
private void primaryAuthNotRequiredByStrongAuthTracker() {
- when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+ when(mStrongAuthTracker.getStrongAuthForUser(mSelectedUserInteractor.getSelectedUserId()))
.thenReturn(0);
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
}
@@ -3270,7 +3296,7 @@
mKeyguardUpdateMonitor.onTrustChanged(
false,
false,
- KeyguardUpdateMonitor.getCurrentUser(),
+ mSelectedUserInteractor.getSelectedUserId(),
-1,
new ArrayList<>()
);
@@ -3435,7 +3461,7 @@
protected TestableKeyguardUpdateMonitor(Context context) {
super(context, mUserTracker,
TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
- mBroadcastDispatcher, mSecureSettings, mDumpManager,
+ mBroadcastDispatcher, mDumpManager,
mBackgroundExecutor, mMainExecutor,
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager,
@@ -3447,7 +3473,7 @@
mFaceWakeUpTriggersConfig, mDevicePostureController,
Optional.of(mInteractiveToAuthProvider),
mTaskStackChangeListeners, mActivityTaskManager, mDisplayTracker,
- mWakefulness);
+ mWakefulness, mSelectedUserInteractor);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
new file mode 100644
index 0000000..b1421b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
@@ -0,0 +1,162 @@
+package com.android.systemui
+
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.RectF
+import android.hardware.camera2.CameraManager
+import android.testing.AndroidTestingRunner
+import android.util.PathParser
+import androidx.test.filters.SmallTest
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import java.util.concurrent.Executor
+import kotlin.math.roundToInt
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class CameraAvailabilityListenerTest : SysuiTestCase() {
+ companion object {
+ const val EXCLUDED_PKG = "test.excluded.package"
+ const val CAMERA_ID_FRONT = "0"
+ const val CAMERA_ID_INNER = "1"
+ const val PROTECTION_PATH_STRING_FRONT = "M 50,50 a 20,20 0 1 0 40,0 a 20,20 0 1 0 -40,0 Z"
+ const val PROTECTION_PATH_STRING_INNER = "M 40,40 a 10,10 0 1 0 20,0 a 10,10 0 1 0 -20,0 Z"
+ val PATH_RECT_FRONT = rectFromPath(pathFromString(PROTECTION_PATH_STRING_FRONT))
+ val PATH_RECT_INNER = rectFromPath(pathFromString(PROTECTION_PATH_STRING_INNER))
+
+ private fun pathFromString(pathString: String): Path {
+ val spec = pathString.trim()
+ val p: Path
+ try {
+ p = PathParser.createPathFromPathData(spec)
+ } catch (e: Throwable) {
+ throw IllegalArgumentException("Invalid protection path", e)
+ }
+
+ return p
+ }
+
+ private fun rectFromPath(path: Path): Rect {
+ val computed = RectF()
+ path.computeBounds(computed)
+ return Rect(
+ computed.left.roundToInt(),
+ computed.top.roundToInt(),
+ computed.right.roundToInt(),
+ computed.bottom.roundToInt()
+ )
+ }
+ }
+
+ @Mock private lateinit var cameraManager: CameraManager
+ @Mock
+ private lateinit var cameraTransitionCb: CameraAvailabilityListener.CameraTransitionCallback
+ private lateinit var cameraAvailabilityListener: CameraAvailabilityListener
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.string.config_cameraProtectionExcludedPackages, EXCLUDED_PKG)
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.string.config_protectedCameraId, CAMERA_ID_FRONT)
+ context
+ .getOrCreateTestableResources()
+ .addOverride(
+ R.string.config_frontBuiltInDisplayCutoutProtection,
+ PROTECTION_PATH_STRING_FRONT
+ )
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.string.config_protectedInnerCameraId, CAMERA_ID_INNER)
+ context
+ .getOrCreateTestableResources()
+ .addOverride(
+ R.string.config_innerBuiltInDisplayCutoutProtection,
+ PROTECTION_PATH_STRING_INNER
+ )
+
+ context.addMockSystemService(CameraManager::class.java, cameraManager)
+
+ cameraAvailabilityListener =
+ CameraAvailabilityListener.Factory.build(context, context.mainExecutor)
+ }
+
+ @Test
+ fun testFrontCamera() {
+ var path: Path? = null
+ var rect: Rect? = null
+ val callback =
+ object : CameraAvailabilityListener.CameraTransitionCallback {
+ override fun onApplyCameraProtection(protectionPath: Path, bounds: Rect) {
+ path = protectionPath
+ rect = bounds
+ }
+
+ override fun onHideCameraProtection() {}
+ }
+
+ cameraAvailabilityListener.addTransitionCallback(callback)
+ cameraAvailabilityListener.startListening()
+
+ val callbackCaptor = withArgCaptor {
+ verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
+ }
+
+ callbackCaptor.onCameraOpened(CAMERA_ID_FRONT, "")
+ assertNotNull(path)
+ assertEquals(PATH_RECT_FRONT, rect)
+ }
+
+ @Test
+ fun testInnerCamera() {
+ var path: Path? = null
+ var rect: Rect? = null
+ val callback =
+ object : CameraAvailabilityListener.CameraTransitionCallback {
+ override fun onApplyCameraProtection(protectionPath: Path, bounds: Rect) {
+ path = protectionPath
+ rect = bounds
+ }
+
+ override fun onHideCameraProtection() {}
+ }
+
+ cameraAvailabilityListener.addTransitionCallback(callback)
+ cameraAvailabilityListener.startListening()
+
+ val callbackCaptor = withArgCaptor {
+ verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
+ }
+
+ callbackCaptor.onCameraOpened(CAMERA_ID_INNER, "")
+ assertNotNull(path)
+ assertEquals(PATH_RECT_INNER, rect)
+ }
+
+ @Test
+ fun testExcludedPackage() {
+ cameraAvailabilityListener.addTransitionCallback(cameraTransitionCb)
+ cameraAvailabilityListener.startListening()
+
+ val callbackCaptor = withArgCaptor {
+ verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
+ }
+ callbackCaptor.onCameraOpened(CAMERA_ID_FRONT, EXCLUDED_PKG)
+
+ verify(cameraTransitionCb, never())
+ .onApplyCameraProtection(any(Path::class.java), any(Rect::class.java))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 3da7261..daa6070 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -19,6 +19,8 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -87,6 +89,7 @@
@Before
public void setUp() throws Exception {
+ setFlagDefaults(mSetFlagsRule);
MockitoAnnotations.initMocks(this);
mContextWrapper = new ContextWrapper(mContext) {
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
index fd258e3..5666435 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
+
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -56,6 +58,7 @@
@Before
public void setUp() throws Exception {
+ setFlagDefaults(mSetFlagsRule);
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
mock(SecureSettings.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 3a8bcd0..ec6ec63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
@@ -75,6 +77,7 @@
@Before
public void setUp() throws Exception {
+ setFlagDefaults(mSetFlagsRule);
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
@@ -96,6 +99,7 @@
Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
mLastIsMoveToTucked);
mEndListenerCaptor.getAllValues().clear();
+ mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
index 9b81947..e8192c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
+
import static com.google.common.truth.Truth.assertThat;
import android.content.res.Resources;
@@ -26,8 +28,8 @@
import androidx.test.filters.SmallTest;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import org.junit.Before;
import org.junit.Test;
@@ -43,6 +45,7 @@
@Before
public void setUp() throws Exception {
+ setFlagDefaults(mSetFlagsRule);
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
mMenuViewAppearance = new MenuViewAppearance(mContext, windowManager);
mMenuEduTooltipView = new MenuEduTooltipView(mContext, mMenuViewAppearance);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 5764839..62cb9a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -19,6 +19,8 @@
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
@@ -37,8 +39,8 @@
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
import androidx.test.filters.SmallTest;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
@@ -72,6 +74,7 @@
@Before
public void setUp() {
+ setFlagDefaults(mSetFlagsRule);
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 98be49f..3248753 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -18,6 +18,8 @@
import static android.view.View.OVER_SCROLL_NEVER;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -33,6 +35,7 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.filters.SmallTest;
@@ -79,6 +82,7 @@
@Before
public void setUp() throws Exception {
+ setFlagDefaults(mSetFlagsRule);
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
mock(SecureSettings.class));
@@ -213,5 +217,6 @@
@After
public void tearDown() {
mMotionEventHelper.recycleEvents();
+ mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
index 31824ec..03a4ba7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
@@ -19,6 +19,8 @@
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.systemBars;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
@@ -73,6 +75,7 @@
@Before
public void setUp() throws Exception {
+ setFlagDefaults(mSetFlagsRule);
final WindowManager wm = mContext.getSystemService(WindowManager.class);
doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
mWindowManager).getMaximumWindowMetrics();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 5bb5e01..df7d1b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -23,6 +23,7 @@
import static android.view.WindowInsets.Type.systemBars;
import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
import static com.google.common.truth.Truth.assertThat;
@@ -50,6 +51,7 @@
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
+import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -110,6 +112,7 @@
@Before
public void setUp() throws Exception {
+ setFlagDefaults(mSetFlagsRule);
final Rect mDisplayBounds = new Rect();
mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH,
DISPLAY_WINDOW_HEIGHT);
@@ -145,6 +148,7 @@
UserHandle.USER_CURRENT);
mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
+ mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);
mMenuViewLayer.onDetachedFromWindow();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 5cd0fd0..b9fd5d0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -18,6 +18,8 @@
import static android.app.UiModeManager.MODE_NIGHT_YES;
+import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
@@ -67,6 +69,7 @@
@Before
public void setUp() throws Exception {
+ setFlagDefaults(mSetFlagsRule);
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mNightMode = mUiModeManager.getNightMode();
mUiModeManager.setNightMode(MODE_NIGHT_YES);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java
new file mode 100644
index 0000000..2975549
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java
@@ -0,0 +1,35 @@
+/*
+ * 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.accessibility.utils;
+
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.systemui.Flags;
+
+public class FlagUtils {
+ /**
+ * Populates a setFlagsRule with every SystemUI a11y feature flag.
+ * This function should be updated when new flags are added.
+ *
+ * @param setFlagsRule set flags rule from the test environment.
+ */
+ public static void setFlagDefaults(SetFlagsRule setFlagsRule) {
+ setFlagDefault(setFlagsRule, Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
deleted file mode 100644
index 215d635..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
+++ /dev/null
@@ -1,101 +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.systemui.biometrics
-
-import android.content.Context
-import android.hardware.biometrics.SensorProperties
-import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintSensorProperties
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.view.ViewGroup.LayoutParams
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenEver
-import org.mockito.junit.MockitoJUnit
-
-private const val SENSOR_ID = 1
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class AuthBiometricFingerprintIconControllerTest : SysuiTestCase() {
-
- @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-
- @Mock private lateinit var iconView: LottieAnimationView
- @Mock private lateinit var iconViewOverlay: LottieAnimationView
- @Mock private lateinit var layoutParam: LayoutParams
- @Mock private lateinit var fingerprintManager: FingerprintManager
-
- private lateinit var controller: AuthBiometricFingerprintIconController
-
- @Before
- fun setUp() {
- context.addMockSystemService(Context.FINGERPRINT_SERVICE, fingerprintManager)
- whenEver(iconView.layoutParams).thenReturn(layoutParam)
- whenEver(iconViewOverlay.layoutParams).thenReturn(layoutParam)
- }
-
- @Test
- fun testIconContentDescription_SfpsDevice() {
- setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_POWER_BUTTON)
- controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
-
- assertThat(controller.getIconContentDescription(BiometricState.STATE_AUTHENTICATING))
- .isEqualTo(
- context.resources.getString(
- R.string.security_settings_sfps_enroll_find_sensor_message
- )
- )
- }
-
- @Test
- fun testIconContentDescription_NonSfpsDevice() {
- setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
- controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
-
- assertThat(controller.getIconContentDescription(BiometricState.STATE_AUTHENTICATING))
- .isEqualTo(context.resources.getString(R.string.fingerprint_dialog_touch_sensor))
- }
-
- private fun setupFingerprintSensorProperties(sensorType: Int) {
- whenEver(fingerprintManager.sensorPropertiesInternal)
- .thenReturn(
- listOf(
- FingerprintSensorPropertiesInternal(
- SENSOR_ID,
- SensorProperties.STRENGTH_STRONG,
- 5 /* maxEnrollmentsPerUser */,
- listOf() /* componentInfo */,
- sensorType,
- true /* halControlsIllumination */,
- true /* resetLockoutRequiresHardwareAuthToken */,
- listOf() /* sensorLocations */
- )
- )
- )
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index d68a313..8c26776 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -72,6 +72,7 @@
runCurrent()
// WHEN shade expands
+ shadeRepository.setLegacyShadeTracking(true)
shadeRepository.setLegacyShadeExpansion(.5f)
runCurrent()
@@ -108,6 +109,7 @@
// WHEN detector is disabled and shade opens
detector.disable()
+ shadeRepository.setLegacyShadeTracking(true)
shadeRepository.setLegacyShadeExpansion(.5f)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 9f24a9f..15633d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -30,6 +30,7 @@
internal fun fingerprintSensorPropertiesInternal(
ids: List<Int> = listOf(0),
strong: Boolean = true,
+ sensorType: Int = FingerprintSensorProperties.TYPE_REAR
): List<FingerprintSensorPropertiesInternal> {
val componentInfo =
listOf(
@@ -54,7 +55,7 @@
if (strong) SensorProperties.STRENGTH_STRONG else SensorProperties.STRENGTH_WEAK,
5 /* maxEnrollmentsPerUser */,
componentInfo,
- FingerprintSensorProperties.TYPE_REAR,
+ sensorType,
false /* resetLockoutRequiresHardwareAuthToken */
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index ebe13fe..c5f16aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -36,7 +36,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
@@ -46,12 +45,14 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -111,6 +112,7 @@
@Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@Mock private lateinit var udfpsUtils: UdfpsUtils
+ @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var udfpsKeyguardAccessibilityDelegate:
UdfpsKeyguardAccessibilityDelegate
@Mock private lateinit var udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>
@@ -163,6 +165,7 @@
isDebuggable,
udfpsKeyguardAccessibilityDelegate,
udfpsKeyguardViewModels,
+ mSelectedUserInteractor,
)
block()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index dcb5398..f32e1a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -102,6 +102,7 @@
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.concurrency.FakeExecution;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -214,6 +215,8 @@
private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
@Mock
private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
+ @Mock
+ private SelectedUserInteractor mSelectedUserInteractor;
// Capture listeners so that they can be used to send events
@Captor
@@ -326,7 +329,8 @@
mInputManager,
mock(KeyguardFaceAuthInteractor.class),
mUdfpsKeyguardAccessibilityDelegate,
- mUdfpsKeyguardViewModels
+ mUdfpsKeyguardViewModels,
+ mSelectedUserInteractor
);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index e512adc..2c4e136 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.concurrency.DelayableExecutor;
import org.junit.Before;
@@ -69,6 +70,7 @@
protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
+ protected @Mock SelectedUserInteractor mSelectedUserInteractor;
protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@@ -142,7 +144,8 @@
mFeatureFlags,
mPrimaryBouncerInteractor,
mAlternateBouncerInteractor,
- mUdfpsKeyguardAccessibilityDelegate);
+ mUdfpsKeyguardAccessibilityDelegate,
+ mSelectedUserInteractor);
return controller;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 02ee53879..97dada2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -96,6 +96,7 @@
mKeyguardUpdateMonitor,
FakeTrustRepository(),
testScope.backgroundScope,
+ mSelectedUserInteractor,
)
mAlternateBouncerInteractor =
AlternateBouncerInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
deleted file mode 100644
index 712eef1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
+++ /dev/null
@@ -1,108 +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.systemui.biometrics.domain.interactor
-
-import android.hardware.biometrics.SensorLocationInternal
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.coroutines.collectLastValue
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.junit.MockitoJUnit
-
-@SmallTest
-@RunWith(JUnit4::class)
-class SideFpsOverlayInteractorTest : SysuiTestCase() {
-
- @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
- private lateinit var testScope: TestScope
-
- private val fingerprintRepository = FakeFingerprintPropertyRepository()
-
- private lateinit var interactor: SideFpsOverlayInteractor
-
- @Before
- fun setup() {
- testScope = TestScope(StandardTestDispatcher())
- interactor = SideFpsOverlayInteractorImpl(fingerprintRepository)
- }
-
- @Test
- fun testOverlayOffsetUpdates() =
- testScope.runTest {
- fingerprintRepository.setProperties(
- sensorId = 1,
- strength = SensorStrength.STRONG,
- sensorType = FingerprintSensorType.REAR,
- sensorLocations =
- mapOf(
- "" to
- SensorLocationInternal(
- "" /* displayId */,
- 540 /* sensorLocationX */,
- 1636 /* sensorLocationY */,
- 130 /* sensorRadius */
- ),
- "display_id_1" to
- SensorLocationInternal(
- "display_id_1" /* displayId */,
- 100 /* sensorLocationX */,
- 300 /* sensorLocationY */,
- 20 /* sensorRadius */
- )
- )
- )
-
- val displayId by collectLastValue(interactor.displayId)
- val offsets by collectLastValue(interactor.overlayOffsets)
-
- // Assert offsets of empty displayId.
- assertThat(displayId).isEqualTo("")
- assertThat(offsets?.displayId).isEqualTo("")
- assertThat(offsets?.sensorLocationX).isEqualTo(540)
- assertThat(offsets?.sensorLocationY).isEqualTo(1636)
- assertThat(offsets?.sensorRadius).isEqualTo(130)
-
- // Offsets should be updated correctly.
- interactor.onDisplayChanged("display_id_1")
- assertThat(displayId).isEqualTo("display_id_1")
- assertThat(offsets?.displayId).isEqualTo("display_id_1")
- assertThat(offsets?.sensorLocationX).isEqualTo(100)
- assertThat(offsets?.sensorLocationY).isEqualTo(300)
- assertThat(offsets?.sensorRadius).isEqualTo(20)
-
- // Should return default offset when the displayId is invalid.
- interactor.onDisplayChanged("invalid_display_id")
- assertThat(displayId).isEqualTo("invalid_display_id")
- assertThat(offsets?.displayId).isEqualTo(SensorLocationInternal.DEFAULT.displayId)
- assertThat(offsets?.sensorLocationX)
- .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationX)
- assertThat(offsets?.sensorLocationY)
- .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationY)
- assertThat(offsets?.sensorRadius).isEqualTo(SensorLocationInternal.DEFAULT.sensorRadius)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
new file mode 100644
index 0000000..99501c42
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
@@ -0,0 +1,410 @@
+/*
+ * 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.biometrics.domain.interactor
+
+import android.graphics.Rect
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.display.DisplayManagerGlobal
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.view.WindowMetrics
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_0
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_180
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_270
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_90
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags.REST_TO_UNLOCK
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.junit.MockitoJUnit
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class SideFpsSensorInteractorTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+ private lateinit var testScope: TestScope
+
+ private val fingerprintRepository = FakeFingerprintPropertyRepository()
+
+ private lateinit var underTest: SideFpsSensorInteractor
+
+ @Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var displayStateInteractor: DisplayStateInteractor
+
+ private val contextDisplayInfo = DisplayInfo()
+ private val displayChangeEvent = MutableStateFlow(0)
+ private val currentRotation = MutableStateFlow(ROTATION_0)
+
+ @Before
+ fun setup() {
+ testScope = TestScope(StandardTestDispatcher())
+ mContext = spy(mContext)
+
+ val displayManager = mock(DisplayManagerGlobal::class.java)
+ val resources = mContext.resources
+ whenever(mContext.display)
+ .thenReturn(Display(displayManager, 1, contextDisplayInfo, resources))
+ whenever(displayStateInteractor.displayChanges).thenReturn(displayChangeEvent)
+ whenever(displayStateInteractor.currentRotation).thenReturn(currentRotation)
+
+ contextDisplayInfo.uniqueId = "current-display"
+
+ underTest =
+ SideFpsSensorInteractor(
+ mContext,
+ fingerprintRepository,
+ windowManager,
+ displayStateInteractor,
+ FakeFeatureFlagsClassic().apply { set(REST_TO_UNLOCK, true) }
+ )
+ }
+
+ @Test
+ fun testSfpsSensorAvailable() =
+ testScope.runTest {
+ val isAvailable by collectLastValue(underTest.isAvailable)
+
+ setupFingerprint(FingerprintSensorType.POWER_BUTTON)
+ assertThat(isAvailable).isTrue()
+
+ setupFingerprint(FingerprintSensorType.HOME_BUTTON)
+ assertThat(isAvailable).isFalse()
+
+ setupFingerprint(FingerprintSensorType.REAR)
+ assertThat(isAvailable).isFalse()
+
+ setupFingerprint(FingerprintSensorType.UDFPS_OPTICAL)
+ assertThat(isAvailable).isFalse()
+
+ setupFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
+ assertThat(isAvailable).isFalse()
+
+ setupFingerprint(FingerprintSensorType.UNKNOWN)
+ assertThat(isAvailable).isFalse()
+ }
+
+ @Test
+ fun authenticationDurationIsAvailableWhenSFPSSensorIsAvailable() =
+ testScope.runTest {
+ assertThat(collectLastValue(underTest.authenticationDuration)())
+ .isEqualTo(context.resources.getInteger(R.integer.config_restToUnlockDuration))
+ }
+
+ @Test
+ fun verticalSensorLocationIsAdjustedToScreenPositionForRotation0() =
+ testScope.runTest {
+ /*
+ (0,0) (1000,0)
+ ------------------
+ | ^^^^^ | (1000, 200)
+ | status bar || <--- start of sensor at Rotation_0
+ | || <--- end of sensor
+ | | (1000, 300)
+ | |
+ ------------------ (1000, 800)
+ */
+ setupFPLocationAndDisplaySize(
+ width = 1000,
+ height = 800,
+ rotation = ROTATION_0,
+ sensorLocationY = 200,
+ sensorWidth = 100,
+ )
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation!!.left).isEqualTo(1000)
+ assertThat(sensorLocation!!.top).isEqualTo(200)
+ assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
+ assertThat(sensorLocation!!.width).isEqualTo(100)
+ }
+
+ @Test
+ fun verticalSensorLocationIsAdjustedToScreenPositionForRotation270() =
+ testScope.runTest {
+ /*
+ (800,0) (800, 1000)
+ ---------------------
+ | | (600, 1000)
+ | < || <--- end of sensor at Rotation_270
+ | < status bar || <--- start of sensor
+ | < | (500, 1000)
+ | < |
+ (0,0) ---------------------
+ */
+ setupFPLocationAndDisplaySize(
+ width = 800,
+ height = 1000,
+ rotation = ROTATION_270,
+ sensorLocationY = 200,
+ sensorWidth = 100,
+ )
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation!!.left).isEqualTo(500)
+ assertThat(sensorLocation!!.top).isEqualTo(1000)
+ assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
+ assertThat(sensorLocation!!.width).isEqualTo(100)
+ }
+
+ @Test
+ fun verticalSensorLocationIsAdjustedToScreenPositionForRotation90() =
+ testScope.runTest {
+ /*
+ (0,0)
+ ---------------------
+ | | (200, 0)
+ | > || <--- end of sensor at Rotation_270
+ | status bar > || <--- start of sensor
+ | > | (300, 0)
+ | > |
+ (800,1000) ---------------------
+ */
+ setupFPLocationAndDisplaySize(
+ width = 800,
+ height = 1000,
+ rotation = ROTATION_90,
+ sensorLocationY = 200,
+ sensorWidth = 100,
+ )
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation!!.left).isEqualTo(200)
+ assertThat(sensorLocation!!.top).isEqualTo(0)
+ assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
+ assertThat(sensorLocation!!.width).isEqualTo(100)
+ }
+
+ @Test
+ fun verticalSensorLocationIsAdjustedToScreenPositionForRotation180() =
+ testScope.runTest {
+ /*
+
+ (1000,800) ---------------------
+ | | (0, 600)
+ | || <--- end of sensor at Rotation_270
+ | status bar || <--- start of sensor
+ | \/\/\/\/\/\/\/ | (0, 500)
+ | |
+ --------------------- (0,0)
+ */
+ setupFPLocationAndDisplaySize(
+ width = 1000,
+ height = 800,
+ rotation = ROTATION_180,
+ sensorLocationY = 200,
+ sensorWidth = 100,
+ )
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation!!.left).isEqualTo(0)
+ assertThat(sensorLocation!!.top).isEqualTo(500)
+ }
+
+ @Test
+ fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation0() =
+ testScope.runTest {
+ /*
+ (0,0) (500,0) (600,0) (1000,0)
+ ____________===_________
+ | |
+ | ^^^^^ |
+ | status bar |
+ | |
+ ------------------------ (1000, 800)
+ */
+ setupFPLocationAndDisplaySize(
+ width = 1000,
+ height = 800,
+ rotation = ROTATION_0,
+ sensorLocationX = 500,
+ sensorWidth = 100,
+ )
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation!!.left).isEqualTo(500)
+ assertThat(sensorLocation!!.top).isEqualTo(0)
+ assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+ assertThat(sensorLocation!!.width).isEqualTo(100)
+ }
+
+ @Test
+ fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation90() =
+ testScope.runTest {
+ /*
+ (0,1000) (0,500) (0,400) (0,0)
+ ____________===_________
+ | |
+ | > |
+ | status bar > |
+ | > |
+ (800, 1000) ------------------------
+ */
+ setupFPLocationAndDisplaySize(
+ width = 800,
+ height = 1000,
+ rotation = ROTATION_90,
+ sensorLocationX = 500,
+ sensorWidth = 100,
+ )
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation!!.left).isEqualTo(0)
+ assertThat(sensorLocation!!.top).isEqualTo(400)
+ assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+ assertThat(sensorLocation!!.width).isEqualTo(100)
+ }
+
+ @Test
+ fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation180() =
+ testScope.runTest {
+ /*
+ (1000, 800) (500, 800) (400, 800) (0,800)
+ ____________===_________
+ | |
+ | |
+ | status bar |
+ | \/ \/ \/ \/ \/ \/ \/ |
+ ------------------------ (0,0)
+ */
+ setupFPLocationAndDisplaySize(
+ width = 1000,
+ height = 800,
+ rotation = ROTATION_180,
+ sensorLocationX = 500,
+ sensorWidth = 100,
+ )
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation!!.left).isEqualTo(400)
+ assertThat(sensorLocation!!.top).isEqualTo(800)
+ assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+ assertThat(sensorLocation!!.width).isEqualTo(100)
+ }
+
+ @Test
+ fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation270() =
+ testScope.runTest {
+ /*
+ (800, 500) (800, 600)
+ (800, 0) ____________===_________ (800,1000)
+ | < |
+ | < |
+ | < status bar |
+ | < |
+ (0,0) ------------------------
+ */
+ setupFPLocationAndDisplaySize(
+ width = 800,
+ height = 1000,
+ rotation = ROTATION_270,
+ sensorLocationX = 500,
+ sensorWidth = 100,
+ )
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation!!.left).isEqualTo(800)
+ assertThat(sensorLocation!!.top).isEqualTo(500)
+ assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+ assertThat(sensorLocation!!.width).isEqualTo(100)
+ }
+
+ private suspend fun TestScope.setupFPLocationAndDisplaySize(
+ width: Int,
+ height: Int,
+ sensorLocationX: Int = 0,
+ sensorLocationY: Int = 0,
+ rotation: DisplayRotation,
+ sensorWidth: Int
+ ) {
+ overrideResource(R.integer.config_sfpsSensorWidth, sensorWidth)
+ setupDisplayDimensions(width, height)
+ currentRotation.value = rotation
+ setupFingerprint(x = sensorLocationX, y = sensorLocationY, displayId = "expanded_display")
+ }
+
+ private fun setupDisplayDimensions(displayWidth: Int, displayHeight: Int) {
+ whenever(windowManager.maximumWindowMetrics)
+ .thenReturn(
+ WindowMetrics(
+ Rect(0, 0, displayWidth, displayHeight),
+ mock(WindowInsets::class.java)
+ )
+ )
+ }
+
+ private suspend fun TestScope.setupFingerprint(
+ fingerprintSensorType: FingerprintSensorType = FingerprintSensorType.POWER_BUTTON,
+ x: Int = 0,
+ y: Int = 0,
+ displayId: String = "display_id_1"
+ ) {
+ contextDisplayInfo.uniqueId = displayId
+ fingerprintRepository.setProperties(
+ sensorId = 1,
+ strength = SensorStrength.STRONG,
+ sensorType = fingerprintSensorType,
+ sensorLocations =
+ mapOf(
+ "someOtherDisplayId" to
+ SensorLocationInternal(
+ "someOtherDisplayId",
+ x + 100,
+ y + 100,
+ 0,
+ ),
+ displayId to
+ SensorLocationInternal(
+ displayId,
+ x,
+ y,
+ 0,
+ )
+ )
+ )
+ // Emit a display change event, this happens whenever any display related change happens,
+ // rotation, active display changing etc, display switched off/on.
+ displayChangeEvent.emit(1)
+
+ runCurrent()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index 6b9c34b..bbf471c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -55,6 +56,7 @@
@Mock private lateinit var udfpsOverlayParams: UdfpsOverlayParams
@Mock private lateinit var overlayBounds: Rect
+ @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
private lateinit var underTest: UdfpsOverlayInteractor
@@ -104,7 +106,12 @@
}
private fun createUdpfsOverlayInteractor() {
- underTest = UdfpsOverlayInteractor(authController, testScope.backgroundScope)
+ underTest =
+ UdfpsOverlayInteractor(
+ authController,
+ selectedUserInteractor,
+ testScope.backgroundScope
+ )
testScope.runCurrent()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
deleted file mode 100644
index fd86486..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.android.systemui.biometrics.ui.viewmodel
-
-import android.content.res.Configuration
-import androidx.test.filters.SmallTest
-import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.data.repository.FakePromptRepository
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.display.data.repository.FakeDisplayRepository
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.junit.MockitoJUnit
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class PromptFingerprintIconViewModelTest : SysuiTestCase() {
-
- @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-
- @Mock private lateinit var lockPatternUtils: LockPatternUtils
-
- private lateinit var displayRepository: FakeDisplayRepository
- private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
- private lateinit var promptRepository: FakePromptRepository
- private lateinit var displayStateRepository: FakeDisplayStateRepository
-
- private val testScope = TestScope(StandardTestDispatcher())
- private val fakeExecutor = FakeExecutor(FakeSystemClock())
-
- private lateinit var promptSelectorInteractor: PromptSelectorInteractor
- private lateinit var displayStateInteractor: DisplayStateInteractor
- private lateinit var viewModel: PromptFingerprintIconViewModel
-
- @Before
- fun setup() {
- displayRepository = FakeDisplayRepository()
- fingerprintRepository = FakeFingerprintPropertyRepository()
- promptRepository = FakePromptRepository()
- displayStateRepository = FakeDisplayStateRepository()
-
- promptSelectorInteractor =
- PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
- displayStateInteractor =
- DisplayStateInteractorImpl(
- testScope.backgroundScope,
- mContext,
- fakeExecutor,
- displayStateRepository,
- displayRepository,
- )
- viewModel = PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor)
- }
-
- @Test
- fun sfpsIconUpdates_onConfigurationChanged() {
- testScope.runTest {
- runCurrent()
- configureFingerprintPropertyRepository(FingerprintSensorType.POWER_BUTTON)
- val testConfig = Configuration()
- val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
- val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
- val currentIcon = collectLastValue(viewModel.iconAsset)
-
- testConfig.smallestScreenWidthDp = folded
- viewModel.onConfigurationChanged(testConfig)
- val foldedIcon = currentIcon()
-
- testConfig.smallestScreenWidthDp = unfolded
- viewModel.onConfigurationChanged(testConfig)
- val unfoldedIcon = currentIcon()
-
- assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
- }
- }
-
- private fun configureFingerprintPropertyRepository(sensorType: FingerprintSensorType) {
- fingerprintRepository.setProperties(0, SensorStrength.STRONG, sensorType, mapOf())
- }
-}
-
-internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index ca6df40..b695a0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -16,8 +16,10 @@
package com.android.systemui.biometrics.ui.viewmodel
+import android.content.res.Configuration
import android.hardware.biometrics.PromptInfo
import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
@@ -36,12 +38,15 @@
import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.toSensorStrength
+import com.android.systemui.biometrics.shared.model.toSensorType
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
+import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -66,6 +71,7 @@
private const val USER_ID = 4
private const val CHALLENGE = 2L
+private const val DELAY = 1000L
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -88,11 +94,22 @@
private lateinit var selector: PromptSelectorInteractor
private lateinit var viewModel: PromptViewModel
+ private lateinit var iconViewModel: PromptIconViewModel
private val featureFlags = FakeFeatureFlags()
@Before
fun setup() {
fingerprintRepository = FakeFingerprintPropertyRepository()
+ testCase.fingerprint?.let {
+ fingerprintRepository.setProperties(
+ it.sensorId,
+ it.sensorStrength.toSensorStrength(),
+ it.sensorType.toSensorType(),
+ it.allLocations.associateBy { sensorLocationInternal ->
+ sensorLocationInternal.displayId
+ }
+ )
+ }
promptRepository = FakePromptRepository()
displayStateRepository = FakeDisplayStateRepository()
displayRepository = FakeDisplayRepository()
@@ -110,6 +127,7 @@
viewModel =
PromptViewModel(displayStateInteractor, selector, vibrator, mContext, featureFlags)
+ iconViewModel = viewModel.iconViewModel
featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
}
@@ -123,7 +141,6 @@
val modalities by collectLastValue(viewModel.modalities)
val message by collectLastValue(viewModel.message)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
assertThat(authenticating).isFalse()
assertThat(authenticated?.isNotAuthenticated).isTrue()
@@ -133,7 +150,6 @@
}
assertThat(message).isEqualTo(PromptMessage.Empty)
assertThat(size).isEqualTo(expectedSize)
- assertThat(legacyState).isEqualTo(BiometricState.STATE_IDLE)
val startMessage = "here we go"
viewModel.showAuthenticating(startMessage, isRetry = false)
@@ -143,7 +159,6 @@
assertThat(authenticated?.isNotAuthenticated).isTrue()
assertThat(size).isEqualTo(expectedSize)
assertButtonsVisible(negative = expectedSize != PromptSize.SMALL)
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING)
}
@Test
@@ -205,6 +220,472 @@
assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.REJECT)
}
+ @Test
+ fun start_idle_and_show_authenticating_iconUpdate() =
+ runGenericTest(doNotStart = true) {
+ val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+ val iconAsset by collectLastValue(iconViewModel.iconAsset)
+ val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+ val forceExplicitFlow = testCase.isCoex && testCase.authenticatedByFingerprint
+ if (forceExplicitFlow) {
+ viewModel.ensureFingerprintHasStarted(isDelayed = true)
+ }
+
+ val startMessage = "here we go"
+ viewModel.showAuthenticating(startMessage, isRetry = false)
+
+ if (testCase.isFingerprintOnly) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by
+ collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val expectedOverlayAsset =
+ when (currentRotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+ else -> throw Exception("invalid rotation")
+ }
+ assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ } else {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(false)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+
+ if (testCase.isFaceOnly) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+ val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark)
+
+ val expectedIconAsset =
+ if (shouldPulseAnimation!!) {
+ if (lastPulseLightToDark!!) {
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else {
+ R.drawable.face_dialog_pulse_light_to_dark
+ }
+ } else {
+ R.drawable.face_dialog_pulse_dark_to_light
+ }
+ assertThat(iconAsset).isEqualTo(expectedIconAsset)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(true)
+ }
+
+ if (testCase.isCoex) {
+ if (testCase.confirmationRequested || forceExplicitFlow) {
+ // explicit flow
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by
+ collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ // TODO: Update when SFPS co-ex is implemented
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(false)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ } else {
+ // implicit flow
+ val shouldRepeatAnimation by
+ collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+ val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark)
+
+ val expectedIconAsset =
+ if (shouldPulseAnimation!!) {
+ if (lastPulseLightToDark!!) {
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else {
+ R.drawable.face_dialog_pulse_light_to_dark
+ }
+ } else {
+ R.drawable.face_dialog_pulse_dark_to_light
+ }
+ assertThat(iconAsset).isEqualTo(expectedIconAsset)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(true)
+ }
+ }
+ }
+
+ @Test
+ fun start_authenticating_show_and_clear_error_iconUpdate() = runGenericTest {
+ val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+
+ val iconAsset by collectLastValue(iconViewModel.iconAsset)
+ val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+ val forceExplicitFlow = testCase.isCoex && testCase.authenticatedByFingerprint
+ if (forceExplicitFlow) {
+ viewModel.ensureFingerprintHasStarted(isDelayed = true)
+ }
+
+ val errorJob = launch {
+ viewModel.showTemporaryError(
+ "so sad",
+ messageAfterError = "",
+ authenticateAfterError = testCase.isFingerprintOnly || testCase.isCoex,
+ )
+ // Usually done by binder
+ iconViewModel.setPreviousIconWasError(true)
+ iconViewModel.setPreviousIconOverlayWasError(true)
+ }
+
+ if (testCase.isFingerprintOnly) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val expectedOverlayAsset =
+ when (currentRotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+ else -> throw Exception("invalid rotation")
+ }
+ assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+ assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+ } else {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+
+ // Clear error, restart authenticating
+ errorJob.join()
+
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val expectedOverlayAsset =
+ when (currentRotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
+ else -> throw Exception("invalid rotation")
+ }
+ assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+ } else {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+
+ if (testCase.isFaceOnly) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+ assertThat(shouldPulseAnimation!!).isEqualTo(false)
+ assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_error)
+ assertThat(iconContentDescriptionId).isEqualTo(R.string.keyguard_face_failed)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
+
+ // Clear error, go to idle
+ errorJob.join()
+
+ assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_error_to_idle)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_idle)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
+ }
+
+ if (testCase.isCoex) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ // TODO: Update when SFPS co-ex is implemented
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+
+ // Clear error, restart authenticating
+ errorJob.join()
+
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+ }
+
+ @Test
+ fun shows_authenticated_no_errors_no_confirmation_required_iconUpdate() = runGenericTest {
+ if (!testCase.confirmationRequested) {
+ val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+
+ val iconAsset by collectLastValue(iconViewModel.iconAsset)
+ val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+ viewModel.showAuthenticated(
+ modality = testCase.authenticatedModality,
+ dismissAfterDelay = DELAY
+ )
+
+ if (testCase.isFingerprintOnly) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by
+ collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val expectedOverlayAsset =
+ when (currentRotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
+ else -> throw Exception("invalid rotation")
+ }
+ assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+ } else {
+ val isAuthenticated by collectLastValue(viewModel.isAuthenticated)
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_success_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+
+ // If co-ex, using implicit flow (explicit flow always requires confirmation)
+ if (testCase.isFaceOnly || testCase.isCoex) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+ assertThat(shouldPulseAnimation!!).isEqualTo(false)
+ assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
+ }
+ }
+ }
+
+ @Test
+ fun shows_pending_confirmation_iconUpdate() = runGenericTest {
+ if (
+ (testCase.isFaceOnly || testCase.isCoex) &&
+ testCase.authenticatedByFace &&
+ testCase.confirmationRequested
+ ) {
+ val iconAsset by collectLastValue(iconViewModel.iconAsset)
+ val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+ viewModel.showAuthenticated(
+ modality = testCase.authenticatedModality,
+ dismissAfterDelay = DELAY
+ )
+
+ if (testCase.isFaceOnly) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+ assertThat(shouldPulseAnimation!!).isEqualTo(false)
+ assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_wink_from_dark)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
+ }
+
+ // explicit flow because confirmation requested
+ if (testCase.isCoex) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by
+ collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ // TODO: Update when SFPS co-ex is implemented
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun shows_authenticated_explicitly_confirmed_iconUpdate() = runGenericTest {
+ if (
+ (testCase.isFaceOnly || testCase.isCoex) &&
+ testCase.authenticatedByFace &&
+ testCase.confirmationRequested
+ ) {
+ val iconAsset by collectLastValue(iconViewModel.iconAsset)
+ val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+ viewModel.showAuthenticated(
+ modality = testCase.authenticatedModality,
+ dismissAfterDelay = DELAY
+ )
+
+ viewModel.confirmAuthenticated()
+
+ if (testCase.isFaceOnly) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+ assertThat(shouldPulseAnimation!!).isEqualTo(false)
+ assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
+ }
+
+ // explicit flow because confirmation requested
+ if (testCase.isCoex) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by
+ collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ // TODO: Update when SFPS co-ex is implemented
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun sfpsIconUpdates_onConfigurationChanged() = runGenericTest {
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val testConfig = Configuration()
+ val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
+ val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
+ val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+ testConfig.smallestScreenWidthDp = folded
+ iconViewModel.onConfigurationChanged(testConfig)
+ val foldedIcon = currentIcon
+
+ testConfig.smallestScreenWidthDp = unfolded
+ iconViewModel.onConfigurationChanged(testConfig)
+ val unfoldedIcon = currentIcon
+
+ assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
+ }
+ }
+
+ @Test
+ fun sfpsIconUpdates_onRotation() = runGenericTest {
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
+ val iconRotation0 = currentIcon
+
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+ val iconRotation90 = currentIcon
+
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+ val iconRotation180 = currentIcon
+
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+ val iconRotation270 = currentIcon
+
+ assertThat(iconRotation0).isEqualTo(iconRotation180)
+ assertThat(iconRotation0).isNotEqualTo(iconRotation90)
+ assertThat(iconRotation0).isNotEqualTo(iconRotation270)
+ }
+ }
+
+ @Test
+ fun sfpsIconUpdates_onRearDisplayMode() = runGenericTest {
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+ displayStateRepository.setIsInRearDisplayMode(false)
+ val iconNotRearDisplayMode = currentIcon
+
+ displayStateRepository.setIsInRearDisplayMode(true)
+ val iconRearDisplayMode = currentIcon
+
+ assertThat(iconNotRearDisplayMode).isNotEqualTo(iconRearDisplayMode)
+ }
+ }
+
private suspend fun TestScope.showAuthenticated(
authenticatedModality: BiometricModality,
expectConfirmation: Boolean,
@@ -213,7 +694,6 @@
val authenticated by collectLastValue(viewModel.isAuthenticated)
val fpStartMode by collectLastValue(viewModel.fingerprintStartMode)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val authWithSmallPrompt =
testCase.shouldStartAsImplicitFlow &&
@@ -221,14 +701,12 @@
assertThat(authenticating).isTrue()
assertThat(authenticated?.isNotAuthenticated).isTrue()
assertThat(size).isEqualTo(if (authWithSmallPrompt) PromptSize.SMALL else PromptSize.MEDIUM)
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING)
assertButtonsVisible(negative = !authWithSmallPrompt)
- val delay = 1000L
- viewModel.showAuthenticated(authenticatedModality, delay)
+ viewModel.showAuthenticated(authenticatedModality, DELAY)
assertThat(authenticated?.isAuthenticated).isTrue()
- assertThat(authenticated?.delay).isEqualTo(delay)
+ assertThat(authenticated?.delay).isEqualTo(DELAY)
assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
assertThat(size)
.isEqualTo(
@@ -238,14 +716,7 @@
PromptSize.SMALL
}
)
- assertThat(legacyState)
- .isEqualTo(
- if (expectConfirmation) {
- BiometricState.STATE_PENDING_CONFIRMATION
- } else {
- BiometricState.STATE_AUTHENTICATED
- }
- )
+
assertButtonsVisible(
cancel = expectConfirmation,
confirm = expectConfirmation,
@@ -298,7 +769,6 @@
val message by collectLastValue(viewModel.message)
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val canTryAgainNow by collectLastValue(viewModel.canTryAgainNow)
val errorJob = launch {
@@ -312,7 +782,6 @@
assertThat(size).isEqualTo(PromptSize.MEDIUM)
assertThat(message).isEqualTo(PromptMessage.Error(errorMessage))
assertThat(messageVisible).isTrue()
- assertThat(legacyState).isEqualTo(BiometricState.STATE_ERROR)
// temporary error should disappear after a delay
errorJob.join()
@@ -323,17 +792,6 @@
assertThat(message).isEqualTo(PromptMessage.Empty)
assertThat(messageVisible).isFalse()
}
- val clearIconError = !restart
- assertThat(legacyState)
- .isEqualTo(
- if (restart) {
- BiometricState.STATE_AUTHENTICATING
- } else if (clearIconError) {
- BiometricState.STATE_IDLE
- } else {
- BiometricState.STATE_HELP
- }
- )
assertThat(authenticating).isEqualTo(restart)
assertThat(authenticated?.isNotAuthenticated).isTrue()
@@ -488,7 +946,6 @@
val authenticated by collectLastValue(viewModel.isAuthenticated)
val message by collectLastValue(viewModel.message)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
@@ -506,7 +963,6 @@
assertThat(authenticating).isFalse()
assertThat(authenticated?.isAuthenticated).isTrue()
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
assertThat(canTryAgain).isFalse()
}
@@ -524,7 +980,6 @@
val authenticated by collectLastValue(viewModel.isAuthenticated)
val message by collectLastValue(viewModel.message)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
assertThat(authenticating).isFalse()
@@ -532,8 +987,6 @@
assertThat(authenticated?.isAuthenticated).isTrue()
if (testCase.isFaceOnly && expectConfirmation) {
- assertThat(legacyState).isEqualTo(BiometricState.STATE_PENDING_CONFIRMATION)
-
assertThat(size).isEqualTo(PromptSize.MEDIUM)
assertButtonsVisible(
cancel = true,
@@ -543,8 +996,6 @@
viewModel.confirmAuthenticated()
assertThat(message).isEqualTo(PromptMessage.Empty)
assertButtonsVisible()
- } else {
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
}
}
@@ -563,7 +1014,6 @@
val authenticated by collectLastValue(viewModel.isAuthenticated)
val message by collectLastValue(viewModel.message)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
@@ -581,7 +1031,6 @@
assertThat(authenticating).isFalse()
assertThat(authenticated?.isAuthenticated).isTrue()
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
assertThat(canTryAgain).isFalse()
}
@@ -610,12 +1059,10 @@
val message by collectLastValue(viewModel.message)
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
viewModel.showHelp(helpMessage)
assertThat(size).isEqualTo(PromptSize.MEDIUM)
- assertThat(legacyState).isEqualTo(BiometricState.STATE_HELP)
assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
assertThat(messageVisible).isTrue()
@@ -632,7 +1079,6 @@
val message by collectLastValue(viewModel.message)
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val confirmationRequired by collectLastValue(viewModel.isConfirmationRequired)
if (testCase.isCoex && testCase.authenticatedByFingerprint) {
@@ -642,11 +1088,7 @@
viewModel.showHelp(helpMessage)
assertThat(size).isEqualTo(PromptSize.MEDIUM)
- if (confirmationRequired == true) {
- assertThat(legacyState).isEqualTo(BiometricState.STATE_PENDING_CONFIRMATION)
- } else {
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
- }
+
assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
assertThat(messageVisible).isTrue()
assertThat(authenticating).isFalse()
@@ -785,6 +1227,15 @@
authenticatedModality = BiometricModality.Fingerprint,
),
TestCase(
+ fingerprint =
+ fingerprintSensorPropertiesInternal(
+ strong = true,
+ sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+ )
+ .first(),
+ authenticatedModality = BiometricModality.Fingerprint,
+ ),
+ TestCase(
face = faceSensorPropertiesInternal(strong = true).first(),
authenticatedModality = BiometricModality.Face,
confirmationRequested = true,
@@ -794,6 +1245,16 @@
authenticatedModality = BiometricModality.Fingerprint,
confirmationRequested = true,
),
+ TestCase(
+ fingerprint =
+ fingerprintSensorPropertiesInternal(
+ strong = true,
+ sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+ )
+ .first(),
+ authenticatedModality = BiometricModality.Fingerprint,
+ confirmationRequested = true,
+ ),
)
private val coexTestCases =
@@ -834,7 +1295,9 @@
val modality =
when {
fingerprint != null && face != null -> "coex"
- fingerprint != null -> "fingerprint only"
+ fingerprint != null && fingerprint.isAnySidefpsType -> "fingerprint only, sideFps"
+ fingerprint != null && !fingerprint.isAnySidefpsType ->
+ "fingerprint only, non-sideFps"
face != null -> "face only"
else -> "?"
}
@@ -864,6 +1327,8 @@
val isCoex: Boolean
get() = face != null && fingerprint != null
+ @FingerprintSensorProperties.SensorType val sensorType: Int? = fingerprint?.sensorType
+
val shouldStartAsImplicitFlow: Boolean
get() = (isFaceOnly || isCoex) && !confirmationRequested
}
@@ -890,3 +1355,5 @@
BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
)
}
+
+internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index cc4eca5..b48bc1d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -48,6 +48,7 @@
import com.android.systemui.res.R.string.kg_trust_agent_disabled
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -86,6 +87,7 @@
@Mock private lateinit var countDownTimerUtil: CountDownTimerUtil
@Mock private lateinit var systemPropertiesHelper: SystemPropertiesHelper
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
private lateinit var testScope: TestScope
@@ -119,6 +121,7 @@
keyguardUpdateMonitor,
fakeTrustRepository,
testScope.backgroundScope,
+ mSelectedUserInteractor,
)
underTest =
BouncerMessageInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index 9a5b4585..f6b284f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -25,7 +25,6 @@
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.DejankUtils
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
@@ -37,7 +36,9 @@
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.android.systemui.utils.os.FakeHandler
@@ -70,6 +71,7 @@
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
private lateinit var mainHandler: FakeHandler
private lateinit var underTest: PrimaryBouncerInteractor
private lateinit var resources: TestableResources
@@ -100,6 +102,7 @@
keyguardUpdateMonitor,
trustRepository,
testScope.backgroundScope,
+ mSelectedUserInteractor,
)
whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
whenever(repository.primaryBouncerShow.value).thenReturn(false)
@@ -108,6 +111,27 @@
}
@Test
+ fun show_nullDelegate() {
+ testScope.run {
+ whenever(bouncerView.delegate).thenReturn(null)
+ mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+ // WHEN bouncer show is requested
+ underTest.show(true)
+
+ // WHEN all queued messages are dispatched
+ mainHandler.dispatchQueuedMessages()
+
+ // THEN primary bouncer state doesn't update to show since delegate was null
+ verify(repository, never()).setPrimaryShow(true)
+ verify(repository, never()).setPrimaryShowingSoon(false)
+ verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
+ verify(mPrimaryBouncerCallbackInteractor, never())
+ .dispatchVisibilityChanged(View.VISIBLE)
+ }
+ }
+
+ @Test
fun testShow_isScrimmed() {
underTest.show(true)
verify(repository).setKeyguardAuthenticatedBiometrics(null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index 2018e61..d1b120e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -28,8 +28,8 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.TestScope
@@ -51,8 +51,8 @@
@Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
- @Mock private lateinit var keyguardBypassController: KeyguardBypassController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
private val mainHandler = FakeHandler(Looper.getMainLooper())
private lateinit var underTest: PrimaryBouncerInteractor
@@ -74,6 +74,7 @@
keyguardUpdateMonitor,
Mockito.mock(TrustRepository::class.java),
TestScope().backgroundScope,
+ mSelectedUserInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index 802f8e6..b75f3e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -32,8 +32,8 @@
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
@@ -60,7 +60,7 @@
@Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
- @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+ @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private val mainHandler = FakeHandler(Looper.getMainLooper())
val repository = FakeKeyguardBouncerRepository()
@@ -82,6 +82,7 @@
keyguardUpdateMonitor,
Mockito.mock(TrustRepository::class.java),
TestScope().backgroundScope,
+ mSelectedUserInteractor,
)
underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index 037c1ba..6d3cc4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -28,11 +28,11 @@
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
@@ -46,8 +46,8 @@
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(JUnit4::class)
@@ -74,7 +74,7 @@
@Mock
lateinit var contentResolver: ContentResolver
@Mock
- lateinit var userTracker: UserTracker
+ lateinit var mSelectedUserInteractor: SelectedUserInteractor
private lateinit var underTest: CameraGestureHelper
@@ -103,7 +103,7 @@
cameraIntents = cameraIntents,
contentResolver = contentResolver,
uiExecutor = MoreExecutors.directExecutor(),
- userTracker = userTracker,
+ selectedUserInteractor = mSelectedUserInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 442bf91..80fe9e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.sensors.ThresholdSensor;
@@ -75,6 +76,8 @@
private ShadeExpansionStateManager mShadeExpansionStateManager;
@Mock
private BatteryController mBatteryController;
+ @Mock
+ private SelectedUserInteractor mSelectedUserInteractor;
private final DockManagerFake mDockManager = new DockManagerFake();
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
@@ -90,7 +93,7 @@
mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor,
mStatusBarStateController, mKeyguardStateController, mShadeExpansionStateManager,
mBatteryController,
- mDockManager, mFakeExecutor, mFakeSystemClock);
+ mDockManager, mFakeExecutor, mFakeSystemClock, () -> mSelectedUserInteractor);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
index 15e5e1c..80b281e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
@@ -37,6 +37,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import org.junit.Before;
import org.junit.Test;
@@ -63,6 +64,8 @@
private WallpaperManager mWallpaperManager;
@Mock
private DumpManager mDumpManager;
+ @Mock
+ private SelectedUserInteractor mSelectedUserInteractor;
private ColorExtractor.GradientColors mColors;
private SysuiColorExtractor mColorExtractor;
@@ -83,7 +86,8 @@
mock(ConfigurationController.class),
mWallpaperManager,
mDumpManager,
- true /* immediately */);
+ true /* immediately */,
+ () -> mSelectedUserInteractor);
}
@Test
@@ -125,7 +129,8 @@
configurationController,
mWallpaperManager,
mDumpManager,
- true /* immediately */);
+ true /* immediately */,
+ () -> mSelectedUserInteractor);
verify(configurationController).addCallback(eq(sysuiColorExtractor));
reset(tonal);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 3df9cbb..fcb191b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -11,11 +11,15 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.communal.data.model.CommunalWidgetMetadata
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.kotlinArgumentCaptor
@@ -35,6 +39,7 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@@ -55,30 +60,52 @@
@Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var featureFlags: FeatureFlagsClassic
@Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
+ @Mock private lateinit var providerInfoA: AppWidgetProviderInfo
+
+ @Mock private lateinit var providerInfoB: AppWidgetProviderInfo
+
+ @Mock private lateinit var providerInfoC: AppWidgetProviderInfo
+
+ private lateinit var communalRepository: FakeCommunalRepository
+
private lateinit var logBuffer: LogBuffer
private val testDispatcher = StandardTestDispatcher()
+
private val testScope = TestScope(testDispatcher)
+ private val fakeAllowlist =
+ listOf(
+ "com.android.fake/WidgetProviderA",
+ "com.android.fake/WidgetProviderB",
+ "com.android.fake/WidgetProviderC",
+ )
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
logBuffer = FakeLogBuffer.Factory.create()
+ communalRepository = FakeCommunalRepository()
- featureFlagEnabled(true)
+ communalEnabled(true)
+ widgetOnKeyguardEnabled(true)
+ setAppWidgetIds(emptyList())
+
+ overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
+
whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
whenever(userTracker.userHandle).thenReturn(userHandle)
}
@Test
- fun broadcastReceiver_featureDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
+ fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
testScope.runTest {
- featureFlagEnabled(false)
+ communalEnabled(false)
val repository = initCommunalWidgetRepository()
collectLastValue(repository.stopwatchAppWidgetInfo)()
verifyBroadcastReceiverNeverRegistered()
@@ -115,7 +142,7 @@
job.cancel()
runCurrent()
- Mockito.verify(broadcastDispatcher).unregisterReceiver(receiver)
+ verify(broadcastDispatcher).unregisterReceiver(receiver)
}
@Test
@@ -152,7 +179,7 @@
installedProviders(listOf(stopwatchProviderInfo))
val repository = initCommunalWidgetRepository()
collectLastValue(repository.stopwatchAppWidgetInfo)()
- Mockito.verify(appWidgetHost).allocateAppWidgetId()
+ verify(appWidgetHost).allocateAppWidgetId()
}
@Test
@@ -171,8 +198,8 @@
// Verify app widget id allocated
assertThat(lastStopwatchProviderInfo()?.appWidgetId).isEqualTo(123456)
- Mockito.verify(appWidgetHost).allocateAppWidgetId()
- Mockito.verify(appWidgetHost, Mockito.never()).deleteAppWidgetId(anyInt())
+ verify(appWidgetHost).allocateAppWidgetId()
+ verify(appWidgetHost, Mockito.never()).deleteAppWidgetId(anyInt())
// User locked again
userUnlocked(false)
@@ -180,7 +207,7 @@
// Verify app widget id deleted
assertThat(lastStopwatchProviderInfo()).isNull()
- Mockito.verify(appWidgetHost).deleteAppWidgetId(123456)
+ verify(appWidgetHost).deleteAppWidgetId(123456)
}
@Test
@@ -189,13 +216,13 @@
userUnlocked(false)
val repository = initCommunalWidgetRepository()
collectLastValue(repository.stopwatchAppWidgetInfo)()
- Mockito.verify(appWidgetHost, Mockito.never()).startListening()
+ verify(appWidgetHost, Mockito.never()).startListening()
userUnlocked(true)
broadcastReceiverUpdate()
collectLastValue(repository.stopwatchAppWidgetInfo)()
- Mockito.verify(appWidgetHost).startListening()
+ verify(appWidgetHost).startListening()
}
@Test
@@ -209,21 +236,105 @@
broadcastReceiverUpdate()
collectLastValue(repository.stopwatchAppWidgetInfo)()
- Mockito.verify(appWidgetHost).startListening()
- Mockito.verify(appWidgetHost, Mockito.never()).stopListening()
+ verify(appWidgetHost).startListening()
+ verify(appWidgetHost, Mockito.never()).stopListening()
userUnlocked(false)
broadcastReceiverUpdate()
collectLastValue(repository.stopwatchAppWidgetInfo)()
- Mockito.verify(appWidgetHost).stopListening()
+ verify(appWidgetHost).stopListening()
+ }
+
+ @Test
+ fun getCommunalWidgetAllowList_onInit() {
+ testScope.runTest {
+ val repository = initCommunalWidgetRepository()
+ val communalWidgetAllowlist = repository.communalWidgetAllowlist
+ assertThat(
+ listOf(
+ CommunalWidgetMetadata(
+ componentName = fakeAllowlist[0],
+ priority = 3,
+ sizes = listOf(CommunalContentSize.HALF),
+ ),
+ CommunalWidgetMetadata(
+ componentName = fakeAllowlist[1],
+ priority = 2,
+ sizes = listOf(CommunalContentSize.HALF),
+ ),
+ CommunalWidgetMetadata(
+ componentName = fakeAllowlist[2],
+ priority = 1,
+ sizes = listOf(CommunalContentSize.HALF),
+ ),
+ )
+ )
+ .containsExactly(*communalWidgetAllowlist.toTypedArray())
+ }
+ }
+
+ // This behavior is temporary before the local database is set up.
+ @Test
+ fun communalWidgets_withPreviouslyBoundWidgets_removeEachBinding() =
+ testScope.runTest {
+ whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(1, 2, 3)
+ setAppWidgetIds(listOf(1, 2, 3))
+ whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
+ userUnlocked(true)
+
+ val repository = initCommunalWidgetRepository()
+
+ collectLastValue(repository.communalWidgets)()
+
+ verify(appWidgetHost).deleteAppWidgetId(1)
+ verify(appWidgetHost).deleteAppWidgetId(2)
+ verify(appWidgetHost).deleteAppWidgetId(3)
+ }
+
+ @Test
+ fun communalWidgets_allowlistNotEmpty_bindEachWidgetFromTheAllowlist() =
+ testScope.runTest {
+ whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(0, 1, 2)
+ userUnlocked(true)
+
+ whenever(appWidgetManager.getAppWidgetInfo(0)).thenReturn(providerInfoA)
+ whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfoB)
+ whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfoC)
+
+ val repository = initCommunalWidgetRepository()
+
+ val inventory by collectLastValue(repository.communalWidgets)
+
+ assertThat(
+ listOf(
+ CommunalWidgetContentModel(
+ appWidgetId = 0,
+ providerInfo = providerInfoA,
+ priority = 3,
+ ),
+ CommunalWidgetContentModel(
+ appWidgetId = 1,
+ providerInfo = providerInfoB,
+ priority = 2,
+ ),
+ CommunalWidgetContentModel(
+ appWidgetId = 2,
+ providerInfo = providerInfoC,
+ priority = 1,
+ ),
+ )
+ )
+ .containsExactly(*inventory!!.toTypedArray())
}
private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl {
return CommunalWidgetRepositoryImpl(
+ context,
appWidgetManager,
appWidgetHost,
broadcastDispatcher,
+ communalRepository,
packageManager,
userManager,
userTracker,
@@ -233,7 +344,7 @@
}
private fun verifyBroadcastReceiverRegistered() {
- Mockito.verify(broadcastDispatcher)
+ verify(broadcastDispatcher)
.registerReceiver(
any(),
any(),
@@ -245,7 +356,7 @@
}
private fun verifyBroadcastReceiverNeverRegistered() {
- Mockito.verify(broadcastDispatcher, Mockito.never())
+ verify(broadcastDispatcher, Mockito.never())
.registerReceiver(
any(),
any(),
@@ -258,7 +369,7 @@
private fun broadcastReceiverUpdate(): BroadcastReceiver {
val broadcastReceiverCaptor = kotlinArgumentCaptor<BroadcastReceiver>()
- Mockito.verify(broadcastDispatcher)
+ verify(broadcastDispatcher)
.registerReceiver(
broadcastReceiverCaptor.capture(),
any(),
@@ -271,7 +382,11 @@
return broadcastReceiverCaptor.value
}
- private fun featureFlagEnabled(enabled: Boolean) {
+ private fun communalEnabled(enabled: Boolean) {
+ communalRepository.setIsCommunalEnabled(enabled)
+ }
+
+ private fun widgetOnKeyguardEnabled(enabled: Boolean) {
whenever(featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)).thenReturn(enabled)
}
@@ -282,4 +397,8 @@
private fun installedProviders(providers: List<AppWidgetProviderInfo>) {
whenever(appWidgetManager.installedProviders).thenReturn(providers)
}
+
+ private fun setAppWidgetIds(ids: List<Int>) {
+ whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index cdc42e0..8e21f29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -22,7 +22,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
new file mode 100644
index 0000000..9b8e581
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.deviceentry.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.logging.BiometricUnlockLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
+import com.android.systemui.keyevent.data.repository.FakeKeyEventRepository
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
+
+ private lateinit var repository: DeviceEntryHapticsRepository
+ private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+ private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+ private lateinit var keyEventRepository: FakeKeyEventRepository
+ private lateinit var powerRepository: FakePowerRepository
+ private lateinit var systemClock: FakeSystemClock
+ private lateinit var underTest: DeviceEntryHapticsInteractor
+
+ @Before
+ fun setUp() {
+ repository = DeviceEntryHapticsRepositoryImpl()
+ fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
+ keyEventRepository = FakeKeyEventRepository()
+ powerRepository = FakePowerRepository()
+ systemClock = FakeSystemClock()
+ underTest =
+ DeviceEntryHapticsInteractor(
+ repository = repository,
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
+ biometricSettingsRepository = biometricSettingsRepository,
+ keyEventInteractor = KeyEventInteractor(keyEventRepository),
+ powerInteractor =
+ PowerInteractor(
+ powerRepository,
+ mock(FalsingCollector::class.java),
+ mock(ScreenOffAnimationController::class.java),
+ mock(StatusBarStateController::class.java),
+ ),
+ systemClock = systemClock,
+ logger = mock(BiometricUnlockLogger::class.java),
+ )
+ }
+
+ @Test
+ fun nonPowerButtonFPS_vibrateSuccess() = runTest {
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+ underTest.vibrateSuccess()
+ assertThat(playSuccessHaptic).isTrue()
+ }
+
+ @Test
+ fun powerButtonFPS_vibrateSuccess() = runTest {
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ setPowerButtonFingerprintProperty()
+ setFingerprintEnrolled()
+ keyEventRepository.setPowerButtonDown(false)
+
+ // It's been 10 seconds since the last power button wakeup
+ setAwakeFromPowerButton()
+ runCurrent()
+ systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000)
+
+ underTest.vibrateSuccess()
+ assertThat(playSuccessHaptic).isTrue()
+ }
+
+ @Test
+ fun powerButtonFPS_powerDown_doNotVibrateSuccess() = runTest {
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ setPowerButtonFingerprintProperty()
+ setFingerprintEnrolled()
+ keyEventRepository.setPowerButtonDown(true) // power button is currently DOWN
+
+ // It's been 10 seconds since the last power button wakeup
+ setAwakeFromPowerButton()
+ runCurrent()
+ systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000)
+
+ underTest.vibrateSuccess()
+ assertThat(playSuccessHaptic).isFalse()
+ }
+
+ @Test
+ fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() = runTest {
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ setPowerButtonFingerprintProperty()
+ setFingerprintEnrolled()
+ keyEventRepository.setPowerButtonDown(false)
+
+ // It's only been 50ms since the last power button wakeup
+ setAwakeFromPowerButton()
+ runCurrent()
+ systemClock.setUptimeMillis(systemClock.uptimeMillis() + 50)
+
+ underTest.vibrateSuccess()
+ assertThat(playSuccessHaptic).isFalse()
+ }
+
+ @Test
+ fun nonPowerButtonFPS_vibrateError() = runTest {
+ val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+ setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+ underTest.vibrateError()
+ assertThat(playErrorHaptic).isTrue()
+ }
+
+ @Test
+ fun powerButtonFPS_vibrateError() = runTest {
+ val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+ setPowerButtonFingerprintProperty()
+ setFingerprintEnrolled()
+ underTest.vibrateError()
+ assertThat(playErrorHaptic).isTrue()
+ }
+
+ @Test
+ fun powerButtonFPS_powerDown_doNotVibrateError() = runTest {
+ val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+ setPowerButtonFingerprintProperty()
+ setFingerprintEnrolled()
+ keyEventRepository.setPowerButtonDown(true)
+ underTest.vibrateError()
+ assertThat(playErrorHaptic).isFalse()
+ }
+
+ private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) {
+ fingerprintPropertyRepository.setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = fingerprintSensorType,
+ sensorLocations = mapOf(),
+ )
+ }
+
+ private fun setPowerButtonFingerprintProperty() {
+ setFingerprintSensorType(FingerprintSensorType.POWER_BUTTON)
+ }
+
+ private fun setFingerprintEnrolled() {
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ }
+
+ private fun setAwakeFromPowerButton() {
+ powerRepository.updateWakefulness(
+ WakefulnessState.AWAKE,
+ WakeSleepReason.POWER_BUTTON,
+ WakeSleepReason.POWER_BUTTON,
+ powerButtonLaunchGestureTriggered = false,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
index 7e1edd2..ba578a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -51,6 +51,7 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.wakelock.WakeLockFake;
import com.android.systemui.utils.os.FakeHandler;
@@ -85,6 +86,8 @@
private DozeLog mDozeLog;
@Mock
private DozeScreenBrightness mDozeScreenBrightness;
+ @Mock
+ private SelectedUserInteractor mSelectedUserInteractor;
@Before
public void setUp() throws Exception {
@@ -100,7 +103,7 @@
mWakeLock = new WakeLockFake();
mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeHost, mDozeParameters,
mWakeLock, mAuthController, mUdfpsControllerProvider, mDozeLog,
- mDozeScreenBrightness);
+ mDozeScreenBrightness, mSelectedUserInteractor);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index a88a8e5..3cc0451 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -52,9 +52,9 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.doze.DozeSensors.TriggerSensor;
import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.settings.FakeSettings;
@@ -102,7 +102,7 @@
@Mock
private DevicePostureController mDevicePostureController;
@Mock
- private UserTracker mUserTracker;
+ private SelectedUserInteractor mSelectedUserInteractor;
@Mock
private ProximitySensor mProximitySensor;
@@ -122,7 +122,8 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
- when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+ when(mSelectedUserInteractor.getSelectedUserId())
+ .thenReturn(ActivityManager.getCurrentUser());
when(mAmbientDisplayConfiguration.tapSensorTypeMapping())
.thenReturn(new String[]{"tapSensor"});
when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
@@ -435,7 +436,7 @@
DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
- mDevicePostureController, mUserTracker);
+ mDevicePostureController, mSelectedUserInteractor);
for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
assertFalse(sensor.mIgnoresSetting);
@@ -480,7 +481,7 @@
DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
- mDevicePostureController, mUserTracker);
+ mDevicePostureController, mSelectedUserInteractor);
for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
// THEN lift to wake's TriggerSensor enabledBySettings is false
@@ -499,7 +500,7 @@
DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
- mDevicePostureController, mUserTracker);
+ mDevicePostureController, mSelectedUserInteractor);
for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
// THEN lift to wake's TriggerSensor enabledBySettings is true
@@ -514,7 +515,7 @@
super(mResources, mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
- mDevicePostureController, mUserTracker);
+ mDevicePostureController, mSelectedUserInteractor);
for (TriggerSensor sensor : mTriggerSensors) {
if (sensor instanceof PluginSensor
&& ((PluginSensor) sensor).mPluginSensor.getType()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 494e230..3a6b075 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -53,6 +53,7 @@
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -101,6 +102,8 @@
@Mock
private UserTracker mUserTracker;
@Mock
+ private SelectedUserInteractor mSelectedUserInteractor;
+ @Mock
private SessionTracker mSessionTracker;
private DozeTriggers mTriggers;
@@ -134,7 +137,7 @@
asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
mProximityCheck, mDozeLog, mBroadcastDispatcher, new FakeSettings(),
mAuthController, mUiEventLogger, mSessionTracker, mKeyguardStateController,
- mDevicePostureController, mUserTracker);
+ mDevicePostureController, mUserTracker, mSelectedUserInteractor);
mTriggers.setDozeMachine(mMachine);
waitForSensorManager();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
index 37c70d8..2bd2bff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -43,10 +43,10 @@
fun accessingUnspecifiedFlags_throwsException() {
val flags: FeatureFlags = FakeFeatureFlags()
try {
- assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse()
+ assertThat(flags.isEnabled(Flags.NULL_FLAG)).isFalse()
fail("Expected an exception when accessing an unspecified flag.")
} catch (ex: IllegalStateException) {
- assertThat(ex.message).contains("UNKNOWN(teamfood)")
+ assertThat(ex.message).contains("UNKNOWN(null_flag)")
}
try {
assertThat(flags.isEnabled(unreleasedFlag)).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
index c12a581..f51745b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -22,6 +22,7 @@
import android.content.res.Resources
import android.content.res.Resources.NotFoundException
import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.FakeFeatureFlagsImpl
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -65,6 +66,7 @@
private lateinit var broadcastReceiver: BroadcastReceiver
private lateinit var clearCacheAction: Consumer<String>
private val serverFlagReader = ServerFlagReaderFake()
+ private val fakeGantryFlags = FakeFeatureFlagsImpl()
private val teamfoodableFlagA = UnreleasedFlag(name = "a", namespace = "test", teamfood = true)
private val teamfoodableFlagB = ReleasedFlag(name = "b", namespace = "test", teamfood = true)
@@ -72,7 +74,7 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- flagMap.put(Flags.TEAMFOOD.name, Flags.TEAMFOOD)
+ fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", false)
flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB)
mFeatureFlagsClassicDebug =
@@ -84,6 +86,7 @@
resources,
serverFlagReader,
flagMap,
+ fakeGantryFlags,
restarter
)
mFeatureFlagsClassicDebug.init()
@@ -121,8 +124,6 @@
@Test
fun teamFoodFlag_False() {
- whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
- .thenReturn(false)
assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isFalse()
assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue()
@@ -133,8 +134,7 @@
@Test
fun teamFoodFlag_True() {
- whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
- .thenReturn(true)
+ fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", true)
assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue()
@@ -149,8 +149,7 @@
.thenReturn(true)
whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.name), any()))
.thenReturn(false)
- whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
- .thenReturn(true)
+ fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", true)
assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
new file mode 100644
index 0000000..bb6786a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.flags
+
+import android.platform.test.flag.junit.SetFlagsRule
+
+fun SetFlagsRule.setFlagDefault(flagName: String) {
+ if (getFlagDefault(flagName)) {
+ enableFlags(flagName)
+ } else {
+ disableFlags(flagName)
+ }
+}
+
+// NOTE: This code uses reflection to gain access to private members of aconfig generated
+// classes (in the same way SetFlagsRule does internally) because this is the only way to get
+// at the underlying information and read the current value of the flag.
+// If aconfig had flag constants with accessible default values, this would be unnecessary.
+private fun getFlagDefault(name: String): Boolean {
+ val flagPackage = name.substringBeforeLast(".")
+ val featureFlagsImplClass = Class.forName("$flagPackage.FeatureFlagsImpl")
+ val featureFlagsImpl = featureFlagsImplClass.getConstructor().newInstance()
+ val flagMethodName = name.substringAfterLast(".").snakeToCamelCase()
+ val flagGetter = featureFlagsImplClass.getDeclaredMethod(flagMethodName)
+ return flagGetter.invoke(featureFlagsImpl) as Boolean
+}
+
+private fun String.snakeToCamelCase(): String {
+ val pattern = "_[a-z]".toRegex()
+ return replace(pattern) { it.value.last().uppercase() }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 2d3f69d..00009f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -76,6 +76,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.RingerModeLiveData;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.settings.FakeGlobalSettings;
@@ -134,6 +135,7 @@
@Mock private ShadeController mShadeController;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private DialogLaunchAnimator mDialogLaunchAnimator;
+ @Mock private SelectedUserInteractor mSelectedUserInteractor;
@Mock private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
@Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
@@ -186,12 +188,14 @@
mPackageManager,
mShadeController,
mKeyguardUpdateMonitor,
- mDialogLaunchAnimator);
+ mDialogLaunchAnimator,
+ mSelectedUserInteractor);
mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors();
backdropColors.setMainColor(Color.BLACK);
when(mColorExtractor.getNeutralColors()).thenReturn(backdropColors);
+ when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(0);
}
@Test
@@ -568,7 +572,8 @@
@Test
public void testOnLockScreen_disableSmartLock() {
mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
- int user = KeyguardUpdateMonitor.getCurrentUser();
+ int expectedUser = 100;
+ doReturn(expectedUser).when(mSelectedUserInteractor).getSelectedUserId();
doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
@@ -586,7 +591,7 @@
mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */);
// Then smart lock will be disabled
- verify(mLockPatternUtils).requireCredentialEntry(eq(user));
+ verify(mLockPatternUtils).requireCredentialEntry(eq(expectedUser));
// hide dialog again
mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
index 0ee348e..7750d25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
@@ -28,6 +28,8 @@
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import kotlin.math.max
+import kotlin.test.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -149,26 +151,52 @@
}
@Test
- fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() {
- // GIVEN max velocity and slider progress
- val progress = 1f
- val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress)
- val ticks = VibrationEffect.startComposition()
- repeat(config.numberOfLowTicks) {
- ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
- }
+ fun playHapticAtProgress_beforeNextDragThreshold_playsLowTicksOnce() {
+ // GIVEN max velocity and a slider progress at half progress
+ val firstProgress = 0.5f
+ val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress)
+
+ // Given a second slider progress event smaller than the progress threshold
+ val secondProgress = firstProgress + max(0f, config.deltaProgressForDragThreshold - 0.01f)
// GIVEN system running for 1s
clock.advanceTime(1000)
- // WHEN two calls to play occur with the required threshold separation
- sliderHapticFeedbackProvider.onProgress(progress)
+ // WHEN two calls to play occur with the required threshold separation (time and progress)
+ sliderHapticFeedbackProvider.onProgress(firstProgress)
clock.advanceTime(dragTextureThresholdMillis.toLong())
- sliderHapticFeedbackProvider.onProgress(progress)
+ sliderHapticFeedbackProvider.onProgress(secondProgress)
- // THEN the correct composition plays two times
- verify(vibratorHelper, times(2))
- .vibrate(eq(ticks.compose()), any(VibrationAttributes::class.java))
+ // THEN Only the first compositions plays
+ verify(vibratorHelper, times(1))
+ .vibrate(eq(firstTicks), any(VibrationAttributes::class.java))
+ verify(vibratorHelper, times(1))
+ .vibrate(any(VibrationEffect::class.java), any(VibrationAttributes::class.java))
+ }
+
+ @Test
+ fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() {
+ // GIVEN max velocity and a slider progress at half progress
+ val firstProgress = 0.5f
+ val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress)
+
+ // Given a second slider progress event beyond progress threshold
+ val secondProgress = firstProgress + config.deltaProgressForDragThreshold + 0.01f
+ val secondTicks = generateTicksComposition(config.maxVelocityToScale, secondProgress)
+
+ // GIVEN system running for 1s
+ clock.advanceTime(1000)
+
+ // WHEN two calls to play occur with the required threshold separation (time and progress)
+ sliderHapticFeedbackProvider.onProgress(firstProgress)
+ clock.advanceTime(dragTextureThresholdMillis.toLong())
+ sliderHapticFeedbackProvider.onProgress(secondProgress)
+
+ // THEN the correct compositions play
+ verify(vibratorHelper, times(1))
+ .vibrate(eq(firstTicks), any(VibrationAttributes::class.java))
+ verify(vibratorHelper, times(1))
+ .vibrate(eq(secondTicks), any(VibrationAttributes::class.java))
}
@Test
@@ -229,6 +257,38 @@
.vibrate(eq(bookendVibration), any(VibrationAttributes::class.java))
}
+ fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() {
+ // GIVEN max velocity and a slider progress at half progress
+ val progress = 0.5f
+
+ // GIVEN system running for 1s
+ clock.advanceTime(1000)
+
+ // WHEN a drag texture plays
+ sliderHapticFeedbackProvider.onProgress(progress)
+
+ // THEN the dragTextureLastProgress remembers the latest progress
+ assertEquals(progress, sliderHapticFeedbackProvider.dragTextureLastProgress)
+ }
+
+ @Test
+ fun dragTextureLastProgress_afterDragTextureHaptics_resetsOnHandleReleased() {
+ // GIVEN max velocity and a slider progress at half progress
+ val progress = 0.5f
+
+ // GIVEN system running for 1s
+ clock.advanceTime(1000)
+
+ // WHEN a drag texture plays
+ sliderHapticFeedbackProvider.onProgress(progress)
+
+ // WHEN the handle is released
+ sliderHapticFeedbackProvider.onHandleReleasedFromTouch()
+
+ // THEN the dragTextureLastProgress tracker is reset
+ assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress)
+ }
+
private fun scaleAtBookends(velocity: Float): Float {
val range = config.upperBookendScale - config.lowerBookendScale
val interpolatedVelocity =
@@ -244,4 +304,15 @@
val bump = interpolatedVelocity * config.additionalVelocityMaxBump
return interpolatedProgress * range + config.progressBasedDragMinScale + bump
}
+
+ private fun generateTicksComposition(velocity: Float, progress: Float): VibrationEffect {
+ val ticks = VibrationEffect.startComposition()
+ repeat(config.numberOfLowTicks) {
+ ticks.addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ scaleAtProgressChange(velocity, progress)
+ )
+ }
+ return ticks.compose()
+ }
}
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 a646823..2b280c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -117,6 +117,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -187,6 +188,7 @@
private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
private @Mock ShadeInteractor mShadeInteractor;
private @Mock ShadeWindowLogger mShadeWindowLogger;
+ private @Mock SelectedUserInteractor mSelectedUserInteractor;
private @Captor ArgumentCaptor<KeyguardStateController.Callback>
mKeyguardStateControllerCallback;
private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -213,7 +215,7 @@
private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
private FakeFeatureFlags mFeatureFlags;
- private int mInitialUserId;
+ private final int mDefaultUserId = 100;
@Before
public void setUp() throws Exception {
@@ -233,6 +235,8 @@
.thenReturn(mock(Flow.class));
when(mDreamingToLockscreenTransitionViewModel.getTransitionEnded())
.thenReturn(mock(Flow.class));
+ when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId);
+ when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId);
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
mContext,
new FakeWindowRootViewComponent.Factory(mock(WindowRootView.class)),
@@ -251,9 +255,11 @@
mAuthController,
mShadeExpansionStateManager,
() -> mShadeInteractor,
- mShadeWindowLogger);
+ mShadeWindowLogger,
+ () -> mSelectedUserInteractor);
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
+ mFeatureFlags.set(Flags.REFACTOR_GETCURRENTUSER, true);
DejankUtils.setImmediate(true);
@@ -266,12 +272,6 @@
}).when(mKeyguardStateController).notifyKeyguardGoingAway(anyBoolean());
createAndStartViewMediator();
- mInitialUserId = KeyguardUpdateMonitor.getCurrentUser();
- }
-
- @After
- public void teardown() {
- KeyguardUpdateMonitor.setCurrentUser(mInitialUserId);
}
/**
@@ -451,7 +451,7 @@
mViewMediator.setKeyguardEnabled(false);
TestableLooper.get(this).processAllMessages();
- mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser());
+ mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId);
mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
final ArgumentCaptor<Runnable> animationRunnableCaptor =
ArgumentCaptor.forClass(Runnable.class);
@@ -617,8 +617,8 @@
public void lockAfterScreenTimeoutUsesValueFromSettings() {
int currentUserId = 99;
int userSpecificTimeout = 5999;
- KeyguardUpdateMonitor.setCurrentUser(currentUserId);
+ when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(currentUserId);
when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
when(mDevicePolicyManager.getMaximumTimeToLock(null, currentUserId)).thenReturn(0L);
when(mSecureSettings.getIntForUser(LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
@@ -718,7 +718,7 @@
startMockKeyguardExitAnimation();
assertTrue(mViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind());
- mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser());
+ mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId);
mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
TestableLooper.get(this).processAllMessages();
verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false);
@@ -782,7 +782,7 @@
// Verify keyguard told of authentication
verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
- mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser());
+ mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId);
mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
final ArgumentCaptor<Runnable> animationRunnableCaptor =
ArgumentCaptor.forClass(Runnable.class);
@@ -814,7 +814,7 @@
// Verify keyguard told of authentication
verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
clearInvocations(mStatusBarKeyguardViewManager);
- mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser());
+ mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId);
mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
final ArgumentCaptor<Runnable> animationRunnableCaptor =
ArgumentCaptor.forClass(Runnable.class);
@@ -844,7 +844,7 @@
// Verify keyguard told of authentication
verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
- mViewMediator.mViewMediatorCallback.keyguardDonePending(mUpdateMonitor.getCurrentUser());
+ mViewMediator.mViewMediatorCallback.keyguardDonePending(mDefaultUserId);
mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
final ArgumentCaptor<Runnable> animationRunnableCaptor =
ArgumentCaptor.forClass(Runnable.class);
@@ -1111,7 +1111,8 @@
mDispatcher,
() -> mDreamingToLockscreenTransitionViewModel,
mSystemPropertiesHelper,
- () -> mock(WindowManagerLockscreenVisibilityManager.class));
+ () -> mock(WindowManagerLockscreenVisibilityManager.class),
+ mSelectedUserInteractor);
mViewMediator.start();
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index d8cdf29..90fd652 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -147,7 +147,7 @@
emptyMap()
)
verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture())
- verify(authController, atLeastOnce()).addCallback(authControllerCallback.capture())
+ verify(authController, times(2)).addCallback(authControllerCallback.capture())
}
@Test
@@ -314,18 +314,18 @@
fun faceEnrollmentChangeIsPropagatedForTheCurrentUser() =
testScope.runTest {
createBiometricSettingsRepository()
+ val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+
faceAuthIsEnabledByBiometricManager()
doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
runCurrent()
- clearInvocations(authController)
- whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false)
- val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+ enrollmentChange(FACE, PRIMARY_USER_ID, false)
assertThat(faceAuthAllowed()).isFalse()
- verify(authController).addCallback(authControllerCallback.capture())
+
enrollmentChange(REAR_FINGERPRINT, PRIMARY_USER_ID, true)
assertThat(faceAuthAllowed()).isFalse()
@@ -375,25 +375,20 @@
fun faceEnrollmentChangesArePropagatedAfterUserSwitch() =
testScope.runTest {
createBiometricSettingsRepository()
+ val faceAuthAllowed by collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+
verify(biometricManager)
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
userRepository.setSelectedUserInfo(ANOTHER_USER)
doNotDisableKeyguardAuthFeatures(ANOTHER_USER_ID)
biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
-
- runCurrent()
- clearInvocations(authController)
-
- val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
- runCurrent()
-
- verify(authController).addCallback(authControllerCallback.capture())
-
+ onNonStrongAuthChanged(true, ANOTHER_USER_ID)
whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
enrollmentChange(FACE, ANOTHER_USER_ID, true)
+ runCurrent()
- assertThat(faceAuthAllowed()).isTrue()
+ assertThat(faceAuthAllowed).isTrue()
}
@Test
@@ -637,6 +632,7 @@
val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
faceAuthIsEnrolled()
+ enrollmentChange(FACE, PRIMARY_USER_ID, true)
deviceIsInPostureThatSupportsFaceAuth()
doNotDisableKeyguardAuthFeatures()
faceAuthIsStrongBiometric()
@@ -660,6 +656,7 @@
val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
faceAuthIsEnrolled()
+ enrollmentChange(FACE, PRIMARY_USER_ID, true)
deviceIsInPostureThatSupportsFaceAuth()
doNotDisableKeyguardAuthFeatures()
faceAuthIsNonStrongBiometric()
@@ -753,7 +750,9 @@
}
private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
- authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
+ authControllerCallback.allValues.forEach {
+ it.onEnrollmentsChanged(biometricType, userId, enabled)
+ }
}
private fun doNotDisableKeyguardAuthFeatures(userId: Int = PRIMARY_USER_ID) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index b9119e1..ef03fdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -30,14 +30,10 @@
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.dreams.DreamOverlayCallbackController
-import com.android.systemui.keyguard.ScreenLifecycle
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.phone.BiometricUnlockController
-import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -47,7 +43,6 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -68,13 +63,10 @@
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var keyguardStateController: KeyguardStateController
- @Mock private lateinit var screenLifecycle: ScreenLifecycle
- @Mock private lateinit var biometricUnlockController: BiometricUnlockController
@Mock private lateinit var dozeTransitionListener: DozeTransitionListener
@Mock private lateinit var authController: AuthController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
- @Mock private lateinit var dozeParameters: DozeParameters
private val mainDispatcher = StandardTestDispatcher()
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -89,12 +81,9 @@
underTest =
KeyguardRepositoryImpl(
statusBarStateController,
- screenLifecycle,
- biometricUnlockController,
keyguardStateController,
keyguardUpdateMonitor,
dozeTransitionListener,
- dozeParameters,
authController,
dreamOverlayCallbackController,
mainDispatcher,
@@ -201,26 +190,6 @@
}
@Test
- fun isAodAvailable() = runTest {
- val flow = underTest.isAodAvailable
- val isAodAvailable = collectLastValue(flow)
- runCurrent()
-
- val callback =
- withArgCaptor<DozeParameters.Callback> { verify(dozeParameters).addCallback(capture()) }
-
- whenever(dozeParameters.getAlwaysOn()).thenReturn(false)
- callback.onAlwaysOnChange()
- assertThat(isAodAvailable()).isEqualTo(false)
-
- whenever(dozeParameters.getAlwaysOn()).thenReturn(true)
- callback.onAlwaysOnChange()
- assertThat(isAodAvailable()).isEqualTo(true)
-
- flow.onCompletion { verify(dozeParameters).removeCallback(callback) }
- }
-
- @Test
fun isKeyguardOccluded() =
testScope.runTest {
whenever(keyguardStateController.isOccluded).thenReturn(false)
@@ -386,53 +355,6 @@
}
@Test
- fun biometricUnlockState() =
- testScope.runTest {
- val values = mutableListOf<BiometricUnlockModel>()
- val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
-
- runCurrent()
- val captor = argumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener>()
- verify(biometricUnlockController).addListener(captor.capture())
-
- listOf(
- BiometricUnlockController.MODE_NONE,
- BiometricUnlockController.MODE_WAKE_AND_UNLOCK,
- BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING,
- BiometricUnlockController.MODE_SHOW_BOUNCER,
- BiometricUnlockController.MODE_ONLY_WAKE,
- BiometricUnlockController.MODE_UNLOCK_COLLAPSING,
- BiometricUnlockController.MODE_DISMISS_BOUNCER,
- BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM,
- )
- .forEach {
- whenever(biometricUnlockController.mode).thenReturn(it)
- captor.value.onModeChanged(it)
- runCurrent()
- }
-
- assertThat(values)
- .isEqualTo(
- listOf(
- // Initial value will be NONE, followed by onModeChanged() call
- BiometricUnlockModel.NONE,
- BiometricUnlockModel.NONE,
- BiometricUnlockModel.WAKE_AND_UNLOCK,
- BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
- BiometricUnlockModel.SHOW_BOUNCER,
- BiometricUnlockModel.ONLY_WAKE,
- BiometricUnlockModel.UNLOCK_COLLAPSING,
- BiometricUnlockModel.DISMISS_BOUNCER,
- BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
- )
- )
-
- job.cancel()
- runCurrent()
- verify(biometricUnlockController).removeListener(captor.value)
- }
-
- @Test
fun dozeTransitionModel() =
testScope.runTest {
// For the initial state
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 5afc405..ad80a06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.util.KeyguardTransitionRunner
@@ -86,7 +87,7 @@
}
@Test
- fun startingSecondTransitionWillCancelTheFirstTransition() =
+ fun startingSecondTransitionWillCancelTheFirstTransitionAndUseLastValue() =
TestScope().runTest {
val steps = mutableListOf<TransitionStep>()
val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
@@ -100,12 +101,19 @@
val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
runner.startTransition(
this,
- TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, getAnimator()),
+ TransitionInfo(
+ OWNER_NAME,
+ LOCKSCREEN,
+ AOD,
+ getAnimator(),
+ TransitionModeOnCanceled.LAST_VALUE
+ ),
)
val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+ // Second transition starts from .1 (LAST_VALUE)
val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.1))
assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
@@ -114,6 +122,76 @@
}
@Test
+ fun startingSecondTransitionWillCancelTheFirstTransitionAndUseReset() =
+ TestScope().runTest {
+ val steps = mutableListOf<TransitionStep>()
+ val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+ runner.startTransition(
+ this,
+ TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+ maxFrames = 3,
+ )
+
+ // Now start 2nd transition, which will interrupt the first
+ val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
+ runner.startTransition(
+ this,
+ TransitionInfo(
+ OWNER_NAME,
+ LOCKSCREEN,
+ AOD,
+ getAnimator(),
+ TransitionModeOnCanceled.RESET
+ ),
+ )
+
+ val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
+ assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+
+ // Second transition starts from 0 (RESET)
+ val secondTransitionSteps = listWithStep(start = BigDecimal(0), step = BigDecimal(.1))
+ assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+
+ job.cancel()
+ job2.cancel()
+ }
+
+ @Test
+ fun startingSecondTransitionWillCancelTheFirstTransitionAndUseReverse() =
+ TestScope().runTest {
+ val steps = mutableListOf<TransitionStep>()
+ val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+ runner.startTransition(
+ this,
+ TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+ maxFrames = 3,
+ )
+
+ // Now start 2nd transition, which will interrupt the first
+ val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
+ runner.startTransition(
+ this,
+ TransitionInfo(
+ OWNER_NAME,
+ LOCKSCREEN,
+ AOD,
+ getAnimator(),
+ TransitionModeOnCanceled.REVERSE
+ ),
+ )
+
+ val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
+ assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+
+ // Second transition starts from .9 (REVERSE)
+ val secondTransitionSteps = listWithStep(start = BigDecimal(0.9), step = BigDecimal(.1))
+ assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+
+ job.cancel()
+ job2.cancel()
+ }
+
+ @Test
fun nullAnimatorEnablesManualControlWithUpdateTransition() =
TestScope().runTest {
val steps = mutableListOf<TransitionStep>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index 5bd747f..8dea57c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -20,10 +20,14 @@
import androidx.test.filters.SmallTest
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.mock
import dagger.Lazy
import junit.framework.Assert.assertEquals
@@ -42,6 +46,12 @@
class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
private lateinit var underTest: FromPrimaryBouncerTransitionInteractor
+ private val mSelectedUserInteractor =
+ SelectedUserInteractor(
+ FakeUserRepository(),
+ FakeFeatureFlagsClassic().apply { set(Flags.REFACTOR_GETCURRENTUSER, true) }
+ )
+
// Override the fromPrimaryBouncerTransitionInteractor provider from the superclass so our
// underTest interactor is provided to any classes that need it.
override var fromPrimaryBouncerTransitionInteractorLazy:
@@ -63,6 +73,7 @@
flags = FakeFeatureFlags(),
keyguardSecurityModel = mock(),
powerInteractor = PowerInteractorFactory.create().powerInteractor,
+ selectedUserInteractor = mSelectedUserInteractor
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index d6e19cb..e87adf5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -64,8 +64,6 @@
KeyguardDismissInteractorFactory.create(
context = context,
testScope = testScope,
- broadcastDispatcher = fakeBroadcastDispatcher,
- dispatcher = dispatcher,
)
keyguardRepository = dismissInteractorWithDependencies.keyguardRepository
transitionRepository = FakeKeyguardTransitionRepository()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
index a5cfbbf..ecb46bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
@@ -58,8 +58,6 @@
KeyguardDismissInteractorFactory.create(
context = context,
testScope = testScope,
- broadcastDispatcher = fakeBroadcastDispatcher,
- dispatcher = dispatcher,
)
underTest = underTestDependencies.interactor
underTestDependencies.userRepository.setUserInfos(listOf(userInfo))
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 06eb0dd..e45f56a 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
@@ -43,7 +43,7 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -62,6 +62,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
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.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -95,9 +96,11 @@
FakeDeviceEntryFingerprintAuthRepository
private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
private lateinit var powerInteractor: PowerInteractor
+ private lateinit var fakeBiometricSettingsRepository: FakeBiometricSettingsRepository
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig
+ @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Before
fun setup() {
@@ -123,6 +126,8 @@
facePropertyRepository = FakeFacePropertyRepository()
fakeKeyguardRepository = FakeKeyguardRepository()
powerInteractor = PowerInteractorFactory.create().powerInteractor
+ fakeBiometricSettingsRepository = FakeBiometricSettingsRepository()
+
underTest =
SystemUIKeyguardFaceAuthInteractor(
mContext,
@@ -142,12 +147,13 @@
keyguardUpdateMonitor,
FakeTrustRepository(),
testScope.backgroundScope,
+ mSelectedUserInteractor,
),
AlternateBouncerInteractor(
mock(StatusBarStateController::class.java),
mock(KeyguardStateController::class.java),
bouncerRepository,
- mock(BiometricSettingsRepository::class.java),
+ fakeBiometricSettingsRepository,
FakeSystemClock(),
keyguardUpdateMonitor,
),
@@ -160,6 +166,7 @@
facePropertyRepository,
faceWakeUpTriggersConfig,
powerInteractor,
+ fakeBiometricSettingsRepository,
)
}
@@ -481,6 +488,7 @@
fun faceUnlockIsDisabledWhenFpIsLockedOut() =
testScope.runTest {
underTest.start()
+ fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
runCurrent()
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 5d5ece0..275ac80 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
@@ -42,6 +42,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.shade.domain.model.ShadeModel
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
@@ -56,7 +57,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
@@ -86,6 +86,7 @@
// Used to verify transition requests for test output
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+ @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
@@ -158,6 +159,7 @@
flags = featureFlags,
keyguardSecurityModel = keyguardSecurityModel,
powerInteractor = powerInteractor,
+ selectedUserInteractor = mSelectedUserInteractor,
)
.apply { start() }
@@ -241,7 +243,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to PRIMARY_BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -268,7 +270,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -295,7 +297,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -325,7 +327,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DREAMING should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -356,7 +358,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -383,7 +385,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -410,7 +412,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -443,7 +445,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to Lockscreen should occur
assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -471,7 +473,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to Gone should occur
assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -501,7 +503,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to PRIMARY_BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -534,7 +536,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -566,7 +568,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -590,7 +592,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -622,7 +624,7 @@
advanceUntilIdle()
// THEN the transition is ignored
- verify(transitionRepository, never()).startTransition(any(), anyBoolean())
+ verify(transitionRepository, never()).startTransition(any())
coroutineContext.cancelChildren()
}
@@ -639,7 +641,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -666,7 +668,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -693,7 +695,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -716,7 +718,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -746,7 +748,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DREAMING should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -777,7 +779,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -803,7 +805,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to PRIMARY_BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -835,7 +837,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -868,7 +870,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -898,7 +900,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to LOCKSCREEN should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -926,7 +928,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -954,7 +956,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -978,7 +980,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to LOCKSCREEN should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1008,7 +1010,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition back to DREAMING_LOCKSCREEN_HOSTED should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1039,7 +1041,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to GONE should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1068,7 +1070,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to LOCKSCREEN should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1093,7 +1095,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AlternateBouncer should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1118,7 +1120,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AlternateBouncer should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1144,7 +1146,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1169,7 +1171,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -1196,7 +1198,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
@@ -1220,7 +1222,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -1250,7 +1252,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
@@ -1284,7 +1286,7 @@
// THEN a transition from LOCKSCREEN => OCCLUDED should occur
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
@@ -1315,7 +1317,7 @@
// THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
@@ -1336,7 +1338,7 @@
// THEN a transition from PRIMARY_BOUNCER => LOCKSCREEN should occur
val info2 =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
assertThat(info2.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index 02c98cd..f9362a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -46,6 +46,7 @@
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -90,6 +91,7 @@
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var mockedContext: Context
@Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Before
fun setup() {
@@ -145,6 +147,7 @@
keyguardUpdateMonitor,
trustRepository,
testScope.backgroundScope,
+ mSelectedUserInteractor
),
AlternateBouncerInteractor(
statusBarStateController = mock(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
new file mode 100644
index 0000000..255f4df
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.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.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GoneToAodTransitionViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: GoneToAodTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var testScope: TestScope
+
+ @Before
+ fun setUp() {
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+
+ repository = FakeKeyguardTransitionRepository()
+ val interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor
+ underTest = GoneToAodTransitionViewModel(interactor)
+ }
+
+ @Test
+ fun enterFromTopTranslationY() =
+ testScope.runTest {
+ val pixels = -100f
+ val enterFromTopTranslationY by
+ collectLastValue(underTest.enterFromTopTranslationY(pixels.toInt()))
+
+ // The animation should only start > halfway through
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(enterFromTopTranslationY).isEqualTo(pixels)
+
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(enterFromTopTranslationY).isEqualTo(pixels)
+
+ repository.sendTransitionStep(step(.85f))
+ assertThat(enterFromTopTranslationY).isIn(Range.closed(pixels, 0f))
+
+ // At the end, the translation should be complete and set to zero
+ repository.sendTransitionStep(step(1f))
+ assertThat(enterFromTopTranslationY).isEqualTo(0f)
+ }
+
+ @Test
+ fun enterFromTopAnimationAlpha() =
+ testScope.runTest {
+ val enterFromTopAnimationAlpha by collectLastValue(underTest.enterFromTopAnimationAlpha)
+
+ // The animation should only start > halfway through
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(enterFromTopAnimationAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(enterFromTopAnimationAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(.85f))
+ assertThat(enterFromTopAnimationAlpha).isIn(Range.closed(0f, 1f))
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(enterFromTopAnimationAlpha).isEqualTo(1f)
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = value,
+ transitionState = state,
+ ownerName = "GoneToAodTransitionViewModelTest"
+ )
+ }
+}
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 71688db..4f545cb 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
@@ -17,8 +17,10 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -26,12 +28,17 @@
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.plugins.ClockController
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import javax.inject.Provider
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -41,6 +48,7 @@
import org.junit.runners.JUnit4
import org.mockito.Answers
import org.mockito.Mock
+import org.mockito.Mockito.anyInt
import org.mockito.MockitoAnnotations
@SmallTest
@@ -51,10 +59,20 @@
private lateinit var testScope: TestScope
private lateinit var repository: FakeKeyguardRepository
private lateinit var keyguardInteractor: KeyguardInteractor
+ private lateinit var configurationRepository: FakeConfigurationRepository
@Mock private lateinit var burnInInteractor: BurnInInteractor
+ @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ @Mock private lateinit var goneToAodTransitionViewModel: GoneToAodTransitionViewModel
+ @Mock
+ private lateinit var aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController
private val burnInFlow = MutableStateFlow(BurnInModel())
+ private val goneToAodTransitionViewModelVisibility = MutableStateFlow(0)
+ private val enterFromTopAnimationAlpha = MutableStateFlow(0f)
+ private val goneToAodTransitionStep = MutableSharedFlow<TransitionStep>(replay = 1)
+ private val dozeAmountTransitionStep = MutableSharedFlow<TransitionStep>(replay = 1)
+ private val startedKeyguardState = MutableStateFlow(KeyguardState.GONE)
@Before
fun setUp() {
@@ -71,9 +89,30 @@
val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
keyguardInteractor = withDeps.keyguardInteractor
repository = withDeps.repository
+ configurationRepository = withDeps.configurationRepository
+
+ whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt()))
+ .thenReturn(emptyFlow<Float>())
+ whenever(goneToAodTransitionViewModel.enterFromTopAnimationAlpha)
+ .thenReturn(enterFromTopAnimationAlpha)
whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
- underTest = KeyguardRootViewModel(keyguardInteractor, burnInInteractor)
+
+ whenever(keyguardTransitionInteractor.goneToAodTransition)
+ .thenReturn(goneToAodTransitionStep)
+ whenever(keyguardTransitionInteractor.dozeAmountTransition)
+ .thenReturn(dozeAmountTransitionStep)
+ whenever(keyguardTransitionInteractor.startedKeyguardState).thenReturn(startedKeyguardState)
+
+ underTest =
+ KeyguardRootViewModel(
+ context,
+ keyguardInteractor,
+ burnInInteractor,
+ goneToAodTransitionViewModel,
+ aodToLockscreenTransitionViewModel,
+ keyguardTransitionInteractor,
+ )
underTest.clockControllerProvider = Provider { clockController }
}
@@ -118,7 +157,7 @@
val scale by collectLastValue(underTest.scale)
// Set to not dozing (on lockscreen)
- repository.setDozeAmount(0f)
+ dozeAmountTransitionStep.emit(TransitionStep(value = 0f))
// Trigger a change to the burn-in model
burnInFlow.value =
@@ -141,8 +180,7 @@
val scale by collectLastValue(underTest.scale)
// Set to dozing (on AOD)
- repository.setDozeAmount(1f)
-
+ dozeAmountTransitionStep.emit(TransitionStep(value = 1f))
// Trigger a change to the burn-in model
burnInFlow.value =
BurnInModel(
@@ -150,10 +188,15 @@
translationY = 30,
scale = 0.5f,
)
-
assertThat(translationX).isEqualTo(20)
assertThat(translationY).isEqualTo(30)
assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */))
+
+ // Set to the beginning of GONE->AOD transition
+ goneToAodTransitionStep.emit(TransitionStep(value = 0f))
+ assertThat(translationX).isEqualTo(0)
+ assertThat(translationY).isEqualTo(0)
+ assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */))
}
@Test
@@ -166,7 +209,7 @@
val scale by collectLastValue(underTest.scale)
// Set to dozing (on AOD)
- repository.setDozeAmount(1f)
+ dozeAmountTransitionStep.emit(TransitionStep(value = 1f))
// Trigger a change to the burn-in model
burnInFlow.value =
@@ -180,4 +223,28 @@
assertThat(translationY).isEqualTo(0)
assertThat(scale).isEqualTo(Pair(0.5f, false /* scaleClockOnly */))
}
+
+ @Test
+ fun burnInLayerVisibility() =
+ testScope.runTest {
+ val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility)
+
+ startedKeyguardState.value = KeyguardState.OCCLUDED
+ assertThat(burnInLayerVisibility).isNull()
+
+ startedKeyguardState.value = KeyguardState.AOD
+ assertThat(burnInLayerVisibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun burnInLayerAlpha() =
+ testScope.runTest {
+ val burnInLayerAlpha by collectLastValue(underTest.burnInLayerAlpha)
+
+ enterFromTopAnimationAlpha.value = 0.2f
+ assertThat(burnInLayerAlpha).isEqualTo(0.2f)
+
+ enterFromTopAnimationAlpha.value = 1f
+ assertThat(burnInLayerAlpha).isEqualTo(1f)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
index 54fc493..1abb441 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
@@ -42,7 +42,7 @@
private var frameCount = 1L
private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
private var job: Job? = null
- private var isTerminated = false
+ @Volatile private var isTerminated = false
/**
* For transitions being directed by an animator. Will control the number of frames being
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 34360d2..6cdf4ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -4,7 +4,9 @@
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED
import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
@@ -20,6 +22,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@RunWith(AndroidTestingRunner::class)
@@ -37,10 +40,11 @@
private val view: MediaProjectionAppSelectorView = mock()
private val policyResolver: ScreenCaptureDevicePolicyResolver = mock()
+ private val logger = mock<MediaProjectionMetricsLogger>()
private val thumbnailLoader = FakeThumbnailLoader()
- private val controller =
+ private fun createController(isFirstStart: Boolean = true) =
MediaProjectionAppSelectorController(
taskListProvider,
view,
@@ -50,6 +54,8 @@
appSelectorComponentName,
callerPackageName,
thumbnailLoader,
+ isFirstStart,
+ logger
)
@Before
@@ -61,7 +67,7 @@
fun initNoRecentTasks_bindsEmptyList() {
taskListProvider.tasks = emptyList()
- controller.init()
+ createController().init()
verify(view).bind(emptyList())
}
@@ -70,7 +76,7 @@
fun initOneRecentTask_bindsList() {
taskListProvider.tasks = listOf(createRecentTask(taskId = 1))
- controller.init()
+ createController().init()
verify(view).bind(listOf(createRecentTask(taskId = 1)))
}
@@ -86,7 +92,7 @@
)
taskListProvider.tasks = tasks
- controller.init()
+ createController().init()
assertThat(thumbnailLoader.capturedTaskIds).containsExactly(2, 3)
}
@@ -101,7 +107,7 @@
)
taskListProvider.tasks = tasks
- controller.init()
+ createController().init()
verify(view)
.bind(
@@ -124,7 +130,7 @@
)
taskListProvider.tasks = tasks
- controller.init()
+ createController().init()
verify(view)
.bind(
@@ -147,7 +153,7 @@
)
taskListProvider.tasks = tasks
- controller.init()
+ createController().init()
verify(view)
.bind(
@@ -172,7 +178,7 @@
)
taskListProvider.tasks = tasks
- controller.init()
+ createController().init()
verify(view)
.bind(
@@ -199,11 +205,29 @@
taskListProvider.tasks = tasks
givenCaptureAllowed(isAllow = false)
- controller.init()
+ createController().init()
verify(view).bind(emptyList())
}
+ @Test
+ fun init_firstStart_logsAppSelectorDisplayed() {
+ val controller = createController(isFirstStart = true)
+
+ controller.init()
+
+ verify(logger).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+ }
+
+ @Test
+ fun init_notFirstStart_doesNotLogAppSelectorDisplayed() {
+ val controller = createController(isFirstStart = false)
+
+ controller.init()
+
+ verify(logger, never()).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+ }
+
private fun givenCaptureAllowed(isAllow: Boolean) {
whenever(policyResolver.isScreenCaptureAllowed(any(), any())).thenReturn(isAllow)
}
@@ -216,6 +240,7 @@
): RecentTask {
return RecentTask(
taskId = taskId,
+ displayId = 0,
topActivityComponent = topActivityComponent,
baseIntentComponent = ComponentName("com", "Test"),
userId = userId,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index 2c7ee56..d75553f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -128,6 +128,7 @@
private fun createRecentTask(taskId: Int): RecentTask =
RecentTask(
taskId = taskId,
+ displayId = 0,
userId = 0,
topActivityComponent = null,
baseIntentComponent = null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 9b1f830..0e6e4fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -36,7 +36,6 @@
import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
import com.android.systemui.res.R
import com.android.systemui.security.data.model.SecurityModel
-import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.statusbar.policy.FakeSecurityController
import com.android.systemui.statusbar.policy.FakeUserInfoController
import com.android.systemui.statusbar.policy.FakeUserInfoController.FakeInfo
@@ -130,7 +129,6 @@
val userInfoController = FakeUserInfoController(FakeInfo(picture = picture))
val settings = FakeGlobalSettings()
val userId = 42
- val userTracker = FakeUserTracker(userId)
val userSwitcherControllerWrapper =
MockUserSwitcherControllerWrapper(currentUserName = "foo")
@@ -148,7 +146,6 @@
utils.footerActionsInteractor(
userSwitcherRepository =
utils.userSwitcherRepository(
- userTracker = userTracker,
settings = settings,
userManager = userManager,
userInfoController = userInfoController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
index 0552ced..0e4b113 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
@@ -124,11 +124,44 @@
}
@Test
- fun testLongClickIntent() {
+ fun testLongClickIntent_safetyCenterEnabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
- assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+ val cameraTile = CameraToggleTile(
+ host,
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ metricsLogger,
+ FalsingManagerFake(),
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ privacyController,
+ keyguardStateController,
+ safetyCenterManager)
+ assertThat(cameraTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+ cameraTile.destroy()
+ testableLooper.processAllMessages()
+ }
+ @Test
+ fun testLongClickIntent_safetyCenterDisabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
+ val cameraTile = CameraToggleTile(
+ host,
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ metricsLogger,
+ FalsingManagerFake(),
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ privacyController,
+ keyguardStateController,
+ safetyCenterManager)
assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+ cameraTile.destroy()
+ testableLooper.processAllMessages()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index e537131..4c5a214 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -54,11 +54,7 @@
import com.android.systemui.statusbar.connectivity.SignalCallback;
import com.android.systemui.statusbar.connectivity.WifiIndicators;
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository;
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor;
-import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl;
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
@@ -111,7 +107,8 @@
private WifiInteractor mWifiInteractor;
private final TileJavaAdapter mJavaAdapter = new TileJavaAdapter();
- private final FakeWifiRepository mWifiRepository = new FakeWifiRepository();
+ private final FakeConnectivityRepository mConnectivityRepository =
+ new FakeConnectivityRepository();
private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
private final TestScope mTestScope = TestScopeProvider.getTestScope();
@@ -124,12 +121,6 @@
mTestableLooper = TestableLooper.get(this);
when(mHost.getContext()).thenReturn(mContext);
-
- mWifiInteractor = new WifiInteractorImpl(
- new FakeConnectivityRepository(),
- mWifiRepository,
- mTestScope
- );
}
@After
@@ -204,25 +195,41 @@
// SIGNAL_CALLBACK_DEPRECATION flag set to true
@Test
- public void stateUnavailable_wifiDisabled_newPipeline() {
+ public void stateUnavailable_noDefaultNetworks_newPipeline() {
createAndStartTileNewImpl();
- mWifiRepository.setIsWifiEnabled(false);
mTestableLooper.processAllMessages();
assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
}
@Test
- public void stateUnavailable_wifiEnabled_notConnected_newPipeline() {
+ public void stateUnavailable_mobileConnected_newPipeline() {
createAndStartTileNewImpl();
- mWifiRepository.setIsWifiEnabled(true);
- mWifiRepository.setWifiNetwork(Inactive.INSTANCE);
+ mConnectivityRepository.setMobileConnected(true);
mTestableLooper.processAllMessages();
assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
}
@Test
+ public void stateInactive_wifiConnected_newPipeline() {
+ createAndStartTileNewImpl();
+ mConnectivityRepository.setWifiConnected(true);
+ mTestableLooper.processAllMessages();
+
+ assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+ }
+
+ @Test
+ public void stateInactive_ethernetConnected_newPipeline() {
+ createAndStartTileNewImpl();
+ mConnectivityRepository.setEthernetConnected(true);
+ mTestableLooper.processAllMessages();
+
+ assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+ }
+
+ @Test
public void stateActive_wifiConnectedAndCasting_newPipeline() {
createAndStartTileNewImpl();
CastController.CastDevice device = new CastController.CastDevice();
@@ -231,40 +238,27 @@
devices.add(device);
when(mController.getCastDevices()).thenReturn(devices);
- mWifiRepository.setWifiNetwork(
- new WifiNetworkModel.Active(
- 1 /* networkId */,
- true /* isValidated */,
- 3 /* level */,
- "test" /* ssid */,
- WifiNetworkModel.HotspotDeviceType.NONE,
- false /* isPasspointAccessPoint */,
- false /* isOnlineSignUpforPasspointAccessPoint */,
- null /* passpointProviderFriendlyName */
- ));
+ mConnectivityRepository.setWifiConnected(true);
+
mTestableLooper.processAllMessages();
assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
}
@Test
- public void stateInactive_wifiConnectedNotCasting_newPipeline() {
+ public void stateActive_ethernetConnectedAndCasting_newPipeline() {
createAndStartTileNewImpl();
+ CastController.CastDevice device = new CastController.CastDevice();
+ device.state = CastDevice.STATE_CONNECTED;
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(device);
+ when(mController.getCastDevices()).thenReturn(devices);
- mWifiRepository.setWifiNetwork(
- new WifiNetworkModel.Active(
- 1 /* networkId */,
- true /* isValidated */,
- 3 /* level */,
- "test" /* ssid */,
- WifiNetworkModel.HotspotDeviceType.NONE,
- false /* isPasspointAccessPoint */,
- false /* isOnlineSignUpforPasspointAccessPoint */,
- null /* passpointProviderFriendlyName */
- ));
+ mConnectivityRepository.setEthernetConnected(true);
+
mTestableLooper.processAllMessages();
- assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+ assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
}
// -------------------------------------------------
@@ -512,7 +506,7 @@
mNetworkController,
mHotspotController,
mDialogLaunchAnimator,
- mWifiInteractor,
+ mConnectivityRepository,
mJavaAdapter,
mFeatureFlags
);
@@ -555,7 +549,7 @@
mNetworkController,
mHotspotController,
mDialogLaunchAnimator,
- mWifiInteractor,
+ mConnectivityRepository,
mJavaAdapter,
mFeatureFlags
);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
index 0fcfdb6..b98a7570 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
@@ -123,11 +123,44 @@
}
@Test
- fun testLongClickIntent() {
+ fun testLongClickIntent_safetyCenterEnabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
- assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+ val micTile = MicrophoneToggleTile(
+ host,
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ metricsLogger,
+ FalsingManagerFake(),
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ privacyController,
+ keyguardStateController,
+ safetyCenterManager)
+ assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+ micTile.destroy()
+ testableLooper.processAllMessages()
+ }
+ @Test
+ fun testLongClickIntent_safetyCenterDisabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
- assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+ val micTile = MicrophoneToggleTile(
+ host,
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ metricsLogger,
+ FalsingManagerFake(),
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ privacyController,
+ keyguardStateController,
+ safetyCenterManager)
+ assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+ micTile.destroy()
+ testableLooper.processAllMessages()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 5b3d45b..ac03073 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -22,11 +22,13 @@
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.Dialog;
import android.os.Handler;
import android.service.quicksettings.Tile;
import android.testing.AndroidTestingRunner;
@@ -35,11 +37,11 @@
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -48,6 +50,7 @@
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -89,6 +92,10 @@
private PanelInteractor mPanelInteractor;
@Mock
private QsEventLogger mUiEventLogger;
+ @Mock
+ private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ @Mock
+ private Dialog mPermissionDialogPrompt;
private TestableLooper mTestableLooper;
private ScreenRecordTile mTile;
@@ -116,7 +123,8 @@
mKeyguardDismissUtil,
mKeyguardStateController,
mDialogLaunchAnimator,
- mPanelInteractor
+ mPanelInteractor,
+ mMediaProjectionMetricsLogger
);
mTile.initialize();
@@ -280,4 +288,27 @@
assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_off));
}
+ @Test
+ public void showingDialogPrompt_logsMediaProjectionPermissionRequested() {
+ when(mController.isStarting()).thenReturn(false);
+ when(mController.isRecording()).thenReturn(false);
+ when(mController.createScreenRecordDialog(any(), any(), any(), any(), any()))
+ .thenReturn(mPermissionDialogPrompt);
+
+ mTile.handleClick(null /* view */);
+ mTestableLooper.processAllMessages();
+
+ verify(mController).createScreenRecordDialog(any(), eq(mFeatureFlags),
+ eq(mDialogLaunchAnimator), eq(mActivityStarter), any());
+ var onDismissAction = ArgumentCaptor.forClass(ActivityStarter.OnDismissAction.class);
+ verify(mKeyguardDismissUtil).executeWhenUnlocked(
+ onDismissAction.capture(), anyBoolean(), anyBoolean());
+ assertNotNull(onDismissAction.getValue());
+
+ onDismissAction.getValue().onDismiss();
+
+ verify(mPermissionDialogPrompt).show();
+ verify(mMediaProjectionMetricsLogger).notifyPermissionRequestDisplayed();
+ }
+
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index f1fcee3..9907278 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -23,11 +23,14 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dump.LogcatEchoTrackerAlways
import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
@@ -42,6 +45,7 @@
class QSTileLoggerTest : SysuiTestCase() {
@Mock private lateinit var statusBarController: StatusBarStateController
+ @Mock private lateinit var logBufferFactory: LogBufferFactory
private val chattyLogBuffer = LogBuffer("TestChatty", 5, LogcatEchoTrackerAlways())
private val logBuffer = LogBuffer("Test", 1, LogcatEchoTrackerAlways())
@@ -51,10 +55,11 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ whenever(logBufferFactory.create(any(), any(), any())).thenReturn(logBuffer)
underTest =
QSTileLogger(
mapOf(TileSpec.create("chatty_tile") to chattyLogBuffer),
- { logBuffer },
+ logBufferFactory,
statusBarController
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 58e36be..10c7c43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -21,6 +21,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -49,6 +51,7 @@
private val testScope = utils.testScope
private val sceneInteractor = utils.sceneInteractor()
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private var mobileIconsViewModel: MobileIconsViewModel =
MobileIconsViewModel(
@@ -61,6 +64,7 @@
FakeConnectivityRepository(),
),
constants = mock(),
+ flags,
scope = testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 61dd69a..88a5c17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -25,6 +25,8 @@
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.model.SysUiState
@@ -137,6 +139,7 @@
)
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private var mobileIconsViewModel: MobileIconsViewModel =
MobileIconsViewModel(
@@ -149,6 +152,7 @@
FakeConnectivityRepository(),
),
constants = mock(),
+ flags,
scope = testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 7b13de6..e1345d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -42,6 +42,7 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assume.assumeTrue
@@ -292,6 +293,7 @@
initialSceneKey = SceneKey.Lockscreen,
authenticationMethod = AuthenticationMethodModel.Pin,
isDeviceUnlocked = false,
+ startsAwake = false
)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
@@ -299,6 +301,7 @@
utils.deviceEntryRepository.setUnlocked(true)
runCurrent()
powerInteractor.setAwakeForTest()
+ runCurrent()
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -403,42 +406,43 @@
initialSceneKey = SceneKey.Lockscreen,
authenticationMethod = AuthenticationMethodModel.Pin,
isDeviceUnlocked = false,
+ startsAwake = false,
)
underTest.start()
runCurrent()
verify(falsingCollector, never()).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
- verify(falsingCollector, never()).onScreenOff()
+ verify(falsingCollector, times(1)).onScreenOff()
powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
runCurrent()
verify(falsingCollector, times(1)).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
- verify(falsingCollector, never()).onScreenOff()
+ verify(falsingCollector, times(1)).onScreenOff()
powerInteractor.setAsleepForTest()
runCurrent()
verify(falsingCollector, times(1)).onScreenTurningOn()
verify(falsingCollector, never()).onScreenOnFromTouch()
- verify(falsingCollector, times(1)).onScreenOff()
+ verify(falsingCollector, times(2)).onScreenOff()
powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_TAP)
runCurrent()
verify(falsingCollector, times(1)).onScreenTurningOn()
verify(falsingCollector, times(1)).onScreenOnFromTouch()
- verify(falsingCollector, times(1)).onScreenOff()
+ verify(falsingCollector, times(2)).onScreenOff()
powerInteractor.setAsleepForTest()
runCurrent()
verify(falsingCollector, times(1)).onScreenTurningOn()
verify(falsingCollector, times(1)).onScreenOnFromTouch()
- verify(falsingCollector, times(2)).onScreenOff()
+ verify(falsingCollector, times(3)).onScreenOff()
powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
runCurrent()
verify(falsingCollector, times(2)).onScreenTurningOn()
verify(falsingCollector, times(1)).onScreenOnFromTouch()
- verify(falsingCollector, times(2)).onScreenOff()
+ verify(falsingCollector, times(3)).onScreenOff()
}
@Test
@@ -509,11 +513,12 @@
verify(falsingCollector, times(2)).onBouncerHidden()
}
- private fun prepareState(
+ private fun TestScope.prepareState(
isDeviceUnlocked: Boolean = false,
isBypassEnabled: Boolean = false,
initialSceneKey: SceneKey? = null,
authenticationMethod: AuthenticationMethodModel? = null,
+ startsAwake: Boolean = true,
): MutableStateFlow<ObservableTransitionState> {
assumeTrue(Flags.SCENE_CONTAINER_ENABLED)
sceneContainerFlags.enabled = true
@@ -537,6 +542,13 @@
authenticationMethod != AuthenticationMethodModel.None
)
}
+ if (startsAwake) {
+ powerInteractor.setAwakeForTest()
+ } else {
+ powerInteractor.setAsleepForTest()
+ }
+ runCurrent()
+
return transitionStateFlow
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 6e6833d..90d2e78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -17,8 +17,10 @@
package com.android.systemui.screenrecord;
import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
@@ -37,6 +39,8 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
+import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
import com.android.systemui.plugins.ActivityStarter;
@@ -76,6 +80,8 @@
private ActivityStarter mActivityStarter;
@Mock
private UserTracker mUserTracker;
+ @Mock
+ private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
private FakeFeatureFlags mFeatureFlags;
private RecordingController mController;
@@ -86,8 +92,15 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mFeatureFlags = new FakeFeatureFlags();
- mController = new RecordingController(mMainExecutor, mBroadcastDispatcher, mContext,
- mFeatureFlags, mUserContextProvider, () -> mDevicePolicyResolver, mUserTracker);
+ mController = new RecordingController(
+ mMainExecutor,
+ mBroadcastDispatcher,
+ mContext,
+ mFeatureFlags,
+ mUserContextProvider,
+ () -> mDevicePolicyResolver,
+ mUserTracker,
+ mMediaProjectionMetricsLogger);
mController.addCallback(mCallback);
}
@@ -269,4 +282,21 @@
assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class);
}
+
+ @Test
+ public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
+ when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
+
+ mController.createScreenRecordDialog(mContext, mFeatureFlags,
+ mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+ verify(mMediaProjectionMetricsLogger)
+ .notifyProjectionInitiated(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index 3ae1f35..da4dc85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertEquals
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -111,6 +112,15 @@
}
@Test
+ fun showDialog_singleAppIsDefault() {
+ dialog.show()
+
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val singleApp = context.getString(R.string.screen_share_permission_dialog_option_single_app)
+ assertEquals(spinner.adapter.getItem(0), singleApp)
+ }
+
+ @Test
fun showDialog_cancelClicked_dialogIsDismissed() {
dialog.show()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 0d694ee..7e41745 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -28,7 +28,6 @@
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.internal.util.ScreenshotRequest
-import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -47,7 +46,6 @@
private val scope = CoroutineScope(Dispatchers.Unconfined)
private val policy = FakeScreenshotPolicy()
- private val flags = FakeFeatureFlags()
/** Tests the Java-compatible function wrapper, ensures callback is invoked. */
@Test
@@ -58,7 +56,7 @@
.setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
.build()
)
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
var result: ScreenshotData? = null
var callbackCount = 0
@@ -86,7 +84,7 @@
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
val processedData = processor.process(ScreenshotData.fromRequest(request))
@@ -111,7 +109,7 @@
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
val processedData = processor.process(ScreenshotData.fromRequest(request))
@@ -138,7 +136,7 @@
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
Assert.assertThrows(IllegalStateException::class.java) {
runBlocking { processor.process(ScreenshotData.fromRequest(request)) }
@@ -148,7 +146,7 @@
@Test
fun testProvidedImageScreenshot() = runBlocking {
val bounds = Rect(50, 50, 150, 150)
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
policy.setManagedProfile(USER_ID, false)
@@ -173,7 +171,7 @@
@Test
fun testProvidedImageScreenshot_managedProfile() = runBlocking {
val bounds = Rect(50, 50, 150, 150)
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
// Indicate that the screenshot belongs to a manged profile
policy.setManagedProfile(USER_ID, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index a105c15..3dc9037 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import java.lang.IllegalStateException
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
@@ -43,8 +44,11 @@
private val controller0 = mock<ScreenshotController>()
private val controller1 = mock<ScreenshotController>()
+ private val notificationsController0 = mock<ScreenshotNotificationsController>()
+ private val notificationsController1 = mock<ScreenshotNotificationsController>()
private val controllerFactory = mock<ScreenshotController.Factory>()
private val callback = mock<TakeScreenshotService.RequestCallback>()
+ private val notificationControllerFactory = mock<ScreenshotNotificationsController.Factory>()
private val fakeDisplayRepository = FakeDisplayRepository()
private val requestProcessor = FakeRequestProcessor()
@@ -59,12 +63,15 @@
testScope,
requestProcessor,
eventLogger,
+ notificationControllerFactory
)
@Before
fun setUp() {
- whenever(controllerFactory.create(eq(0))).thenReturn(controller0)
- whenever(controllerFactory.create(eq(1))).thenReturn(controller1)
+ whenever(controllerFactory.create(eq(0), any())).thenReturn(controller0)
+ whenever(controllerFactory.create(eq(1), any())).thenReturn(controller1)
+ whenever(notificationControllerFactory.create(eq(0))).thenReturn(notificationsController0)
+ whenever(notificationControllerFactory.create(eq(1))).thenReturn(notificationsController1)
}
@Test
@@ -74,8 +81,8 @@
val onSaved = { _: Uri -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
- verify(controllerFactory).create(eq(0))
- verify(controllerFactory).create(eq(1))
+ verify(controllerFactory).create(eq(0), any())
+ verify(controllerFactory).create(eq(1), any())
val capturer = ArgumentCaptor<ScreenshotData>()
@@ -107,8 +114,8 @@
callback
)
- verify(controllerFactory).create(eq(0))
- verify(controllerFactory, never()).create(eq(1))
+ verify(controllerFactory).create(eq(0), any())
+ verify(controllerFactory, never()).create(eq(1), any())
val capturer = ArgumentCaptor<ScreenshotData>()
@@ -139,7 +146,7 @@
@Test
fun executeScreenshots_allowedTypes_allCaptured() =
testScope.runTest {
- whenever(controllerFactory.create(any())).thenReturn(controller0)
+ whenever(controllerFactory.create(any(), any())).thenReturn(controller0)
setDisplays(
display(TYPE_INTERNAL, id = 0),
@@ -310,6 +317,123 @@
screenshotExecutor.onDestroy()
}
+ @Test
+ fun executeScreenshots_errorFromProcessor_logsScreenshotRequested() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ requestProcessor.shouldThrowException = true
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ val screenshotRequested =
+ eventLogger.logs.filter {
+ it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id
+ }
+ assertThat(screenshotRequested).hasSize(2)
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromProcessor_logsUiError() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ requestProcessor.shouldThrowException = true
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ val screenshotRequested =
+ eventLogger.logs.filter {
+ it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id
+ }
+ assertThat(screenshotRequested).hasSize(2)
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromProcessorOnDefaultDisplay_showsErrorNotification() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ requestProcessor.shouldThrowException = true
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ verify(notificationsController0).notifyScreenshotError(any())
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromProcessorOnSecondaryDisplay_showsErrorNotification() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0))
+ val onSaved = { _: Uri -> }
+ requestProcessor.shouldThrowException = true
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ verify(notificationsController0).notifyScreenshotError(any())
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromScreenshotController_reportsRequested() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ whenever(controller0.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+ whenever(controller1.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ val screenshotRequested =
+ eventLogger.logs.filter {
+ it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id
+ }
+ assertThat(screenshotRequested).hasSize(2)
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromScreenshotController_reportsError() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ whenever(controller0.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+ whenever(controller1.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ val screenshotRequested =
+ eventLogger.logs.filter {
+ it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id
+ }
+ assertThat(screenshotRequested).hasSize(2)
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromScreenshotController_showsErrorNotification() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ whenever(controller0.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+ whenever(controller1.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ verify(notificationsController0).notifyScreenshotError(any())
+ verify(notificationsController1).notifyScreenshotError(any())
+ screenshotExecutor.onDestroy()
+ }
+
private suspend fun TestScope.setDisplays(vararg displays: Display) {
fakeDisplayRepository.emit(displays.toSet())
runCurrent()
@@ -328,8 +452,9 @@
private class FakeRequestProcessor : ScreenshotRequestProcessor {
var processed: ScreenshotData? = null
var toReturn: ScreenshotData? = null
-
+ var shouldThrowException = false
override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
+ if (shouldThrowException) throw RequestProcessorException("")
processed = screenshot
return toReturn ?: screenshot
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 6205d90..f3809aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -65,6 +65,7 @@
private val requestProcessor = mock<RequestProcessor>()
private val devicePolicyManager = mock<DevicePolicyManager>()
private val devicePolicyResourcesManager = mock<DevicePolicyResourcesManager>()
+ private val notificationsControllerFactory = mock<ScreenshotNotificationsController.Factory>()
private val notificationsController = mock<ScreenshotNotificationsController>()
private val callback = mock<RequestCallback>()
@@ -86,7 +87,8 @@
)
.thenReturn(false)
whenever(userManager.isUserUnlocked).thenReturn(true)
- whenever(controllerFactory.create(any())).thenReturn(controller)
+ whenever(controllerFactory.create(any(), any())).thenReturn(controller)
+ whenever(notificationsControllerFactory.create(any())).thenReturn(notificationsController)
// Stub request processor as a synchronous no-op for tests with the flag enabled
doAnswer {
@@ -323,7 +325,7 @@
userManager,
devicePolicyManager,
eventLogger,
- notificationsController,
+ notificationsControllerFactory,
mContext,
Runnable::run,
flags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 8d8c70e..6223e25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -157,6 +157,7 @@
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
import com.android.systemui.statusbar.phone.LightBarController;
@@ -328,6 +329,7 @@
@Mock private CastController mCastController;
@Mock private KeyguardRootView mKeyguardRootView;
@Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
+ @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm;
protected final int mMaxUdfpsBurnInOffsetY = 5;
protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
@@ -388,6 +390,7 @@
mFeatureFlags,
mInteractionJankMonitor,
mKeyguardInteractor,
+ mKeyguardTransitionInteractor,
mDumpManager,
mPowerInteractor));
@@ -667,7 +670,8 @@
mKeyguardViewConfigurator,
mKeyguardFaceAuthInteractor,
new ResourcesSplitShadeStateController(),
- mPowerInteractor);
+ mPowerInteractor,
+ mKeyguardClockPositionAlgorithm);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
null,
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 eb00610..be82bc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -90,7 +90,8 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
-import com.android.systemui.user.domain.interactor.UserInteractor;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
import com.google.common.util.concurrent.MoreExecutors;
@@ -131,6 +132,7 @@
@Mock private AuthController mAuthController;
@Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
@Mock private ShadeWindowLogger mShadeWindowLogger;
+ @Mock private SelectedUserInteractor mSelectedUserInteractor;
@Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
@@ -216,6 +218,7 @@
keyguardInteractor,
featureFlags,
mKeyguardSecurityModel,
+ mSelectedUserInteractor,
powerInteractor);
mShadeInteractor =
@@ -230,7 +233,7 @@
keyguardTransitionInteractor,
powerInteractor,
new FakeUserSetupRepository(),
- mock(UserInteractor.class),
+ mock(UserSwitcherInteractor.class),
new SharedNotificationContainerInteractor(
configurationRepository,
mContext,
@@ -255,7 +258,8 @@
mAuthController,
mShadeExpansionStateManager,
() -> mShadeInteractor,
- mShadeWindowLogger) {
+ mShadeWindowLogger,
+ () -> mSelectedUserInteractor) {
@Override
protected boolean isDebuggable() {
return false;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 677d9db..da49230 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -21,7 +21,9 @@
import android.testing.TestableLooper.RunWithLooper
import android.view.KeyEvent
import android.view.MotionEvent
+import android.view.View
import android.view.ViewGroup
+import androidx.core.view.contains
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardMessageAreaController
import com.android.keyguard.KeyguardSecurityContainerController
@@ -29,6 +31,8 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.FakeFeatureFlagsImpl
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -43,11 +47,19 @@
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.compose.ComposeFacade.isComposeAvailable
import com.android.systemui.dock.DockManager
import com.android.systemui.dump.DumpManager
import com.android.systemui.dump.logcatLogBuffer
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags.ALTERNATE_BOUNCER_VIEW
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
+import com.android.systemui.flags.Flags.MIGRATE_NSSL
+import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES
+import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION
+import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON
+import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES
import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
import com.android.systemui.keyguard.DismissCallbackRegistry
@@ -81,7 +93,9 @@
import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -96,6 +110,7 @@
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import java.util.Optional
@@ -134,6 +149,7 @@
@Mock
private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@Mock private lateinit var notificationInsetsController: NotificationInsetsController
+ @Mock private lateinit var mCommunalViewModel: CommunalViewModel
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
@@ -142,6 +158,7 @@
Optional<UnfoldTransitionProgressProvider>
@Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock lateinit var dragDownHelper: DragDownHelper
+ @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Mock
lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel
@Mock lateinit var sysUIKeyEventHandler: SysUIKeyEventHandler
@@ -157,7 +174,8 @@
private lateinit var testScope: TestScope
- private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
+ private lateinit var featureFlags: FakeFeatureFlagsImpl
@Before
fun setUp() {
@@ -172,14 +190,16 @@
whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
.thenReturn(emptyFlow<TransitionStep>())
- featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
- featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
- featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
- featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
- featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
- featureFlags.set(Flags.MIGRATE_NSSL, false)
- featureFlags.set(Flags.ALTERNATE_BOUNCER_VIEW, false)
+ featureFlagsClassic = FakeFeatureFlagsClassic()
+ featureFlagsClassic.set(TRACKPAD_GESTURE_COMMON, true)
+ featureFlagsClassic.set(TRACKPAD_GESTURE_FEATURES, false)
+ featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
+ featureFlagsClassic.set(REVAMPED_BOUNCER_MESSAGES, true)
+ featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
+ featureFlagsClassic.set(MIGRATE_NSSL, false)
+ featureFlagsClassic.set(ALTERNATE_BOUNCER_VIEW, false)
+
+ featureFlags = FakeFeatureFlagsImpl()
testScope = TestScope()
fakeClock = FakeSystemClock()
@@ -200,8 +220,6 @@
centralSurfaces,
dozeServiceHost,
dozeScrimController,
- backActionInteractor,
- powerInteractor,
notificationShadeWindowController,
unfoldTransitionProgressProvider,
keyguardUnlockAnimationController,
@@ -216,14 +234,16 @@
mock(KeyguardMessageAreaController.Factory::class.java),
keyguardTransitionInteractor,
primaryBouncerToGoneTransitionViewModel,
+ mCommunalViewModel,
notificationExpansionRepository,
+ featureFlagsClassic,
featureFlags,
fakeClock,
BouncerMessageInteractor(
repository = BouncerMessageRepositoryImpl(),
userRepository = FakeUserRepository(),
countDownTimerUtil = mock(CountDownTimerUtil::class.java),
- featureFlags = featureFlags,
+ featureFlags = featureFlagsClassic,
updateMonitor = mock(KeyguardUpdateMonitor::class.java),
biometricSettingsRepository = FakeBiometricSettingsRepository(),
applicationScope = testScope.backgroundScope,
@@ -243,6 +263,7 @@
mock(KeyguardUpdateMonitor::class.java),
FakeTrustRepository(),
testScope.backgroundScope,
+ mSelectedUserInteractor,
),
facePropertyRepository = FakeFacePropertyRepository(),
deviceEntryFingerprintAuthRepository =
@@ -254,6 +275,7 @@
sysUIKeyEventHandler,
primaryBouncerInteractor,
alternateBouncerInteractor,
+ mSelectedUserInteractor,
)
underTest.setupExpandedStatusBar()
underTest.setDragDownHelper(dragDownHelper)
@@ -441,7 +463,7 @@
whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT))
.thenReturn(true)
- featureFlags.set(Flags.MIGRATE_NSSL, true)
+ featureFlagsClassic.set(MIGRATE_NSSL, true)
// THEN touch should NOT be intercepted by NotificationShade
assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse()
@@ -458,7 +480,7 @@
whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT))
.thenReturn(false)
- featureFlags.set(Flags.MIGRATE_NSSL, true)
+ featureFlagsClassic.set(MIGRATE_NSSL, true)
// THEN touch should NOT be intercepted by NotificationShade
assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
@@ -472,6 +494,48 @@
}
@Test
+ fun setsUpCommunalHubLayout_whenFlagEnabled() {
+ if (!isComposeAvailable()) {
+ return
+ }
+
+ featureFlags.setFlag(FLAG_COMMUNAL_HUB, true)
+
+ val mockCommunalPlaceholder = mock(View::class.java)
+ val fakeViewIndex = 20
+ whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder)
+ whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex)
+ whenever(view.context).thenReturn(context)
+
+ underTest.setupCommunalHubLayout()
+
+ // Communal view added as a child of the container at the proper index, the stub is removed.
+ verify(view).removeView(mockCommunalPlaceholder)
+ verify(view).addView(any(), eq(fakeViewIndex))
+ }
+
+ @Test
+ fun doesNotSetupCommunalHubLayout_whenFlagDisabled() {
+ if (!isComposeAvailable()) {
+ return
+ }
+
+ featureFlags.setFlag(FLAG_COMMUNAL_HUB, false)
+
+ val mockCommunalPlaceholder = mock(View::class.java)
+ val fakeViewIndex = 20
+ whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder)
+ whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex)
+ whenever(view.context).thenReturn(context)
+
+ underTest.setupCommunalHubLayout()
+
+ // No adding or removing of views occurs.
+ verify(view, times(0)).removeView(mockCommunalPlaceholder)
+ verify(view, times(0)).addView(any(), eq(fakeViewIndex))
+ }
+
+ @Test
fun forwardsDispatchKeyEvent() {
val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
interactionEventHandler.dispatchKeyEvent(keyEvent)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index a4a2ca0..c94741f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -28,6 +28,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.FakeFeatureFlagsImpl
import com.android.systemui.SysuiTestCase
import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -42,6 +43,7 @@
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.dock.DockManager
import com.android.systemui.dump.DumpManager
import com.android.systemui.dump.logcatLogBuffer
@@ -79,6 +81,7 @@
import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -141,9 +144,11 @@
private lateinit var unfoldTransitionProgressProvider:
Optional<UnfoldTransitionProgressProvider>
@Mock private lateinit var notificationInsetsController: NotificationInsetsController
+ @Mock private lateinit var mCommunalViewModel: CommunalViewModel
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+ @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Mock
private lateinit var primaryBouncerToGoneTransitionViewModel:
PrimaryBouncerToGoneTransitionViewModel
@@ -202,8 +207,6 @@
centralSurfaces,
dozeServiceHost,
dozeScrimController,
- backActionInteractor,
- powerInteractor,
notificationShadeWindowController,
unfoldTransitionProgressProvider,
keyguardUnlockAnimationController,
@@ -218,8 +221,10 @@
Mockito.mock(KeyguardMessageAreaController.Factory::class.java),
keyguardTransitionInteractor,
primaryBouncerToGoneTransitionViewModel,
+ mCommunalViewModel,
NotificationExpansionRepository(),
featureFlags,
+ FakeFeatureFlagsImpl(),
FakeSystemClock(),
BouncerMessageInteractor(
repository = BouncerMessageRepositoryImpl(),
@@ -245,6 +250,7 @@
Mockito.mock(KeyguardUpdateMonitor::class.java),
FakeTrustRepository(),
testScope.backgroundScope,
+ mSelectedUserInteractor,
),
facePropertyRepository = FakeFacePropertyRepository(),
deviceEntryFingerprintAuthRepository =
@@ -256,6 +262,7 @@
Mockito.mock(SysUIKeyEventHandler::class.java),
primaryBouncerInteractor,
alternateBouncerInteractor,
+ mSelectedUserInteractor,
)
controller.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 65174ba..0fcfaf9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -95,15 +95,17 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
-import com.android.systemui.user.domain.interactor.UserInteractor;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
import com.android.systemui.util.kotlin.JavaAdapter;
+import dagger.Lazy;
+
import org.junit.After;
import org.junit.Before;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import dagger.Lazy;
import kotlinx.coroutines.test.TestScope;
public class QuickSettingsControllerBaseTest extends SysuiTestCase {
@@ -162,7 +164,8 @@
@Mock protected DumpManager mDumpManager;
@Mock protected UiEventLogger mUiEventLogger;
@Mock protected CastController mCastController;
- @Mock protected UserInteractor mUserInteractor;
+ @Mock protected UserSwitcherInteractor mUserSwitcherInteractor;
+ @Mock protected SelectedUserInteractor mSelectedUserInteractor;
protected FakeDisableFlagsRepository mDisableFlagsRepository =
new FakeDisableFlagsRepository();
protected FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
@@ -249,6 +252,7 @@
keyguardInteractor,
featureFlags,
mock(KeyguardSecurityModel.class),
+ mSelectedUserInteractor,
powerInteractor);
ResourcesSplitShadeStateController splitShadeStateController =
@@ -266,7 +270,7 @@
keyguardTransitionInteractor,
powerInteractor,
new FakeUserSetupRepository(),
- mUserInteractor,
+ mUserSwitcherInteractor,
new SharedNotificationContainerInteractor(
configurationRepository,
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
index 5ca45f3..7cb6d93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -398,6 +398,12 @@
.isEqualTo(mQsController.getScrimCornerRadius());
}
+ @Test
+ public void disallowTouches_nullQs_false() {
+ mQsController.setQs(null);
+ assertThat(mQsController.disallowTouches()).isFalse();
+ }
+
private void lockScreen() {
mQsController.setBarState(KEYGUARD);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index 81382a4..3a260ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -445,105 +445,6 @@
}
@Test
- fun expanding_shadeDraggedDown_expandingTrue() =
- testScope.runTest() {
- val actual by collectLastValue(underTest.isAnyExpanding)
-
- // GIVEN shade and QS collapsed
- shadeRepository.setLegacyShadeExpansion(0f)
- shadeRepository.setQsExpansion(0f)
- runCurrent()
-
- // WHEN shade partially expanded
- shadeRepository.setLegacyShadeExpansion(.5f)
- runCurrent()
-
- // THEN anyExpanding is true
- assertThat(actual).isTrue()
- }
-
- @Test
- fun expanding_qsDraggedDown_expandingTrue() =
- testScope.runTest() {
- val actual by collectLastValue(underTest.isAnyExpanding)
-
- // GIVEN shade and QS collapsed
- shadeRepository.setLegacyShadeExpansion(0f)
- shadeRepository.setQsExpansion(0f)
- runCurrent()
-
- // WHEN shade partially expanded
- shadeRepository.setQsExpansion(.5f)
- runCurrent()
-
- // THEN anyExpanding is true
- assertThat(actual).isTrue()
- }
-
- @Test
- fun expanding_shadeDraggedUpAndDown() =
- testScope.runTest() {
- val actual by collectLastValue(underTest.isAnyExpanding)
-
- // WHEN shade starts collapsed then partially expanded
- shadeRepository.setLegacyShadeExpansion(0f)
- shadeRepository.setLegacyShadeExpansion(.5f)
- shadeRepository.setQsExpansion(0f)
- runCurrent()
-
- // THEN anyExpanding is true
- assertThat(actual).isTrue()
-
- // WHEN shade dragged up a bit
- shadeRepository.setLegacyShadeExpansion(.2f)
- runCurrent()
-
- // THEN anyExpanding is still true
- assertThat(actual).isTrue()
-
- // WHEN shade dragged down a bit
- shadeRepository.setLegacyShadeExpansion(.7f)
- runCurrent()
-
- // THEN anyExpanding is still true
- assertThat(actual).isTrue()
-
- // WHEN shade fully expanded
- shadeRepository.setLegacyShadeExpansion(1f)
- runCurrent()
-
- // THEN anyExpanding is now false
- assertThat(actual).isFalse()
-
- // WHEN shade dragged up a bit
- shadeRepository.setLegacyShadeExpansion(.7f)
- runCurrent()
-
- // THEN anyExpanding is still false
- assertThat(actual).isFalse()
- }
-
- @Test
- fun expanding_shadeDraggedDownThenUp_expandingFalse() =
- testScope.runTest() {
- val actual by collectLastValue(underTest.isAnyExpanding)
-
- // GIVEN shade starts collapsed
- shadeRepository.setLegacyShadeExpansion(0f)
- shadeRepository.setQsExpansion(0f)
- runCurrent()
-
- // WHEN shade expands but doesn't complete
- shadeRepository.setLegacyShadeExpansion(.5f)
- runCurrent()
- shadeRepository.setLegacyShadeExpansion(0f)
- runCurrent()
-
- // THEN anyExpanding is false
- assertThat(actual).isFalse()
- }
-
- @Test
fun lockscreenShadeExpansion_idle_onScene() =
testScope.runTest() {
// GIVEN an expansion flow based on transitions to and from a scene
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 607cdab..df38f93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -4,6 +4,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
@@ -31,6 +33,7 @@
private val sceneInteractor = utils.sceneInteractor()
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private var mobileIconsViewModel: MobileIconsViewModel =
MobileIconsViewModel(
@@ -43,6 +46,7 @@
FakeConnectivityRepository(),
),
constants = mock(),
+ flags,
scope = testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index c423782..3064f4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -21,6 +21,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -55,6 +57,7 @@
)
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private var mobileIconsViewModel: MobileIconsViewModel =
MobileIconsViewModel(
@@ -67,6 +70,7 @@
FakeConnectivityRepository(),
),
constants = mock(),
+ flags,
scope = testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
deleted file mode 100644
index e4da53a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2019 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.statusbar
-
-import com.google.common.truth.Truth.assertThat
-
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Point
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-private const val WIDTH = 200
-private const val HEIGHT = 200
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class MediaArtworkProcessorTest : SysuiTestCase() {
-
- private var screenWidth = 0
- private var screenHeight = 0
-
- private lateinit var processor: MediaArtworkProcessor
-
- @Before
- fun setUp() {
- processor = MediaArtworkProcessor()
-
- val point = Point()
- checkNotNull(context.display).getSize(point)
- screenWidth = point.x
- screenHeight = point.y
- }
-
- @After
- fun tearDown() {
- processor.clearCache()
- }
-
- @Test
- fun testProcessArtwork() {
- // GIVEN some "artwork", which is just a solid blue image
- val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888)
- Canvas(artwork).drawColor(Color.BLUE)
- // WHEN the background is created from the artwork
- val background = processor.processArtwork(context, artwork)!!
- // THEN the background has the size of the screen that has been downsamples
- assertThat(background.height).isLessThan(screenHeight)
- assertThat(background.width).isLessThan(screenWidth)
- assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888)
- }
-
- @Test
- fun testCache() {
- // GIVEN a solid blue image
- val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888)
- Canvas(artwork).drawColor(Color.BLUE)
- // WHEN the background is processed twice
- val background1 = processor.processArtwork(context, artwork)!!
- val background2 = processor.processArtwork(context, artwork)!!
- // THEN the two bitmaps are the same
- // Note: This is currently broken and trying to use caching causes issues
- assertThat(background1).isNotSameInstanceAs(background2)
- }
-
- @Test
- fun testConfig() {
- // GIVEN some which is not ARGB_8888
- val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ALPHA_8)
- Canvas(artwork).drawColor(Color.BLUE)
- // WHEN the background is created from the artwork
- val background = processor.processArtwork(context, artwork)!!
- // THEN the background has Config ARGB_8888
- assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888)
- }
-
- @Test
- fun testRecycledArtwork() {
- // GIVEN some "artwork", which is just a solid blue image
- val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888)
- Canvas(artwork).drawColor(Color.BLUE)
- // AND the artwork is recycled
- artwork.recycle()
- // WHEN the background is created from the artwork
- val background = processor.processArtwork(context, artwork)
- // THEN the processed bitmap is null
- assertThat(background).isNull()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
new file mode 100644
index 0000000..3a9c24a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
@@ -0,0 +1,597 @@
+/*
+ * 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.statusbar;
+
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.KeyguardManager;
+import android.app.Notification;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.FakeSettings;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationLockscreenUserManagerMainThreadTest extends SysuiTestCase {
+ @Mock
+ private NotificationPresenter mPresenter;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private UserTracker mUserTracker;
+
+ // Dependency mocks:
+ @Mock
+ private NotificationVisibilityProvider mVisibilityProvider;
+ @Mock
+ private CommonNotifCollection mNotifCollection;
+ @Mock
+ private DevicePolicyManager mDevicePolicyManager;
+ @Mock
+ private NotificationClickNotifier mClickNotifier;
+ @Mock
+ private OverviewProxyService mOverviewProxyService;
+ @Mock
+ private KeyguardManager mKeyguardManager;
+ @Mock
+ private DeviceProvisionedController mDeviceProvisionedController;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+
+ private UserInfo mCurrentUser;
+ private UserInfo mSecondaryUser;
+ private UserInfo mWorkUser;
+ private FakeSettings mSettings;
+ private TestNotificationLockscreenUserManager mLockscreenUserManager;
+ private NotificationEntry mCurrentUserNotif;
+ private NotificationEntry mSecondaryUserNotif;
+ private NotificationEntry mWorkProfileNotif;
+ private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
+ private Executor mBackgroundExecutor = Runnable::run; // Direct executor
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
+
+ int currentUserId = ActivityManager.getCurrentUser();
+ when(mUserTracker.getUserId()).thenReturn(currentUserId);
+ mSettings = new FakeSettings();
+ mSettings.setUserId(ActivityManager.getCurrentUser());
+ mCurrentUser = new UserInfo(currentUserId, "", 0);
+ mSecondaryUser = new UserInfo(currentUserId + 1, "", 0);
+ mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0,
+ UserManager.USER_TYPE_PROFILE_MANAGED);
+
+ when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
+ when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
+ mCurrentUser, mWorkUser));
+ when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
+ mSecondaryUser));
+ mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
+ Handler.createAsync(Looper.myLooper()));
+
+ Notification notifWithPrivateVisibility = new Notification();
+ notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE;
+ mCurrentUserNotif = new NotificationEntryBuilder()
+ .setNotification(notifWithPrivateVisibility)
+ .setUser(new UserHandle(mCurrentUser.id))
+ .build();
+ mSecondaryUserNotif = new NotificationEntryBuilder()
+ .setNotification(notifWithPrivateVisibility)
+ .setUser(new UserHandle(mSecondaryUser.id))
+ .build();
+ mWorkProfileNotif = new NotificationEntryBuilder()
+ .setNotification(notifWithPrivateVisibility)
+ .setUser(new UserHandle(mWorkUser.id))
+ .build();
+
+ mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
+ mLockscreenUserManager.setUpWithPresenter(mPresenter);
+ }
+
+ private void changeSetting(String setting) {
+ final Collection<Uri> lockScreenUris = new ArrayList<>();
+ lockScreenUris.add(Settings.Secure.getUriFor(setting));
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false,
+ lockScreenUris, 0);
+ }
+
+ @Test
+ public void testLockScreenShowNotificationsFalse() {
+ mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+ assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
+ }
+
+ @Test
+ public void testLockScreenShowNotificationsTrue() {
+ mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+ assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
+ }
+
+ @Test
+ public void testLockScreenAllowPrivateNotificationsTrue() {
+ mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
+ }
+
+ @Test
+ public void testLockScreenAllowPrivateNotificationsFalse() {
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
+ }
+
+ @Test
+ public void testLockScreenAllowsWorkPrivateNotificationsFalse() {
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mWorkUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
+ }
+
+ @Test
+ public void testLockScreenAllowsWorkPrivateNotificationsTrue() {
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mWorkUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
+ }
+
+ @Test
+ public void testCurrentUserPrivateNotificationsNotRedacted() {
+ // GIVEN current user doesn't allow private notifications to show
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // THEN current user's notification is redacted
+ assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ }
+
+ @Test
+ public void testCurrentUserPrivateNotificationsRedacted() {
+ // GIVEN current user allows private notifications to show
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // THEN current user's notification isn't redacted
+ assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ }
+
+ @Test
+ public void testWorkPrivateNotificationsRedacted() {
+ // GIVEN work profile doesn't private notifications to show
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mWorkUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // THEN work profile notification is redacted
+ assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+ assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
+ }
+
+ @Test
+ public void testWorkPrivateNotificationsNotRedacted() {
+ // GIVEN work profile allows private notifications to show
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mWorkUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // THEN work profile notification isn't redacted
+ assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+ assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
+ }
+
+ @Test
+ public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {
+ // GIVEN work profile allows private notifications to show but the other users don't
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mWorkUser.id);
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mCurrentUser.id);
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // THEN the work profile notification doesn't need to be redacted
+ assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+
+ // THEN the current user and secondary user notifications do need to be redacted
+ assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ }
+
+ @Test
+ public void testWorkProfileRedacted_otherUsersNotRedacted() {
+ // GIVEN work profile doesn't allow private notifications to show but the other users do
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mWorkUser.id);
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mCurrentUser.id);
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // THEN the work profile notification needs to be redacted
+ assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+
+ // THEN the current user and secondary user notifications don't need to be redacted
+ assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ }
+
+ @Test
+ public void testSecondaryUserNotRedacted_currentUserRedacted() {
+ // GIVEN secondary profile allows private notifications to show but the current user
+ // doesn't allow private notifications to show
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mCurrentUser.id);
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // THEN the secondary profile notification still needs to be redacted because the current
+ // user's setting takes precedence
+ assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ }
+
+ @Test
+ public void testUserSwitchedCallsOnUserSwitching() {
+ mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id,
+ mContext);
+ verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
+ }
+
+ @Test
+ public void testIsLockscreenPublicMode() {
+ assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
+ mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id);
+ assertTrue(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
+ }
+
+ @Test
+ public void testUpdateIsPublicMode() {
+ when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
+
+ NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
+ mLockscreenUserManager.addNotificationStateChangedListener(listener);
+ mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+
+ // first call explicitly sets user 0 to not public; notifies
+ mLockscreenUserManager.updatePublicMode();
+ TestableLooper.get(this).processAllMessages();
+ assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener).onNotificationStateChanged();
+ clearInvocations(listener);
+
+ // calling again has no changes; does not notify
+ mLockscreenUserManager.updatePublicMode();
+ TestableLooper.get(this).processAllMessages();
+ assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener, never()).onNotificationStateChanged();
+
+ // Calling again with keyguard now showing makes user 0 public; notifies
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ mLockscreenUserManager.updatePublicMode();
+ TestableLooper.get(this).processAllMessages();
+ assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener).onNotificationStateChanged();
+ clearInvocations(listener);
+
+ // calling again has no changes; does not notify
+ mLockscreenUserManager.updatePublicMode();
+ TestableLooper.get(this).processAllMessages();
+ assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener, never()).onNotificationStateChanged();
+ }
+
+ @Test
+ public void testDevicePolicyDoesNotAllowNotifications() {
+ // User allows them
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ // DevicePolicy hides notifs on lockscreen
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+ .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+ }
+
+ @Test
+ public void testDevicePolicyDoesNotAllowNotifications_secondary() {
+ Mockito.clearInvocations(mDevicePolicyManager);
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ // DevicePolicy hides notifications
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+ .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+ }
+
+ @Test
+ public void testDevicePolicy_noPrivateNotifications() {
+ Mockito.clearInvocations(mDevicePolicyManager);
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ // DevicePolicy hides sensitive content
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+ .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ }
+
+ @Test
+ public void testDevicePolicy_noPrivateNotifications_userAll() {
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ // DevicePolicy hides sensitive content
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+ .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder()
+ .setNotification(new Notification())
+ .setUser(UserHandle.ALL)
+ .build()));
+ }
+
+ @Test
+ public void testDevicePolicyPrivateNotifications_secondary() {
+ Mockito.clearInvocations(mDevicePolicyManager);
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ // DevicePolicy hides sensitive content
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+ .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+ assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ }
+
+ @Test
+ public void testHideNotifications_primary() {
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+ }
+
+ @Test
+ public void testHideNotifications_secondary() {
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+ }
+
+ @Test
+ public void testHideNotifications_secondary_userSwitch() {
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+ }
+
+ @Test
+ public void testShowNotifications_secondary_userSwitch() {
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+ assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+ }
+
+ @Test
+ public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
+ // DevicePolicy allows notifications
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+ .thenReturn(0);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ // KeyguardManager does not allow notifications
+ when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no
+ // callback, so it's only updated when the setting is
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+ }
+
+ @Test
+ public void testUserAllowsNotificationsInPublic_settingsChange() {
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+
+ // User disables
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+ }
+
+ private class TestNotificationLockscreenUserManager
+ extends NotificationLockscreenUserManagerImpl {
+ public TestNotificationLockscreenUserManager(Context context) {
+ super(
+ context,
+ mBroadcastDispatcher,
+ mDevicePolicyManager,
+ mUserManager,
+ mUserTracker,
+ (() -> mVisibilityProvider),
+ (() -> mNotifCollection),
+ mClickNotifier,
+ (() -> mOverviewProxyService),
+ NotificationLockscreenUserManagerMainThreadTest.this.mKeyguardManager,
+ mStatusBarStateController,
+ Handler.createAsync(Looper.myLooper()),
+ Handler.createAsync(Looper.myLooper()),
+ mBackgroundExecutor,
+ mDeviceProvisionedController,
+ mKeyguardStateController,
+ mSettings,
+ mock(DumpManager.class),
+ mock(LockPatternUtils.class),
+ mFakeFeatureFlags);
+ }
+
+ public BroadcastReceiver getBaseBroadcastReceiverForTest() {
+ return mBaseBroadcastReceiver;
+ }
+
+ public UserTracker.Callback getUserTrackerCallbackForTest() {
+ return mUserChangedCallback;
+ }
+
+ public ContentObserver getLockscreenSettingsObserverForTest() {
+ return mLockscreenSettingsObserver;
+ }
+
+ public ContentObserver getSettingsObserverForTest() {
+ return mSettingsObserver;
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 19863ec..a5f5fc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -16,17 +16,23 @@
package com.android.systemui.statusbar;
+import static android.app.Notification.VISIBILITY_PRIVATE;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
import static android.os.UserHandle.USER_ALL;
+import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -38,12 +44,15 @@
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -59,6 +68,8 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.UserTracker;
@@ -74,12 +85,16 @@
import com.google.android.collect.Lists;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.Executor;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -121,11 +136,15 @@
private NotificationEntry mCurrentUserNotif;
private NotificationEntry mSecondaryUserNotif;
private NotificationEntry mWorkProfileNotif;
+ private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
+ private Executor mBackgroundExecutor = Runnable::run; // Direct executor
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+
int currentUserId = ActivityManager.getCurrentUser();
when(mUserTracker.getUserId()).thenReturn(currentUserId);
mSettings = new FakeSettings();
@@ -138,103 +157,144 @@
when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
mCurrentUser, mWorkUser));
+ when(mUserManager.getUsers()).thenReturn(Lists.newArrayList(
+ mCurrentUser, mWorkUser, mSecondaryUser));
when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
mSecondaryUser));
mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
Handler.createAsync(Looper.myLooper()));
Notification notifWithPrivateVisibility = new Notification();
- notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE;
+ notifWithPrivateVisibility.visibility = VISIBILITY_PRIVATE;
mCurrentUserNotif = new NotificationEntryBuilder()
.setNotification(notifWithPrivateVisibility)
.setUser(new UserHandle(mCurrentUser.id))
.build();
+ NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+ channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE);
+ mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
+ .setChannel(channel)
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+ when(mNotifCollection.getEntry(mCurrentUserNotif.getKey())).thenReturn(mCurrentUserNotif);
mSecondaryUserNotif = new NotificationEntryBuilder()
.setNotification(notifWithPrivateVisibility)
.setUser(new UserHandle(mSecondaryUser.id))
.build();
+ mSecondaryUserNotif.setRanking(new RankingBuilder(mSecondaryUserNotif.getRanking())
+ .setChannel(channel)
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+ when(mNotifCollection.getEntry(
+ mSecondaryUserNotif.getKey())).thenReturn(mSecondaryUserNotif);
mWorkProfileNotif = new NotificationEntryBuilder()
.setNotification(notifWithPrivateVisibility)
.setUser(new UserHandle(mWorkUser.id))
.build();
+ mWorkProfileNotif.setRanking(new RankingBuilder(mWorkProfileNotif.getRanking())
+ .setChannel(channel)
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+ when(mNotifCollection.getEntry(mWorkProfileNotif.getKey())).thenReturn(mWorkProfileNotif);
mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
mLockscreenUserManager.setUpWithPresenter(mPresenter);
}
+ private void changeSetting(String setting) {
+ final Collection<Uri> lockScreenUris = new ArrayList<>();
+ lockScreenUris.add(Settings.Secure.getUriFor(setting));
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false,
+ lockScreenUris, 0);
+ }
+
@Test
public void testLockScreenShowNotificationsFalse() {
mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
}
@Test
public void testLockScreenShowNotificationsTrue() {
mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
}
@Test
public void testLockScreenAllowPrivateNotificationsTrue() {
- mSettings.putInt(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
}
@Test
public void testLockScreenAllowPrivateNotificationsFalse() {
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
}
@Test
public void testLockScreenAllowsWorkPrivateNotificationsFalse() {
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
mWorkUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
}
@Test
public void testLockScreenAllowsWorkPrivateNotificationsTrue() {
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mWorkUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
}
@Test
- public void testCurrentUserPrivateNotificationsNotRedacted() {
+ public void testCurrentUserPrivateNotificationsRedacted() {
// GIVEN current user doesn't allow private notifications to show
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN current user's notification is redacted
assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
}
@Test
- public void testCurrentUserPrivateNotificationsRedacted() {
+ public void testCurrentUserPrivateNotificationsNotRedacted() {
// GIVEN current user allows private notifications to show
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN current user's notification isn't redacted
assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
}
@Test
+ public void testCurrentUserPrivateNotificationsRedactedChannel() {
+ // GIVEN current user allows private notifications to show
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // but doesn't allow it at the channel level
+ NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+ channel.setLockscreenVisibility(VISIBILITY_PRIVATE);
+ mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
+ .setChannel(channel)
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+ // THEN the notification is redacted
+ assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ }
+
+ @Test
public void testWorkPrivateNotificationsRedacted() {
// GIVEN work profile doesn't private notifications to show
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
mWorkUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN work profile notification is redacted
assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -244,9 +304,9 @@
@Test
public void testWorkPrivateNotificationsNotRedacted() {
// GIVEN work profile allows private notifications to show
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mWorkUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN work profile notification isn't redacted
assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -256,13 +316,15 @@
@Test
public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {
// GIVEN work profile allows private notifications to show but the other users don't
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mWorkUser.id);
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
mCurrentUser.id);
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
mSecondaryUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN the work profile notification doesn't need to be redacted
assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -275,13 +337,15 @@
@Test
public void testWorkProfileRedacted_otherUsersNotRedacted() {
// GIVEN work profile doesn't allow private notifications to show but the other users do
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
mWorkUser.id);
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mCurrentUser.id);
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mSecondaryUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN the work profile notification needs to be redacted
assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -295,11 +359,12 @@
public void testSecondaryUserNotRedacted_currentUserRedacted() {
// GIVEN secondary profile allows private notifications to show but the current user
// doesn't allow private notifications to show
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
mCurrentUser.id);
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mSecondaryUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN the secondary profile notification still needs to be redacted because the current
// user's setting takes precedence
@@ -323,6 +388,7 @@
@Test
public void testUpdateIsPublicMode() {
when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
mLockscreenUserManager.addNotificationStateChangedListener(listener);
@@ -330,24 +396,28 @@
// first call explicitly sets user 0 to not public; notifies
mLockscreenUserManager.updatePublicMode();
+ TestableLooper.get(this).processAllMessages();
assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
verify(listener).onNotificationStateChanged();
clearInvocations(listener);
// calling again has no changes; does not notify
mLockscreenUserManager.updatePublicMode();
+ TestableLooper.get(this).processAllMessages();
assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
verify(listener, never()).onNotificationStateChanged();
// Calling again with keyguard now showing makes user 0 public; notifies
when(mKeyguardStateController.isShowing()).thenReturn(true);
mLockscreenUserManager.updatePublicMode();
+ TestableLooper.get(this).processAllMessages();
assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
verify(listener).onNotificationStateChanged();
clearInvocations(listener);
// calling again has no changes; does not notify
mLockscreenUserManager.updatePublicMode();
+ TestableLooper.get(this).processAllMessages();
assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
verify(listener, never()).onNotificationStateChanged();
}
@@ -356,14 +426,14 @@
public void testDevicePolicyDoesNotAllowNotifications() {
// User allows them
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
// DevicePolicy hides notifs on lockscreen
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
.thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
@@ -371,19 +441,18 @@
assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
}
- @Ignore("b/286230167")
@Test
public void testDevicePolicyDoesNotAllowNotifications_userAll() {
// User allows them
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
// DevicePolicy hides notifications
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
.thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
@@ -392,98 +461,105 @@
}
@Test
- @Ignore("b/286230167")
public void testDevicePolicyDoesNotAllowNotifications_secondary() {
+ Mockito.clearInvocations(mDevicePolicyManager);
// User allows notifications
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
// DevicePolicy hides notifications
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
.thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
- // TODO (b/286230167): enable assertion
verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
}
@Test
public void testDevicePolicy_noPrivateNotifications() {
+ Mockito.clearInvocations(mDevicePolicyManager);
// User allows notifications
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
// DevicePolicy hides sensitive content
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
.thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- // TODO (b/286230167): enable assertion. It's currently called 4 times.
- //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+ verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
}
@Test
public void testDevicePolicy_noPrivateNotifications_userAll() {
+ NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+ channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE);
+ NotificationEntry notifEntry = new NotificationEntryBuilder()
+ .setNotification(new Notification())
+ .setUser(UserHandle.ALL)
+ .build();
+ notifEntry.setRanking(new RankingBuilder(notifEntry.getRanking())
+ .setChannel(channel)
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+ when(mNotifCollection.getEntry(notifEntry.getKey())).thenReturn(notifEntry);
// User allows notifications
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
// DevicePolicy hides sensitive content
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
.thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
- assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder()
- .setNotification(new Notification())
- .setUser(UserHandle.ALL)
- .build()));
+ assertTrue(mLockscreenUserManager.needsRedaction(notifEntry));
}
@Test
public void testDevicePolicyPrivateNotifications_secondary() {
+ Mockito.clearInvocations(mDevicePolicyManager);
// User allows notifications
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
// DevicePolicy hides sensitive content
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
.thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+ mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
- // TODO (b/286230167): enable assertion. It's currently called 5 times.
- //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+ verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
}
@Test
public void testHideNotifications_primary() {
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
}
@@ -491,16 +567,17 @@
@Test
public void testHideNotifications_secondary() {
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
}
- @Ignore("b/286230167")
@Test
public void testHideNotifications_workProfile() {
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mWorkUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mWorkUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mWorkUser.id));
}
@@ -508,67 +585,62 @@
@Test
public void testHideNotifications_secondary_userSwitch() {
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
- TestNotificationLockscreenUserManager lockscreenUserManager
- = new TestNotificationLockscreenUserManager(mContext);
- lockscreenUserManager.setUpWithPresenter(mPresenter);
+ mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
- lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
- assertFalse(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
}
@Test
public void testShowNotifications_secondary_userSwitch() {
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
- TestNotificationLockscreenUserManager lockscreenUserManager
- = new TestNotificationLockscreenUserManager(mContext);
- lockscreenUserManager.setUpWithPresenter(mPresenter);
+ mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
- lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
- assertTrue(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+ assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
}
- @Ignore("b/286230167")
@Test
public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications() {
+ // KeyguardManager does not allow notifications
+ when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
// User allows notifications
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
// DevicePolicy allows notifications
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
.thenReturn(0);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
- // KeyguardManager does not
- when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
-
assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
}
@Test
public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
- // User allows notifications
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
// DevicePolicy allows notifications
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
.thenReturn(0);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
- // KeyguardManager does not
+ // KeyguardManager does not allow notifications
when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no
+ // callback, so it's only updated when the setting is
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
}
@@ -576,31 +648,30 @@
public void testUserAllowsNotificationsInPublic_settingsChange() {
// User allows notifications
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
// User disables
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
}
- @Ignore("b/286230167")
@Test
public void testUserAllowsNotificationsInPublic_devicePolicyChange() {
// User allows notifications
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
// DevicePolicy disables notifications
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
.thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
@@ -608,6 +679,28 @@
assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
}
+ @Test
+ public void testNewUserAdded() {
+ int newUserId = 14;
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, newUserId);
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, newUserId);
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, newUserId))
+ .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+ BroadcastReceiver broadcastReceiver =
+ mLockscreenUserManager.getBaseBroadcastReceiverForTest();
+ final Bundle extras = new Bundle();
+ final Intent intent = new Intent(Intent.ACTION_USER_ADDED);
+ intent.putExtras(extras);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
+ broadcastReceiver.onReceive(mContext, intent);
+
+ verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), eq(newUserId));
+
+ assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(newUserId));
+ assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(newUserId));
+ }
+
private class TestNotificationLockscreenUserManager
extends NotificationLockscreenUserManagerImpl {
public TestNotificationLockscreenUserManager(Context context) {
@@ -623,12 +716,17 @@
(() -> mOverviewProxyService),
NotificationLockscreenUserManagerTest.this.mKeyguardManager,
mStatusBarStateController,
- Handler.createAsync(Looper.myLooper()),
+ Handler.createAsync(TestableLooper.get(
+ NotificationLockscreenUserManagerTest.this).getLooper()),
+ Handler.createAsync(TestableLooper.get(
+ NotificationLockscreenUserManagerTest.this).getLooper()),
+ mBackgroundExecutor,
mDeviceProvisionedController,
mKeyguardStateController,
mSettings,
mock(DumpManager.class),
- mock(LockPatternUtils.class));
+ mock(LockPatternUtils.class),
+ mFakeFeatureFlags);
}
public BroadcastReceiver getBaseBroadcastReceiverForTest() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
index 9d6ea85..cfcf425 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
@@ -48,9 +48,7 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- doCallRealMethod()
- .whenever(notificationMediaManager)
- .updateMediaMetaData(anyBoolean(), anyBoolean())
+ doCallRealMethod().whenever(notificationMediaManager).updateMediaMetaData(anyBoolean())
doReturn(mockBackDropView).whenever(notificationMediaManager).backDropView
}
@@ -62,7 +60,7 @@
notificationMediaManager.mIsLockscreenLiveWallpaperEnabled = true
for (metaDataChanged in listOf(true, false)) {
for (allowEnterAnimation in listOf(true, false)) {
- notificationMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation)
+ notificationMediaManager.updateMediaMetaData(metaDataChanged)
verify(notificationMediaManager, never()).mediaMetadata
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
deleted file mode 100644
index aeb5b03..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2017 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.statusbar.notification;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.annotation.Nullable;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.palette.graphics.Palette;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.After;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class MediaNotificationProcessorTest extends SysuiTestCase {
-
- private static final int BITMAP_WIDTH = 10;
- private static final int BITMAP_HEIGHT = 10;
-
- /**
- * Color tolerance is borrowed from the AndroidX test utilities for Palette.
- */
- private static final int COLOR_TOLERANCE = 8;
-
- @Nullable private Bitmap mArtwork;
-
- @After
- public void tearDown() {
- if (mArtwork != null) {
- mArtwork.recycle();
- mArtwork = null;
- }
- }
-
- @Test
- public void findBackgroundSwatch_white() {
- // Given artwork that is completely white.
- mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(mArtwork);
- canvas.drawColor(Color.WHITE);
- // WHEN the background swatch is computed
- Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork);
- // THEN the swatch color is white
- assertCloseColors(swatch.getRgb(), Color.WHITE);
- }
-
- @Test
- public void findBackgroundSwatch_red() {
- // Given artwork that is completely red.
- mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(mArtwork);
- canvas.drawColor(Color.RED);
- // WHEN the background swatch is computed
- Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork);
- // THEN the swatch color is red
- assertCloseColors(swatch.getRgb(), Color.RED);
- }
-
- static void assertCloseColors(int expected, int actual) {
- assertThat((float) Color.red(expected)).isWithin(COLOR_TOLERANCE).of(Color.red(actual));
- assertThat((float) Color.green(expected)).isWithin(COLOR_TOLERANCE).of(Color.green(actual));
- assertThat((float) Color.blue(expected)).isWithin(COLOR_TOLERANCE).of(Color.blue(actual));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index c664c39..2ef4374 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -18,10 +18,16 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.test.suitebuilder.annotation.SmallTest;
@@ -33,8 +39,8 @@
import android.view.animation.Interpolator;
import com.android.app.animation.Interpolators;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.stack.AnimationFilter;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -85,7 +91,7 @@
return mEffectiveProperty;
}
};
- private AnimatorListenerAdapter mFinishListener = mock(AnimatorListenerAdapter.class);
+ private AnimatorListenerAdapter mFinishListener;
private AnimationProperties mAnimationProperties = new AnimationProperties() {
@Override
public AnimationFilter getAnimationFilter() {
@@ -104,6 +110,7 @@
@Before
public void setUp() {
mView = new View(getContext());
+ mFinishListener = mock(AnimatorListenerAdapter.class);
}
@Test
@@ -229,6 +236,32 @@
}
@Test
+ public void testListenerCallbackOrderAndTagState() {
+ mAnimationFilter.reset();
+ mAnimationFilter.animate(mProperty.getProperty());
+ mAnimationProperties.setCustomInterpolator(mEffectiveProperty, mTestInterpolator);
+ mAnimationProperties.setDuration(500);
+
+ // Validates that the onAnimationEnd function set by PropertyAnimator was run first.
+ doAnswer(invocation -> {
+ assertNull(mView.getTag(mProperty.getAnimatorTag()));
+ return null;
+ })
+ .when(mFinishListener)
+ .onAnimationEnd(any(Animator.class), anyBoolean());
+
+ // Begin the animation and verify it set state correctly
+ PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties);
+ ValueAnimator animator = ViewState.getChildTag(mView, mProperty.getAnimatorTag());
+ assertNotNull(animator);
+ assertNotNull(mView.getTag(mProperty.getAnimatorTag()));
+
+ // Terminate the animation to run end runners, and validate they executed.
+ animator.end();
+ verify(mFinishListener).onAnimationEnd(animator, false);
+ }
+
+ @Test
public void testIsAnimating() {
mAnimationFilter.reset();
mAnimationFilter.animate(mProperty.getProperty());
@@ -236,4 +269,4 @@
PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties);
assertTrue(PropertyAnimator.isAnimating(mView, mProperty));
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 27be4c8..df547ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
@@ -53,6 +54,7 @@
val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock()
val statusBarStateController: StatusBarStateController = mock()
val keyguardStateController: KeyguardStateController = mock()
+ val mSelectedUserInteractor: SelectedUserInteractor = mock()
val coordinator: SensitiveContentCoordinator =
DaggerTestSensitiveContentCoordinatorComponent
@@ -62,7 +64,8 @@
lockscreenUserManager,
keyguardUpdateMonitor,
statusBarStateController,
- keyguardStateController)
+ keyguardStateController,
+ mSelectedUserInteractor)
.coordinator
@Test
@@ -263,7 +266,8 @@
@BindsInstance lockscreenUserManager: NotificationLockscreenUserManager,
@BindsInstance keyguardUpdateMonitor: KeyguardUpdateMonitor,
@BindsInstance statusBarStateController: StatusBarStateController,
- @BindsInstance keyguardStateController: KeyguardStateController
+ @BindsInstance keyguardStateController: KeyguardStateController,
+ @BindsInstance selectedUserInteractor: SelectedUserInteractor,
): TestSensitiveContentCoordinatorComponent
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index 83f2a5d..b922ab3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -22,6 +22,7 @@
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -36,6 +37,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.NotificationChannel;
import android.content.Context;
import android.os.Handler;
import android.os.UserHandle;
@@ -51,6 +53,9 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -96,6 +101,7 @@
@Mock private UserTracker mUserTracker;
private final FakeSettings mSecureSettings = new FakeSettings();
private final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
+ private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
private NotificationEntry mEntry;
@@ -116,7 +122,8 @@
mStatusBarStateController,
mUserTracker,
mSecureSettings,
- mGlobalSettings);
+ mGlobalSettings,
+ mFeatureFlags);
mKeyguardNotificationVisibilityProvider = component.getProvider();
for (CoreStartable startable : component.getCoreStartables().values()) {
startable.start();
@@ -424,6 +431,7 @@
@Test
public void publicMode_settingsDisallow() {
+ mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
// GIVEN an 'unfiltered-keyguard-showing' state
setupUnfilteredState(mEntry);
@@ -433,12 +441,59 @@
when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
.thenReturn(false);
+ mEntry.setRanking(new RankingBuilder()
+ .setChannel(new NotificationChannel("1", "1", 4))
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
+ .setKey(mEntry.getKey()).build());
+
+ // THEN filter out the entry
+ assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
+ public void publicMode_settingsDisallow_mainThread() {
+ mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
+ // GIVEN an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // WHEN the notification's user is in public mode and settings are configured to disallow
+ // notifications in public mode
+ when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true);
+ when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
+ .thenReturn(false);
+
+ mEntry.setRanking(new RankingBuilder()
+ .setChannel(new NotificationChannel("1", "1", 4))
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
+ .setKey(mEntry.getKey()).build());
+
// THEN filter out the entry
assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
}
@Test
public void publicMode_notifDisallowed() {
+ mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+ NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+ channel.setLockscreenVisibility(VISIBILITY_SECRET);
+ // GIVEN an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // WHEN the notification's user is in public mode and settings are configured to disallow
+ // notifications in public mode
+ when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true);
+ mEntry.setRanking(new RankingBuilder()
+ .setKey(mEntry.getKey())
+ .setChannel(channel)
+ .setVisibilityOverride(VISIBILITY_SECRET).build());
+
+ // THEN filter out the entry
+ assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
+ public void publicMode_notifDisallowed_mainThread() {
+ mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
// GIVEN an 'unfiltered-keyguard-showing' state
setupUnfilteredState(mEntry);
@@ -506,6 +561,54 @@
}
@Test
+ public void notificationChannelVisibilityNoOverride() {
+ mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+ // GIVEN a VISIBILITY_PRIVATE notification
+ NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID));
+ entryBuilder.modifyNotification(mContext)
+ .setVisibility(VISIBILITY_PRIVATE);
+ mEntry = entryBuilder.build();
+ // ranking says secret because of DPC or Setting
+ mEntry.setRanking(new RankingBuilder()
+ .setKey(mEntry.getKey())
+ .setVisibilityOverride(VISIBILITY_SECRET)
+ .setImportance(IMPORTANCE_HIGH)
+ .build());
+
+ // WHEN we're in an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // THEN don't hide the entry based on visibility. (Redaction is handled elsewhere.)
+ assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
+ public void notificationChannelVisibilitySecret() {
+ mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+ // GIVEN a VISIBILITY_PRIVATE notification
+ NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID));
+ entryBuilder.modifyNotification(mContext)
+ .setVisibility(VISIBILITY_PRIVATE);
+ // And a VISIBILITY_SECRET NotificationChannel
+ NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_HIGH);
+ channel.setLockscreenVisibility(VISIBILITY_SECRET);
+ mEntry = entryBuilder.build();
+ // WHEN we're in an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+ when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true);
+ when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true);
+
+ mEntry.setRanking(new RankingBuilder(mEntry.getRanking())
+ .setChannel(channel)
+ .build());
+
+ // THEN hide the entry based on visibility.
+ assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
public void notificationVisibilityPrivate() {
// GIVEN a VISIBILITY_PRIVATE notification
NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
@@ -635,7 +738,8 @@
@BindsInstance SysuiStatusBarStateController statusBarStateController,
@BindsInstance UserTracker userTracker,
@BindsInstance SecureSettings secureSettings,
- @BindsInstance GlobalSettings globalSettings
+ @BindsInstance GlobalSettings globalSettings,
+ @BindsInstance FeatureFlagsClassic featureFlags
);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index ac11ff2..0a7dc4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -192,10 +192,26 @@
val isOnLockscreen by collectLastValue(underTest.isOnLockscreen)
keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(to = KeyguardState.GONE, transitionState = TransitionState.FINISHED)
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ value = 1f,
+ transitionState = TransitionState.FINISHED
+ )
)
assertThat(isOnLockscreen).isFalse()
+ // While progressing from lockscreen, should still be true
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ value = 0.8f,
+ transitionState = TransitionState.RUNNING
+ )
+ )
+ assertThat(isOnLockscreen).isTrue()
+
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
to = KeyguardState.LOCKSCREEN,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 700de53..cfd220b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -18,7 +18,9 @@
import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
+
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -38,7 +40,6 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.TestableResources;
-import android.view.HapticFeedbackConstants;
import android.view.ViewRootImpl;
import com.android.internal.logging.MetricsLogger;
@@ -47,17 +48,19 @@
import com.android.keyguard.logging.BiometricUnlockLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
@@ -107,8 +110,6 @@
@Mock
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
- private ScreenLifecycle mScreenLifecycle;
- @Mock
private StatusBarStateController mStatusBarStateController;
@Mock
private SessionTracker mSessionTracker;
@@ -122,6 +123,12 @@
private BiometricUnlockLogger mLogger;
@Mock
private ViewRootImpl mViewRootImpl;
+ @Mock
+ private DeviceEntryHapticsInteractor mDeviceEntryHapticsInteractor;
+ @Mock
+ private SelectedUserInteractor mSelectedUserInteractor;
+ @Mock
+ private BiometricUnlockInteractor mBiometricUnlockInteractor;
private final FakeSystemClock mSystemClock = new FakeSystemClock();
private FakeFeatureFlags mFeatureFlags;
private BiometricUnlockController mBiometricUnlockController;
@@ -158,7 +165,10 @@
mAuthController, mStatusBarStateController,
mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper,
mSystemClock,
- mFeatureFlags
+ mFeatureFlags,
+ mDeviceEntryHapticsInteractor,
+ () -> mSelectedUserInteractor,
+ mBiometricUnlockInteractor
);
biometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
biometricUnlockController.addListener(mBiometricUnlockEventsListener);
@@ -462,145 +472,23 @@
}
@Test
- public void onSideFingerprintSuccess_recentPowerButtonPress_noHaptic() {
- // GIVEN side fingerprint enrolled, last wake reason was power button
- when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
- when(mWakefulnessLifecycle.getLastWakeReason())
- .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-
- // GIVEN last wake time just occurred
- when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
+ public void onFingerprintSuccess_requestSuccessHaptic() {
// WHEN biometric fingerprint succeeds
givenFingerprintModeUnlockCollapsing();
mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
true);
- // THEN DO NOT vibrate the device
- verify(mVibratorHelper, never()).vibrateAuthSuccess(anyString());
+ // THEN always vibrate the device
+ verify(mDeviceEntryHapticsInteractor).vibrateSuccess();
}
@Test
- public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() {
- // GIVEN side fingerprint enrolled, last wake reason was power button
- when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
- when(mWakefulnessLifecycle.getLastWakeReason())
- .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-
- // GIVEN last wake time was 500ms ago
- when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
- mSystemClock.advanceTime(500);
-
- // WHEN biometric fingerprint succeeds
- givenFingerprintModeUnlockCollapsing();
- mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
- true);
-
- // THEN vibrate the device
- verify(mVibratorHelper).vibrateAuthSuccess(anyString());
- }
-
- @Test
- public void onSideFingerprintSuccess_oldPowerButtonPress_playOneWayHaptic() {
- // GIVEN oneway haptics is enabled
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
- // GIVEN side fingerprint enrolled, last wake reason was power button
- when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
- when(mWakefulnessLifecycle.getLastWakeReason())
- .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-
- // GIVEN last wake time was 500ms ago
- when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
- mSystemClock.advanceTime(500);
-
- // WHEN biometric fingerprint succeeds
- givenFingerprintModeUnlockCollapsing();
- mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
- true);
-
- // THEN vibrate the device
- verify(mVibratorHelper).performHapticFeedback(
- any(),
- eq(HapticFeedbackConstants.CONFIRM)
- );
- }
-
- @Test
- public void onSideFingerprintSuccess_recentGestureWakeUp_playHaptic() {
- // GIVEN side fingerprint enrolled, wakeup just happened
- when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
- when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
- // GIVEN last wake reason was from a gesture
- when(mWakefulnessLifecycle.getLastWakeReason())
- .thenReturn(PowerManager.WAKE_REASON_GESTURE);
-
- // WHEN biometric fingerprint succeeds
- givenFingerprintModeUnlockCollapsing();
- mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
- true);
-
- // THEN vibrate the device
- verify(mVibratorHelper).vibrateAuthSuccess(anyString());
- }
-
- @Test
- public void onSideFingerprintSuccess_recentGestureWakeUp_playOnewayHaptic() {
- //GIVEN oneway haptics is enabled
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
- // GIVEN side fingerprint enrolled, wakeup just happened
- when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
- when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
- // GIVEN last wake reason was from a gesture
- when(mWakefulnessLifecycle.getLastWakeReason())
- .thenReturn(PowerManager.WAKE_REASON_GESTURE);
-
- // WHEN biometric fingerprint succeeds
- givenFingerprintModeUnlockCollapsing();
- mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
- true);
-
- // THEN vibrate the device
- verify(mVibratorHelper).performHapticFeedback(
- any(),
- eq(HapticFeedbackConstants.CONFIRM)
- );
- }
-
- @Test
- public void onSideFingerprintFail_alwaysPlaysHaptic() {
- // GIVEN side fingerprint enrolled, last wake reason was recent power button
- when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
- when(mWakefulnessLifecycle.getLastWakeReason())
- .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
- when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
+ public void onFingerprintFail_requestErrorHaptic() {
// WHEN biometric fingerprint fails
mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
// THEN always vibrate the device
- verify(mVibratorHelper).vibrateAuthError(anyString());
- }
-
- @Test
- public void onSideFingerprintFail_alwaysPlaysOneWayHaptic() {
- // GIVEN oneway haptics is enabled
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
- // GIVEN side fingerprint enrolled, last wake reason was recent power button
- when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
- when(mWakefulnessLifecycle.getLastWakeReason())
- .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
- when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
- // WHEN biometric fingerprint fails
- mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
-
- // THEN always vibrate the device
- verify(mVibratorHelper).performHapticFeedback(
- any(),
- eq(HapticFeedbackConstants.REJECT)
- );
+ verify(mDeviceEntryHapticsInteractor).vibrateError();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index f5b7ca8..6fecbb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -23,7 +23,6 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -44,6 +43,7 @@
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -85,6 +85,7 @@
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private ConfigurationController mConfigurationController;
@Mock private UserTracker mUserTracker;
+ @Mock private DozeInteractor mDozeInteractor;
@Captor private ArgumentCaptor<BatteryStateChangeCallback> mBatteryStateChangeCallback;
/**
@@ -128,7 +129,8 @@
mKeyguardUpdateMonitor,
mConfigurationController,
mStatusBarStateController,
- mUserTracker
+ mUserTracker,
+ mDozeInteractor
);
verify(mBatteryController).addCallback(mBatteryStateChangeCallback.capture());
@@ -186,9 +188,7 @@
@Test
public void testGetAlwaysOn_whenBatterySaverCallback() {
- DozeParameters.Callback callback = mock(DozeParameters.Callback.class);
- mDozeParameters.addCallback(callback);
-
+ reset(mDozeInteractor);
when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
when(mBatteryController.isAodPowerSave()).thenReturn(true);
@@ -196,16 +196,16 @@
mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1");
mBatteryStateChangeCallback.getValue().onPowerSaveChanged(true);
- verify(callback, times(2)).onAlwaysOnChange();
+ verify(mDozeInteractor, times(2)).setAodAvailable(anyBoolean());
verify(mScreenOffAnimationController, times(2)).onAlwaysOnChanged(false);
assertThat(mDozeParameters.getAlwaysOn()).isFalse();
reset(mScreenOffAnimationController);
- reset(callback);
+ reset(mDozeInteractor);
when(mBatteryController.isAodPowerSave()).thenReturn(false);
mBatteryStateChangeCallback.getValue().onPowerSaveChanged(true);
- verify(callback).onAlwaysOnChange();
+ verify(mDozeInteractor).setAodAvailable(anyBoolean());
verify(mScreenOffAnimationController).onAlwaysOnChanged(true);
assertThat(mDozeParameters.getAlwaysOn()).isTrue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 03f5f00..3556703 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -30,9 +30,11 @@
import androidx.test.filters.SmallTest;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.doze.util.BurnInHelperKt;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.core.FakeLogBuffer;
+import com.android.systemui.res.R;
import org.junit.After;
import org.junit.Before;
@@ -51,8 +53,7 @@
private static final float OPAQUE = 1.f;
private static final float TRANSPARENT = 0.f;
- @Mock
- private Resources mResources;
+ @Mock private Resources mResources;
private KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
private KeyguardClockPositionAlgorithm.Result mClockPosition;
@@ -80,7 +81,8 @@
.mockStatic(BurnInHelperKt.class)
.startMocking();
- mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm();
+ LogBuffer logBuffer = FakeLogBuffer.Factory.Companion.create();
+ mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm(logBuffer);
when(mResources.getDimensionPixelSize(anyInt())).thenReturn(0);
mClockPositionAlgorithm.loadDimens(mResources);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 45e9224..46b3996 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -93,6 +93,7 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.google.common.truth.Truth;
@@ -147,6 +148,7 @@
@Mock private WindowInsetsController mWindowInsetsController;
@Mock private TaskbarDelegate mTaskbarDelegate;
@Mock private StatusBarKeyguardViewManager.KeyguardViewManagerCallback mCallback;
+ @Mock private SelectedUserInteractor mSelectedUserInteractor;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
@@ -212,7 +214,8 @@
mock(KeyguardTransitionInteractor.class),
StandardTestDispatcher(null, null),
() -> mock(WindowManagerLockscreenVisibilityInteractor.class),
- () -> mock(KeyguardDismissActionInteractor.class)) {
+ () -> mock(KeyguardDismissActionInteractor.class),
+ mSelectedUserInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -715,7 +718,8 @@
mock(KeyguardTransitionInteractor.class),
StandardTestDispatcher(null, null),
() -> mock(WindowManagerLockscreenVisibilityInteractor.class),
- () -> mock(KeyguardDismissActionInteractor.class)) {
+ () -> mock(KeyguardDismissActionInteractor.class),
+ mSelectedUserInteractor) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index e6f8c48..9aafee4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import android.os.Handler
import android.os.PowerManager
import android.testing.AndroidTestingRunner
@@ -80,10 +82,14 @@
@Mock
private lateinit var handler: Handler
+ private lateinit var featureFlags: FakeFeatureFlags
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
+ featureFlags = FakeFeatureFlags().apply {
+ set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false)
+ }
controller = UnlockedScreenOffAnimationController(
context,
wakefulnessLifecycle,
@@ -95,7 +101,8 @@
dagger.Lazy<NotificationShadeWindowController> { notifShadeWindowController },
interactionJankMonitor,
powerManager,
- handler = handler
+ handler = handler,
+ featureFlags,
)
controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 812e91b..610fede 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -60,6 +60,8 @@
override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
+ override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
+
fun setDataEnabled(enabled: Boolean) {
_dataEnabled.value = enabled
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index 57f97ec..1f8cc54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -123,6 +123,7 @@
carrierNetworkChange = testCase.carrierNetworkChange,
roaming = testCase.roaming,
name = "demo name",
+ slice = testCase.slice,
)
fakeNetworkEventFlow.value = networkModel
@@ -142,6 +143,7 @@
launch { conn.carrierName.collect {} }
launch { conn.isEmergencyOnly.collect {} }
launch { conn.dataConnectionState.collect {} }
+ launch { conn.hasPrioritizedNetworkCapabilities.collect {} }
}
return job
}
@@ -165,6 +167,7 @@
.isEqualTo(NetworkNameModel.IntentDerived(model.name))
assertThat(conn.carrierName.value)
.isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}"))
+ assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice)
// TODO(b/261029387): check these once we start handling them
assertThat(conn.isEmergencyOnly.value).isFalse()
@@ -190,6 +193,7 @@
val carrierNetworkChange: Boolean,
val roaming: Boolean,
val name: String,
+ val slice: Boolean,
) {
override fun toString(): String {
return "INPUT(level=$level, " +
@@ -200,7 +204,8 @@
"activity=$activity, " +
"carrierNetworkChange=$carrierNetworkChange, " +
"roaming=$roaming, " +
- "name=$name)"
+ "name=$name," +
+ "slice=$slice)"
}
// Convenience for iterating test data and creating new cases
@@ -214,6 +219,7 @@
carrierNetworkChange: Boolean? = null,
roaming: Boolean? = null,
name: String? = null,
+ slice: Boolean? = null,
): TestCase =
TestCase(
level = level ?: this.level,
@@ -225,6 +231,7 @@
carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange,
roaming = roaming ?: this.roaming,
name = name ?: this.name,
+ slice = slice ?: this.slice,
)
}
@@ -254,6 +261,7 @@
// false first so the base case doesn't have roaming set (more common)
private val roaming = listOf(false, true)
private val names = listOf("name 1", "name 2")
+ private val slice = listOf(false, true)
@Parameters(name = "{0}") @JvmStatic fun data() = testData()
@@ -291,6 +299,7 @@
carrierNetworkChange.first(),
roaming.first(),
names.first(),
+ slice.first(),
)
val tail =
@@ -303,6 +312,7 @@
carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
roaming.map { baseCase.modifiedBy(roaming = it) },
names.map { baseCase.modifiedBy(name = it) },
+ slice.map { baseCase.modifiedBy(slice = it) }
)
.flatten()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index 2712b70..d918fa8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -549,6 +549,7 @@
launch { conn.carrierName.collect {} }
launch { conn.isEmergencyOnly.collect {} }
launch { conn.dataConnectionState.collect {} }
+ launch { conn.hasPrioritizedNetworkCapabilities.collect {} }
}
return job
}
@@ -574,6 +575,7 @@
.isEqualTo(NetworkNameModel.IntentDerived(model.name))
assertThat(conn.carrierName.value)
.isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}"))
+ assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice)
// TODO(b/261029387) check these once we start handling them
assertThat(conn.isEmergencyOnly.value).isFalse()
@@ -599,6 +601,7 @@
assertThat(conn.isEmergencyOnly.value).isFalse()
assertThat(conn.isGsm.value).isFalse()
assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
+ assertThat(conn.hasPrioritizedNetworkCapabilities.value).isFalse()
job.cancel()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index ede02d1..1c21ebe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.net.ConnectivityManager
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.TelephonyCallback
@@ -80,6 +81,7 @@
)
private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>()
private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>()
+ private val connectivityManager = mock<ConnectivityManager>()
private val subscriptionModel =
MutableStateFlow(
@@ -678,6 +680,7 @@
subscriptionModel,
DEFAULT_NAME_MODEL,
SEP,
+ connectivityManager,
telephonyManager,
systemUiCarrierConfig = mock(),
fakeBroadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 8ef82c9..ba64265 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -19,6 +19,8 @@
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN
import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
@@ -85,7 +87,9 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -107,6 +111,7 @@
private lateinit var underTest: MobileConnectionRepositoryImpl
private lateinit var connectionsRepo: FakeMobileConnectionsRepository
+ @Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: MobileInputLogger
@Mock private lateinit var tableLogger: TableLogBuffer
@@ -144,6 +149,7 @@
subscriptionModel,
DEFAULT_NAME_MODEL,
SEP,
+ connectivityManager,
telephonyManager,
systemUiCarrierConfig,
fakeBroadcastDispatcher,
@@ -904,6 +910,45 @@
assertThat(latest).isFalse()
}
+ @Test
+ fun hasPrioritizedCaps_defaultFalse() {
+ assertThat(underTest.hasPrioritizedNetworkCapabilities.value).isFalse()
+ }
+
+ @Test
+ fun hasPrioritizedCaps_trueWhenAvailable() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.hasPrioritizedNetworkCapabilities)
+
+ val callback: NetworkCallback =
+ withArgCaptor<NetworkCallback> {
+ verify(connectivityManager).registerNetworkCallback(any(), capture())
+ }
+
+ callback.onAvailable(mock())
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun hasPrioritizedCaps_becomesFalseWhenNetworkLost() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.hasPrioritizedNetworkCapabilities)
+
+ val callback: NetworkCallback =
+ withArgCaptor<NetworkCallback> {
+ verify(connectivityManager).registerNetworkCallback(any(), capture())
+ }
+
+ callback.onAvailable(mock())
+
+ assertThat(latest).isTrue()
+
+ callback.onLost(mock())
+
+ assertThat(latest).isFalse()
+ }
+
private inline fun <reified T> getTelephonyCallbackForType(): T {
return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index 852ed20..889f60a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.net.ConnectivityManager
import android.telephony.ServiceState
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.CarrierNetworkListener
@@ -96,6 +97,7 @@
private lateinit var underTest: MobileConnectionRepositoryImpl
private lateinit var connectionsRepo: FakeMobileConnectionsRepository
+ @Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: MobileInputLogger
@Mock private lateinit var tableLogger: TableLogBuffer
@@ -129,6 +131,7 @@
subscriptionModel,
DEFAULT_NAME,
SEP,
+ connectivityManager,
telephonyManager,
systemUiCarrierConfig,
fakeBroadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 9148c75..18ba6c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -180,6 +180,7 @@
MobileConnectionRepositoryImpl.Factory(
context,
fakeBroadcastDispatcher,
+ connectivityManager,
telephonyManager = telephonyManager,
bgDispatcher = dispatcher,
logger = logger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index de2b6a8..5f4d7bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -48,6 +48,8 @@
NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
)
+ override val showSliceAttribution = MutableStateFlow(false)
+
override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo mode"))
override val carrierName = MutableStateFlow("demo mode")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
index 218fd33..3936bed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -22,13 +22,15 @@
import android.testing.TestableLooper.RunWithLooper
import android.testing.ViewUtils
import android.view.View
+import android.widget.FrameLayout
import android.widget.ImageView
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
@@ -58,8 +60,8 @@
private lateinit var testableLooper: TestableLooper
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
+ private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
- @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var tableLogBuffer: TableLogBuffer
@Mock private lateinit var viewLogger: MobileViewLogger
@Mock private lateinit var constants: ConnectivityConstants
@@ -246,7 +248,8 @@
testableLooper.processAllMessages()
val color = 0x12345678
- view.onDarkChanged(arrayListOf(), 1.0f, color)
+ val contrast = 0x12344321
+ view.onDarkChangedWithContrast(arrayListOf(), color, contrast)
testableLooper.processAllMessages()
assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
@@ -267,7 +270,8 @@
testableLooper.processAllMessages()
val color = 0x23456789
- view.setStaticDrawableColor(color)
+ val contrast = 0x12344321
+ view.setStaticDrawableColor(color, contrast)
testableLooper.processAllMessages()
assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
@@ -275,6 +279,35 @@
ViewUtils.detachView(view)
}
+ @Test
+ fun colorChange_layersUpdateWithContrast() {
+ // Allow the slice, and set it to visible. This cause us to use special color logic
+ flags.set(Flags.NEW_NETWORK_SLICE_UI, true)
+ interactor.showSliceAttribution.value = true
+ createViewModel()
+
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ val color = 0x23456789
+ val contrast = 0x12344321
+ view.setStaticDrawableColor(color, contrast)
+
+ testableLooper.processAllMessages()
+
+ assertThat(view.getNetTypeContainer().backgroundTintList).isEqualTo(color.colorState())
+ assertThat(view.getNetTypeView().imageTintList).isEqualTo(contrast.colorState())
+
+ ViewUtils.detachView(view)
+ }
+
private fun View.getGroupView(): View {
return this.requireViewById(R.id.mobile_group)
}
@@ -283,10 +316,20 @@
return this.requireViewById(R.id.mobile_signal)
}
+ private fun View.getNetTypeContainer(): FrameLayout {
+ return this.requireViewById(R.id.mobile_type_container)
+ }
+
+ private fun View.getNetTypeView(): ImageView {
+ return this.requireViewById(R.id.mobile_type)
+ }
+
private fun View.getDotView(): View {
return this.requireViewById(R.id.status_bar_dot)
}
+ private fun Int.colorState() = ColorStateList.valueOf(this)
+
private fun createViewModel() {
viewModelCommon =
MobileIconViewModel(
@@ -294,6 +337,7 @@
interactor,
airplaneModeInteractor,
constants,
+ flags,
testScope.backgroundScope,
)
viewModel = QsMobileIconViewModel(viewModelCommon)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index 1878329..1d5487f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -16,8 +16,11 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
@@ -47,12 +50,14 @@
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@RunWith(AndroidJUnit4::class)
class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
private lateinit var commonImpl: MobileIconViewModelCommon
private lateinit var homeIcon: HomeMobileIconViewModel
@@ -65,6 +70,7 @@
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
private val connectivityRepository = FakeConnectivityRepository()
+ private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var constants: ConnectivityConstants
@@ -133,6 +139,7 @@
interactor,
airplaneModeInteractor,
constants,
+ flags,
testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 796d5ec..c831e62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
@@ -26,7 +27,12 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.Flags.NEW_NETWORK_SLICE_UI
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.res.R
import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -54,12 +60,14 @@
import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@RunWith(AndroidJUnit4::class)
class MobileIconViewModelTest : SysuiTestCase() {
private var connectivityRepository = FakeConnectivityRepository()
@@ -74,6 +82,7 @@
@Mock private lateinit var tableLogBuffer: TableLogBuffer
@Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
+ private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -595,6 +604,46 @@
containerJob.cancel()
}
+ @Test
+ fun netTypeBackground_flagOff_isNull() =
+ testScope.runTest {
+ flags.set(NEW_NETWORK_SLICE_UI, false)
+ createAndSetViewModel()
+
+ val latest by collectLastValue(underTest.networkTypeBackground)
+
+ repository.hasPrioritizedNetworkCapabilities.value = true
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun netTypeBackground_flagOn_nullWhenNoPrioritizedCapabilities() =
+ testScope.runTest {
+ flags.set(NEW_NETWORK_SLICE_UI, true)
+ createAndSetViewModel()
+
+ val latest by collectLastValue(underTest.networkTypeBackground)
+
+ repository.hasPrioritizedNetworkCapabilities.value = false
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun netTypeBackground_flagOn_notNullWhenPrioritizedCapabilities() =
+ testScope.runTest {
+ flags.set(NEW_NETWORK_SLICE_UI, true)
+ createAndSetViewModel()
+
+ val latest by collectLastValue(underTest.networkTypeBackground)
+
+ repository.hasPrioritizedNetworkCapabilities.value = true
+
+ assertThat(latest)
+ .isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background, null))
+ }
+
private fun createAndSetViewModel() {
underTest =
MobileIconViewModel(
@@ -602,6 +651,7 @@
interactor,
airplaneModeInteractor,
constants,
+ flags,
testScope.backgroundScope,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index eb6f2f8..f3e334e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -16,9 +16,12 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -41,15 +44,18 @@
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@RunWith(AndroidJUnit4::class)
class MobileIconsViewModelTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsViewModel
private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@@ -77,6 +83,7 @@
interactor,
airplaneModeInteractor,
constants,
+ flags,
testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
index e44ff8e..28d632d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
@@ -41,6 +41,7 @@
* setting mobile connected && validated, since the default state is disconnected && not
* validated
*/
+ @JvmOverloads
fun setMobileConnected(
default: Boolean = true,
validated: Boolean = true,
@@ -53,6 +54,7 @@
}
/** Similar convenience method for ethernet */
+ @JvmOverloads
fun setEthernetConnected(
default: Boolean = true,
validated: Boolean = true,
@@ -64,6 +66,7 @@
)
}
+ @JvmOverloads
fun setWifiConnected(
default: Boolean = true,
validated: Boolean = true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
index b4039d9..028a58c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
@@ -77,9 +77,10 @@
fun onDarkChanged_bindingReceivesIconAndDecorTint() {
val view = createAndInitView()
- view.onDarkChanged(arrayListOf(), 1.0f, 0x12345678)
+ view.onDarkChangedWithContrast(arrayListOf(), 0x12345678, 0x12344321)
assertThat(binding.iconTint).isEqualTo(0x12345678)
+ assertThat(binding.contrastTint).isEqualTo(0x12344321)
assertThat(binding.decorTint).isEqualTo(0x12345678)
}
@@ -87,9 +88,10 @@
fun setStaticDrawableColor_bindingReceivesIconTint() {
val view = createAndInitView()
- view.setStaticDrawableColor(0x12345678)
+ view.setStaticDrawableColor(0x12345678, 0x12344321)
assertThat(binding.iconTint).isEqualTo(0x12345678)
+ assertThat(binding.contrastTint).isEqualTo(0x12344321)
}
@Test
@@ -144,13 +146,15 @@
inner class TestBinding : ModernStatusBarViewBinding {
var iconTint: Int? = null
+ var contrastTint: Int? = null
var decorTint: Int? = null
var onVisibilityStateChangedCalled: Boolean = false
var shouldIconBeVisibleInternal: Boolean = true
- override fun onIconTintChanged(newTint: Int) {
+ override fun onIconTintChanged(newTint: Int, contrastTint: Int) {
iconTint = newTint
+ this.contrastTint = contrastTint
}
override fun onDecorTintChanged(newTint: Int) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
index c2f5665..3126362 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
@@ -201,11 +201,11 @@
testScope.runTest {
val latest by collectLastValue(underTest.isWifiDefault)
- val wifiEntry =
+ val mergedEntry =
mock<MergedCarrierEntry>().apply {
whenever(this.isDefaultNetwork).thenReturn(true)
}
- whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
getCallback().onWifiEntriesChanged()
assertThat(latest).isTrue()
@@ -229,11 +229,11 @@
testScope.runTest {
val latest by collectLastValue(underTest.isWifiDefault)
- val wifiEntry =
+ val mergedEntry =
mock<MergedCarrierEntry>().apply {
whenever(this.isDefaultNetwork).thenReturn(false)
}
- whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
getCallback().onWifiEntriesChanged()
assertThat(latest).isFalse()
@@ -526,13 +526,14 @@
testScope.runTest {
val latest by collectLastValue(underTest.wifiNetwork)
- val wifiEntry =
+ val mergedEntry =
mock<MergedCarrierEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
whenever(this.level).thenReturn(3)
whenever(this.subscriptionId).thenReturn(567)
+ whenever(this.isDefaultNetwork).thenReturn(true)
}
- whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
getCallback().onWifiEntriesChanged()
assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
@@ -546,11 +547,12 @@
testScope.runTest {
val latest by collectLastValue(underTest.wifiNetwork)
- val wifiEntry =
+ val mergedEntry =
mock<MergedCarrierEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.isDefaultNetwork).thenReturn(true)
}
- whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
whenever(wifiManager.maxSignalLevel).thenReturn(5)
getCallback().onWifiEntriesChanged()
@@ -566,12 +568,13 @@
testScope.runTest {
val latest by collectLastValue(underTest.wifiNetwork)
- val wifiEntry =
+ val mergedEntry =
mock<MergedCarrierEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
+ whenever(this.isDefaultNetwork).thenReturn(true)
}
- whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
getCallback().onWifiEntriesChanged()
@@ -628,11 +631,12 @@
testScope.runTest {
val latest by collectLastValue(underTest.wifiNetwork)
- val wifiEntry =
+ val mergedEntry =
mock<MergedCarrierEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(false)
}
- whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
getCallback().onWifiEntriesChanged()
assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
@@ -717,12 +721,14 @@
testScope.runTest {
val latest by collectLastValue(underTest.wifiNetwork)
- val wifiEntry =
+ val mergedEntry =
mock<MergedCarrierEntry>().apply {
whenever(this.isPrimaryNetwork).thenReturn(true)
whenever(this.level).thenReturn(3)
+ whenever(this.isDefaultNetwork).thenReturn(true)
}
- whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
getCallback().onWifiEntriesChanged()
assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
@@ -730,6 +736,7 @@
// WHEN we lose our current network
whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(null)
getCallback().onWifiEntriesChanged()
// THEN we update to no network
@@ -767,6 +774,56 @@
}
@Test
+ fun wifiNetwork_carrierMerged_default_usesCarrierMergedInfo() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val mergedEntry =
+ mock<MergedCarrierEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(3)
+ whenever(this.isDefaultNetwork).thenReturn(true)
+ }
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(1)
+ whenever(this.title).thenReturn(TITLE)
+ }
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+ }
+
+ @Test
+ fun wifiNetwork_carrierMerged_notDefault_usesConnectedInfo() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiNetwork)
+
+ val mergedEntry =
+ mock<MergedCarrierEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(3)
+ whenever(this.isDefaultNetwork).thenReturn(false)
+ }
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.level).thenReturn(1)
+ whenever(this.title).thenReturn(TITLE)
+ }
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+
+ getCallback().onWifiEntriesChanged()
+
+ assertThat(latest is WifiNetworkModel.Active).isTrue()
+ }
+
+ @Test
fun secondaryNetworks_activeEntriesEmpty_isEmpty() =
testScope.runTest {
featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index a27f899..d75a452 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -25,9 +25,9 @@
import android.view.ViewGroup
import android.widget.ImageView
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
@@ -229,7 +229,8 @@
testableLooper.processAllMessages()
val color = 0x12345678
- view.onDarkChanged(arrayListOf(), 1.0f, color)
+ val contrast = 0x12344321
+ view.onDarkChangedWithContrast(arrayListOf(), color, contrast)
testableLooper.processAllMessages()
assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
@@ -244,7 +245,8 @@
testableLooper.processAllMessages()
val color = 0x23456789
- view.setStaticDrawableColor(color)
+ val contrast = 0x12344321
+ view.setStaticDrawableColor(color, contrast)
testableLooper.processAllMessages()
assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
index 1250228..d33806e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy
+import com.android.systemui.flags.FakeFeatureFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.ViewUtils
@@ -77,11 +78,13 @@
private lateinit var view: FrameLayout
private lateinit var testableLooper: TestableLooper
private lateinit var keyguardQsUserSwitchController: KeyguardQsUserSwitchController
+ private lateinit var featureFlags: FakeFeatureFlags
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
+ featureFlags = FakeFeatureFlags()
view = LayoutInflater.from(context)
.inflate(R.layout.keyguard_qs_user_switch, null) as FrameLayout
@@ -98,6 +101,7 @@
dozeParameters,
screenOffAnimationController,
userSwitchDialogController,
+ featureFlags,
uiEventLogger)
ViewUtils.attachView(view)
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 cae892f..e6b09e3 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
@@ -38,6 +38,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import dagger.Lazy;
@@ -67,6 +68,8 @@
@Mock
private Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
@Mock
+ private SelectedUserInteractor mSelectedUserInteractor;
+ @Mock
private KeyguardUpdateMonitorLogger mLogger;
@Mock
private FeatureFlags mFeatureFlags;
@@ -84,7 +87,8 @@
mKeyguardUnlockAnimationControllerLazy,
mLogger,
mDumpManager,
- mFeatureFlags);
+ mFeatureFlags,
+ mSelectedUserInteractor);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
new file mode 100644
index 0000000..60fe7d2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
@@ -0,0 +1,50 @@
+package com.android.systemui.user.domain.interactor
+
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SelectedUserInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: SelectedUserInteractor
+
+ private val userRepository = FakeUserRepository()
+
+ @Before
+ fun setUp() {
+ userRepository.setUserInfos(USER_INFOS)
+ underTest =
+ SelectedUserInteractor(
+ userRepository,
+ FakeFeatureFlagsClassic().apply { set(Flags.REFACTOR_GETCURRENTUSER, true) }
+ )
+ }
+
+ @Test
+ fun getSelectedUserIdReturnsId() {
+ runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) }
+
+ val actualId = underTest.getSelectedUserId()
+
+ assertThat(actualId).isEqualTo(USER_INFOS[0].id)
+ }
+
+ companion object {
+ private val USER_INFOS =
+ listOf(
+ UserInfo(100, "First user", 0),
+ UserInfo(101, "Second user", 0),
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index c56266d..1968d75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -88,7 +88,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
-class UserInteractorTest : SysuiTestCase() {
+class UserSwitcherInteractorTest : SysuiTestCase() {
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var manager: UserManager
@@ -102,7 +102,7 @@
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- private lateinit var underTest: UserInteractor
+ private lateinit var underTest: UserSwitcherInteractor
private lateinit var spyContext: Context
private lateinit var testScope: TestScope
@@ -665,8 +665,8 @@
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- val callback1: UserInteractor.UserCallback = mock()
- val callback2: UserInteractor.UserCallback = mock()
+ val callback1: UserSwitcherInteractor.UserCallback = mock()
+ val callback2: UserSwitcherInteractor.UserCallback = mock()
underTest.addCallback(callback1)
underTest.addCallback(callback2)
runCurrent()
@@ -1117,7 +1117,7 @@
userRepository.setSelectedUserInfo(userInfo)
}
underTest =
- UserInteractor(
+ UserSwitcherInteractor(
applicationContext = spyContext,
repository = userRepository,
activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index a8db368..7041eab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -42,7 +42,7 @@
import com.android.systemui.user.domain.interactor.GuestUserInteractor
import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -243,9 +243,8 @@
userRepository.setSelectedUserInfo(USER_0)
}
return StatusBarUserChipViewModel(
- context = context,
interactor =
- UserInteractor(
+ UserSwitcherInteractor(
applicationContext = context,
repository = userRepository,
activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index c236b12..686f492 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -42,7 +42,7 @@
import com.android.systemui.user.domain.interactor.GuestUserInteractor
import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.util.mockito.any
@@ -157,8 +157,8 @@
underTest =
UserSwitcherViewModel(
- userInteractor =
- UserInteractor(
+ userSwitcherInteractor =
+ UserSwitcherInteractor(
applicationContext = context,
repository = userRepository,
activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java
index a853f1d..c69f5c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java
@@ -209,6 +209,11 @@
new int[]{MetricsEvent.POWER_OVERHEAT_ALARM,
MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM},
Events.VolumeDialogEvent.USB_OVERHEAT_ALARM_DISMISSED},
+ {Events.EVENT_ODI_CAPTIONS_CLICK, null, "writeEvent odi_captions_click", null,
+ Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_CLICKED},
+ {Events.EVENT_ODI_CAPTIONS_TOOLTIP_CLICK, null,
+ "writeEvent odi_captions_tooltip_click", null,
+ Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED}
});
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index b8f747b..c4c7472 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -53,11 +53,12 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
+import androidx.test.core.view.MotionEventBuilder;
import androidx.test.filters.SmallTest;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.Prefs;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.dump.DumpManager;
@@ -66,6 +67,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -76,6 +78,8 @@
import dagger.Lazy;
+import junit.framework.Assert;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -699,6 +703,60 @@
}
}
+ /**
+ * The click should be a single tap, thus we inject a down and an up event.
+ */
+ @Test
+ public void clickCaptionsButton_logsUiEvent() {
+ UiEventLoggerFake logger = new UiEventLoggerFake();
+ Events.sUiEventLogger = logger;
+ MotionEvent down = MotionEventBuilder.newBuilder()
+ .setAction(MotionEvent.ACTION_DOWN).build();
+ MotionEvent up = MotionEventBuilder.newBuilder()
+ .setAction(MotionEvent.ACTION_UP).build();
+
+ mODICaptionsIcon.onTouchEvent(down);
+ mODICaptionsIcon.onTouchEvent(up);
+ mTestableLooper.moveTimeForward(300); // to confirm it was only a single tap
+ mTestableLooper.processAllMessages();
+
+ boolean foundCaptionLog = false;
+ for (UiEventLoggerFake.FakeUiEvent event : logger.getLogs()) {
+ if (event.eventId
+ == Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_CLICKED.getId()) {
+ foundCaptionLog = true;
+ break;
+ }
+ }
+ Assert.assertTrue("Did not log the captions button click.", foundCaptionLog);
+ }
+
+ /**
+ * Pressing the small x button at top right dismisses the captions tooltip.
+ */
+ @Test
+ public void dismissCaptionsTooltip_logsUiEvent() {
+ UiEventLoggerFake logger = new UiEventLoggerFake();
+ Events.sUiEventLogger = logger;
+ mDialog.showCaptionsTooltip();
+ assumeNotNull(mDialog.mODICaptionsTooltipView);
+ View dismissButton = mDialog.mODICaptionsTooltipView.findViewById(R.id.dismiss);
+
+ dismissButton.performClick();
+
+ boolean foundCaptionLog = false;
+ for (UiEventLoggerFake.FakeUiEvent event : logger.getLogs()) {
+ if (event.eventId
+ == Events.VolumeDialogEvent.VOLUME_DIALOG_ODI_CAPTIONS_TOOLTIP_CLICKED.getId()
+ ) {
+ foundCaptionLog = true;
+ break;
+ }
+ }
+ Assert.assertTrue("Did not log the captions tooltip dismiss button click.",
+ foundCaptionLog);
+ }
+
@After
public void teardown() {
// Detailed logs to track down timeout issues in b/299491332
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 c832702..a42fa41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -156,7 +156,8 @@
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
-import com.android.systemui.user.domain.interactor.UserInteractor;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.Bubble;
@@ -324,6 +325,8 @@
@Mock
private ShadeWindowLogger mShadeWindowLogger;
@Mock
+ private SelectedUserInteractor mSelectedUserInteractor;
+ @Mock
private NotifPipelineFlags mNotifPipelineFlags;
@Mock
private Icon mAppBubbleIcon;
@@ -433,6 +436,7 @@
keyguardInteractor,
featureFlags,
mock(KeyguardSecurityModel.class),
+ mSelectedUserInteractor,
powerInteractor);
ResourcesSplitShadeStateController splitShadeStateController =
@@ -450,7 +454,7 @@
keyguardTransitionInteractor,
powerInteractor,
new FakeUserSetupRepository(),
- mock(UserInteractor.class),
+ mock(UserSwitcherInteractor.class),
new SharedNotificationContainerInteractor(
configurationRepository,
mContext,
@@ -476,7 +480,8 @@
mAuthController,
mShadeExpansionStateManager,
() -> mShadeInteractor,
- mShadeWindowLogger
+ mShadeWindowLogger,
+ () -> mSelectedUserInteractor
);
mNotificationShadeWindowController.fetchWindowRootView();
mNotificationShadeWindowController.attach();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index f7e0120..6ef812b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui;
+import static com.android.systemui.Flags.FLAG_EXAMPLE_FLAG;
+
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -25,6 +27,7 @@
import android.os.Looper;
import android.os.MessageQueue;
import android.os.ParcelFileDescriptor;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.DexmakerShareClassLoaderRule;
import android.testing.LeakCheck;
import android.testing.TestWithLooperRule;
@@ -68,6 +71,9 @@
new AndroidXAnimatorIsolationRule();
@Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Rule
public SysuiTestableContext mContext = new SysuiTestableContext(
InstrumentationRegistry.getContext(), getLeakCheck());
@Rule
@@ -88,6 +94,10 @@
if (isRobolectricTest()) {
mContext = mContext.createDefaultDisplayContext();
}
+ // Set the value of a single gantry flag inside the com.android.systemui package to
+ // ensure all flags in that package are faked (and thus require a value to be set).
+ mSetFlagsRule.disableFlags(FLAG_EXAMPLE_FLAG);
+
mDependency = SysuiTestDependencyKt.installSysuiTestDependency(mContext);
mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(
mContext,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 10b284a..5dcc742 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -47,7 +47,7 @@
}
override fun getDimensionPixelSize(id: Int): Int {
- throw IllegalStateException("Don't use for tests")
+ return 0
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 1a8c583..08adda3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -1,6 +1,8 @@
package com.android.systemui.communal.data.repository
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.data.model.CommunalWidgetMetadata
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -8,8 +10,16 @@
class FakeCommunalWidgetRepository : CommunalWidgetRepository {
private val _stopwatchAppWidgetInfo = MutableStateFlow<CommunalAppWidgetInfo?>(null)
override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> = _stopwatchAppWidgetInfo
+ override var communalWidgetAllowlist: List<CommunalWidgetMetadata> = emptyList()
+
+ private val _communalWidgets = MutableStateFlow<List<CommunalWidgetContentModel>>(emptyList())
+ override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = _communalWidgets
fun setStopwatchAppWidgetInfo(appWidgetInfo: CommunalAppWidgetInfo) {
_stopwatchAppWidgetInfo.value = appWidgetInfo
}
+
+ fun setCommunalWidgets(inventory: List<CommunalWidgetContentModel>) {
+ _communalWidgets.value = inventory
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index e91e955..85261123 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -34,7 +34,7 @@
get() = _isFingerprintAuthCurrentlyAllowed
private val _isFaceAuthEnrolledAndEnabled = MutableStateFlow(false)
- override val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
+ override val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean>
get() = _isFaceAuthEnrolledAndEnabled
private val _isFaceAuthCurrentlyAllowed = MutableStateFlow(false)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 9de7ad8..fae49b1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -29,12 +29,12 @@
import com.android.systemui.keyguard.shared.model.StatusBarState
import dagger.Binds
import dagger.Module
+import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import javax.inject.Inject
/** Fake implementation of [KeyguardRepository] */
@SysUISingleton
@@ -182,8 +182,8 @@
_lastDozeTapToWakePosition.value = position
}
- fun setAodAvailable(isAodAvailable: Boolean) {
- _isAodAvailable.value = isAodAvailable
+ override fun setAodAvailable(value: Boolean) {
+ _isAodAvailable.value = value
}
fun setDreaming(isDreaming: Boolean) {
@@ -202,8 +202,8 @@
_dozeAmount.value = dozeAmount
}
- fun setBiometricUnlockState(state: BiometricUnlockModel) {
- _biometricUnlockState.tryEmit(state)
+ override fun setBiometricUnlockState(value: BiometricUnlockModel) {
+ _biometricUnlockState.tryEmit(value)
}
fun setBiometricUnlockSource(source: BiometricUnlockSource?) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index e160548..71e2bc1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -42,7 +42,7 @@
_transitions.emit(step)
}
- override fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean): UUID? {
+ override fun startTransition(info: TransitionInfo): UUID? {
return if (info.animator == null) UUID.randomUUID() else null
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
index 8e96b52..fc34903 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
@@ -16,11 +16,8 @@
package com.android.systemui.keyguard.domain.interactor
-import android.app.ActivityManager
import android.content.Context
import android.os.Handler
-import android.os.UserManager
-import com.android.internal.logging.UiEventLogger
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
@@ -28,7 +25,6 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.ui.BouncerView
-import com.android.systemui.broadcast.FakeBroadcastDispatcher
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
@@ -36,21 +32,13 @@
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
-import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.domain.interactor.GuestUserInteractor
-import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
-import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.utils.UserRestrictionChecker
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.test.TestScope
import org.mockito.Mockito.mock
@@ -64,8 +52,6 @@
fun create(
context: Context,
testScope: TestScope,
- broadcastDispatcher: FakeBroadcastDispatcher,
- dispatcher: CoroutineDispatcher,
trustRepository: FakeTrustRepository = FakeTrustRepository(),
keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(),
bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
@@ -74,6 +60,7 @@
FakeFeatureFlagsClassic().apply {
set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, true)
set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ set(Flags.REFACTOR_GETCURRENTUSER, true)
},
powerRepository: FakePowerRepository = FakePowerRepository(),
userRepository: FakeUserRepository = FakeUserRepository(),
@@ -92,6 +79,7 @@
keyguardUpdateMonitor,
trustRepository,
testScope.backgroundScope,
+ mock(SelectedUserInteractor::class.java),
)
val alternateBouncerInteractor =
AlternateBouncerInteractor(
@@ -103,38 +91,11 @@
keyguardUpdateMonitor,
)
val powerInteractorWithDeps =
- PowerInteractorFactory.create(
- repository = powerRepository,
- )
- val userInteractor =
- UserInteractor(
- applicationContext = context,
- repository = userRepository,
- mock(ActivityStarter::class.java),
- keyguardInteractor =
- KeyguardInteractorFactory.create(
- repository = keyguardRepository,
- bouncerRepository = bouncerRepository,
- featureFlags = featureFlags,
- )
- .keyguardInteractor,
- featureFlags = featureFlags,
- manager = mock(UserManager::class.java),
- headlessSystemUserMode = mock(HeadlessSystemUserMode::class.java),
- applicationScope = testScope.backgroundScope,
- telephonyInteractor =
- TelephonyInteractor(
- repository = FakeTelephonyRepository(),
- ),
- broadcastDispatcher = broadcastDispatcher,
- keyguardUpdateMonitor = keyguardUpdateMonitor,
- backgroundDispatcher = dispatcher,
- activityManager = mock(ActivityManager::class.java),
- refreshUsersScheduler = mock(RefreshUsersScheduler::class.java),
- guestUserInteractor = mock(GuestUserInteractor::class.java),
- uiEventLogger = mock(UiEventLogger::class.java),
- userRestrictionChecker = mock(UserRestrictionChecker::class.java),
+ PowerInteractorFactory.create(
+ repository = powerRepository,
)
+ val selectedUserInteractor =
+ SelectedUserInteractor(repository = userRepository, flags = featureFlags)
return WithDependencies(
trustRepository = trustRepository,
keyguardRepository = keyguardRepository,
@@ -149,7 +110,7 @@
primaryBouncerInteractor,
alternateBouncerInteractor,
powerInteractorWithDeps.powerInteractor,
- userInteractor,
+ selectedUserInteractor,
),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/core/FakeLogBuffer.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/log/core/FakeLogBuffer.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index 911eafa..cddb007 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -41,17 +41,17 @@
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.security.data.repository.SecurityRepository
import com.android.systemui.security.data.repository.SecurityRepositoryImpl
-import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.FakeSecurityController
import com.android.systemui.statusbar.policy.FakeUserInfoController
import com.android.systemui.statusbar.policy.SecurityController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.user.data.repository.UserSwitcherRepository
import com.android.systemui.user.data.repository.UserSwitcherRepositoryImpl
-import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeGlobalSettings
import com.android.systemui.util.settings.GlobalSettings
@@ -102,7 +102,7 @@
deviceProvisionedController: DeviceProvisionedController = mock(),
qsSecurityFooterUtils: QSSecurityFooterUtils = mock(),
fgsManagerController: FgsManagerController = mock(),
- userInteractor: UserInteractor = mock(),
+ userSwitcherInteractor: UserSwitcherInteractor = mock(),
securityRepository: SecurityRepository = securityRepository(),
foregroundServicesRepository: ForegroundServicesRepository = foregroundServicesRepository(),
userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(),
@@ -116,7 +116,7 @@
deviceProvisionedController,
qsSecurityFooterUtils,
fgsManagerController,
- userInteractor,
+ userSwitcherInteractor,
securityRepository,
foregroundServicesRepository,
userSwitcherRepository,
@@ -149,7 +149,7 @@
bgHandler: Handler = Handler(testableLooper.looper),
bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
userManager: UserManager = mock(),
- userTracker: UserTracker = FakeUserTracker(),
+ userRepository: UserRepository = FakeUserRepository(),
userSwitcherController: UserSwitcherController = mock(),
userInfoController: UserInfoController = FakeUserInfoController(),
settings: GlobalSettings = FakeGlobalSettings(),
@@ -159,10 +159,10 @@
bgHandler,
bgDispatcher,
userManager,
- userTracker,
userSwitcherController,
userInfoController,
settings,
+ userRepository,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index 4307ff9..7494ccf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -69,10 +69,16 @@
_userId = _userInfo.id
_userHandle = UserHandle.of(_userId)
+ onBeforeUserSwitching()
onUserChanging()
onUserChanged()
}
+ fun onBeforeUserSwitching(userId: Int = _userId) {
+ val copy = callbacks.toList()
+ copy.forEach { it.onBeforeUserSwitching(userId) }
+ }
+
fun onUserChanging(userId: Int = _userId) {
val copy = callbacks.toList()
copy.forEach { it.onUserChanging(userId, userContext) {} }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
index 197873f..288dcfe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
@@ -186,7 +186,7 @@
}
@Override
- protected boolean initDataInjectionImpl(boolean enable) {
+ protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {
return false;
}
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 21d0979..b403a7f 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -404,5 +404,9 @@
// Notify the user that audio was lowered based on Calculated Sound Dose (CSD)
NOTE_CSD_LOWER_AUDIO = 1007;
+
+ // Notify the user about external display events related to screenshot.
+ // Package: com.android.systemui
+ NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY = 1008;
}
}
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 10ac2eb..1a735f8 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -43,8 +43,29 @@
}
flag {
+ name: "disable_continuous_shortcut_on_force_stop"
+ namespace: "accessibility"
+ description: "When a package is force stopped, remove the button shortcuts of any continuously-running shortcuts."
+ bug: "198018180"
+}
+
+flag {
name: "deprecate_package_list_observer"
namespace: "accessibility"
description: "Stops using the deprecated PackageListObserver."
bug: "304561459"
}
+
+flag {
+ name: "scan_packages_without_lock"
+ namespace: "accessibility"
+ description: "Scans packages for accessibility service/activity info without holding the A11yMS lock"
+ bug: "295969873"
+}
+
+flag {
+ name: "reduce_touch_exploration_sensitivity"
+ namespace: "accessibility"
+ description: "Reduces touch exploration sensitivity by only sending a hover event when the ifnger has moved the amount of pixels defined by the system's touch slop."
+ bug: "303677860"
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index cd83f8f..5af80da 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -68,7 +68,7 @@
*
* @see #setUserAndEnabledFeatures(int, int)
*/
- static final int FLAG_FEATURE_SCREEN_MAGNIFIER = 0x00000001;
+ static final int FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP = 0x00000001;
/**
* Flag for enabling the touch exploration feature.
@@ -100,7 +100,7 @@
/**
* Flag for enabling the feature to control the screen magnifier. If
- * {@link #FLAG_FEATURE_SCREEN_MAGNIFIER} is set this flag is ignored
+ * {@link #FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP} is set this flag is ignored
* as the screen magnifier feature performs a super set of the work
* performed by this feature.
*
@@ -149,7 +149,7 @@
FLAG_FEATURE_INJECT_MOTION_EVENTS
| FLAG_FEATURE_AUTOCLICK
| FLAG_FEATURE_TOUCH_EXPLORATION
- | FLAG_FEATURE_SCREEN_MAGNIFIER
+ | FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP
| FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
| FLAG_SERVICE_HANDLES_DOUBLE_TAP
| FLAG_REQUEST_MULTI_FINGER_GESTURES
@@ -530,7 +530,7 @@
}
if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
- || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
+ || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP) != 0)
|| ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
final MagnificationGestureHandler magnificationGestureHandler =
createMagnificationGestureHandler(displayId,
@@ -648,7 +648,7 @@
private MagnificationGestureHandler createMagnificationGestureHandler(
int displayId, Context displayContext) {
final boolean detectControlGestures = (mEnabledFeatures
- & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0;
+ & FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP) != 0;
final boolean triggerable = (mEnabledFeatures
& FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0;
MagnificationGestureHandler magnificationGestureHandler;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index aa6d800..e65a185 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.accessibility;
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER_CLIENT;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT;
@@ -182,6 +183,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
@@ -290,14 +292,13 @@
private final Set<ComponentName> mTempComponentNameSet = new HashSet<>();
- private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList =
- new ArrayList<>();
-
private final IntArray mTempIntArray = new IntArray(0);
private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients =
new RemoteCallbackList<>();
+ private PackageMonitor mPackageMonitor;
+
@VisibleForTesting
final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>();
@@ -531,6 +532,19 @@
disableAccessibilityMenuToMigrateIfNeeded();
}
+ /**
+ * Returns if the current thread is holding {@link #mLock}. Used for testing
+ * deadlock bug fixes.
+ *
+ * <p><strong>Warning:</strong> this should not be used for production logic
+ * because by the time you receive an answer it may no longer be valid.
+ * </p>
+ */
+ @VisibleForTesting
+ boolean unsafeIsLockHeld() {
+ return Thread.holdsLock(mLock);
+ }
+
@Override
public int getCurrentUserIdLocked() {
return mCurrentUserId;
@@ -638,6 +652,16 @@
}
}
+ /**
+ * Returns the lock object for any synchronized test blocks.
+ * Should not be used outside of testing.
+ * @return lock object.
+ */
+ @VisibleForTesting
+ Object getLock() {
+ return mLock;
+ }
+
AccessibilityUserState getCurrentUserState() {
synchronized (mLock) {
return getCurrentUserStateLocked();
@@ -690,6 +714,19 @@
}
}
+ private void onSomePackagesChangedLocked(
+ @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
+ @Nullable List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
+ final AccessibilityUserState userState = getCurrentUserStateLocked();
+ // Reload the installed services since some services may have different attributes
+ // or resolve info (does not support equals), etc. Remove them then to force reload.
+ userState.mInstalledServices.clear();
+ if (readConfigurationForUserStateLocked(userState,
+ parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos)) {
+ onUserStateChangedLocked(userState);
+ }
+ }
+
private void onPackageRemovedLocked(String packageName) {
final AccessibilityUserState userState = getCurrentUserState();
final Predicate<ComponentName> filter =
@@ -721,8 +758,69 @@
}
}
+ /**
+ * Handles a package or packages being force stopped.
+ * Will disable any relevant services,
+ * and remove any button targets of continuous services,
+ * denoted by {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON}.
+ * If the result is {@code true},
+ * then {@link AccessibilityManagerService#onUserStateChangedLocked(
+ * AccessibilityUserState, boolean)} should be called afterwards.
+ *
+ * @param packages list of packages that have stopped.
+ * @param userState user state to be read & modified.
+ * @return {@code true} if a service was enabled or a button target was removed,
+ * {@code false} otherwise.
+ */
+ @VisibleForTesting
+ boolean onPackagesForceStoppedLocked(
+ String[] packages, AccessibilityUserState userState) {
+ final List<String> continuousServicePackages =
+ userState.mInstalledServices.stream().filter(service ->
+ (service.flags & FLAG_REQUEST_ACCESSIBILITY_BUTTON)
+ == FLAG_REQUEST_ACCESSIBILITY_BUTTON
+ ).map(service -> service.getComponentName().flattenToString()).toList();
+
+ boolean enabledServicesChanged = false;
+ final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
+ while (it.hasNext()) {
+ final ComponentName comp = it.next();
+ final String compPkg = comp.getPackageName();
+ for (String pkg : packages) {
+ if (compPkg.equals(pkg)) {
+ it.remove();
+ userState.getBindingServicesLocked().remove(comp);
+ userState.getCrashedServicesLocked().remove(comp);
+ enabledServicesChanged = true;
+ }
+ }
+ }
+ if (enabledServicesChanged) {
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userState.mEnabledServices, userState.mUserId);
+ }
+
+ boolean buttonTargetsChanged = userState.mAccessibilityButtonTargets.removeIf(
+ target -> continuousServicePackages.stream().anyMatch(
+ pkg -> Objects.equals(target, pkg)));
+ if (buttonTargetsChanged) {
+ persistColonDelimitedSetToSettingLocked(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+ userState.mUserId,
+ userState.mAccessibilityButtonTargets, str -> str);
+ }
+
+ return enabledServicesChanged || buttonTargetsChanged;
+ }
+
+ @VisibleForTesting
+ PackageMonitor getPackageMonitor() {
+ return mPackageMonitor;
+ }
+
private void registerBroadcastReceivers() {
- PackageMonitor monitor = new PackageMonitor() {
+ mPackageMonitor = new PackageMonitor() {
@Override
public void onSomePackagesChanged() {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
@@ -730,13 +828,25 @@
FLAGS_PACKAGE_BROADCAST_RECEIVER);
}
+ final int userId = getChangingUserId();
+ List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
+ List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
+ if (Flags.scanPackagesWithoutLock()) {
+ parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+ parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
+ }
synchronized (mLock) {
// Only the profile parent can install accessibility services.
// Therefore we ignore packages from linked profiles.
- if (getChangingUserId() != mCurrentUserId) {
+ if (userId != mCurrentUserId) {
return;
}
- onSomePackagesChangedLocked();
+ if (Flags.scanPackagesWithoutLock()) {
+ onSomePackagesChangedLocked(parsedAccessibilityServiceInfos,
+ parsedAccessibilityShortcutInfos);
+ } else {
+ onSomePackagesChangedLocked();
+ }
}
}
@@ -751,8 +861,14 @@
FLAGS_PACKAGE_BROADCAST_RECEIVER,
"packageName=" + packageName + ";uid=" + uid);
}
+ final int userId = getChangingUserId();
+ List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
+ List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
+ if (Flags.scanPackagesWithoutLock()) {
+ parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+ parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
+ }
synchronized (mLock) {
- final int userId = getChangingUserId();
if (userId != mCurrentUserId) {
return;
}
@@ -765,8 +881,13 @@
// Reloads the installed services info to make sure the rebound service could
// get a new one.
userState.mInstalledServices.clear();
- final boolean configurationChanged =
- readConfigurationForUserStateLocked(userState);
+ final boolean configurationChanged;
+ if (Flags.scanPackagesWithoutLock()) {
+ configurationChanged = readConfigurationForUserStateLocked(userState,
+ parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
+ } else {
+ configurationChanged = readConfigurationForUserStateLocked(userState);
+ }
if (reboundAService || configurationChanged) {
onUserStateChangedLocked(userState);
}
@@ -797,6 +918,16 @@
}
}
+ /**
+ * Handles instances in which a package or packages have forcibly stopped.
+ *
+ * @param intent intent containing package event information.
+ * @param uid linux process user id (different from Android user id).
+ * @param packages array of package names that have stopped.
+ * @param doit whether to try and handle the stop or just log the trace.
+ *
+ * @return {@code true} if package should be restarted, {@code false} otherwise.
+ */
@Override
public boolean onHandleForceStop(Intent intent, String[] packages,
int uid, boolean doit) {
@@ -814,32 +945,42 @@
return false;
}
final AccessibilityUserState userState = getUserStateLocked(userId);
- final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
- while (it.hasNext()) {
- final ComponentName comp = it.next();
- final String compPkg = comp.getPackageName();
- for (String pkg : packages) {
- if (compPkg.equals(pkg)) {
- if (!doit) {
- return true;
+
+ if (Flags.disableContinuousShortcutOnForceStop()) {
+ if (doit && onPackagesForceStoppedLocked(packages, userState)) {
+ onUserStateChangedLocked(userState);
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
+ while (it.hasNext()) {
+ final ComponentName comp = it.next();
+ final String compPkg = comp.getPackageName();
+ for (String pkg : packages) {
+ if (compPkg.equals(pkg)) {
+ if (!doit) {
+ return true;
+ }
+ it.remove();
+ userState.getBindingServicesLocked().remove(comp);
+ userState.getCrashedServicesLocked().remove(comp);
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userState.mEnabledServices, userId);
+ onUserStateChangedLocked(userState);
}
- it.remove();
- userState.getBindingServicesLocked().remove(comp);
- userState.getCrashedServicesLocked().remove(comp);
- persistComponentNamesToSettingLocked(
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- userState.mEnabledServices, userId);
- onUserStateChangedLocked(userState);
}
}
+ return false;
}
- return false;
}
}
};
// package changes
- monitor.register(mContext, null, UserHandle.ALL, true);
+ mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
if (!Flags.deprecatePackageListObserver()) {
final PackageManagerInternal pm = LocalServices.getService(
@@ -1831,8 +1972,15 @@
mA11yWindowManager.onTouchInteractionEnd();
}
- private void switchUser(int userId) {
+ @VisibleForTesting
+ void switchUser(int userId) {
mMagnificationController.updateUserIdIfNeeded(userId);
+ List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
+ List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
+ if (Flags.scanPackagesWithoutLock()) {
+ parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+ parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
+ }
synchronized (mLock) {
if (mCurrentUserId == userId && mInitialized) {
return;
@@ -1857,7 +2005,12 @@
mCurrentUserId = userId;
AccessibilityUserState userState = getCurrentUserStateLocked();
- readConfigurationForUserStateLocked(userState);
+ if (Flags.scanPackagesWithoutLock()) {
+ readConfigurationForUserStateLocked(userState,
+ parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
+ } else {
+ readConfigurationForUserStateLocked(userState);
+ }
mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices);
// Even if reading did not yield change, we have to update
// the state since the context in which the current user
@@ -2105,8 +2258,17 @@
}
}
- private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState) {
- mTempAccessibilityServiceInfoList.clear();
+ /**
+ * Finds packages that provide AccessibilityService interfaces, and parses
+ * their metadata XML to build up {@link AccessibilityServiceInfo} objects.
+ *
+ * <p>
+ * <strong>Note:</strong> XML parsing is a resource-heavy operation that may
+ * stall, so this method should not be called while holding a lock.
+ * </p>
+ */
+ private List<AccessibilityServiceInfo> parseAccessibilityServiceInfos(int userId) {
+ List<AccessibilityServiceInfo> result = new ArrayList<>();
int flags = PackageManager.GET_SERVICES
| PackageManager.GET_META_DATA
@@ -2114,12 +2276,14 @@
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- if (userState.getBindInstantServiceAllowedLocked()) {
- flags |= PackageManager.MATCH_INSTANT;
+ synchronized (mLock) {
+ if (getUserStateLocked(userId).getBindInstantServiceAllowedLocked()) {
+ flags |= PackageManager.MATCH_INSTANT;
+ }
}
List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser(
- new Intent(AccessibilityService.SERVICE_INTERFACE), flags, mCurrentUserId);
+ new Intent(AccessibilityService.SERVICE_INTERFACE), flags, userId);
for (int i = 0, count = installedServices.size(); i < count; i++) {
ResolveInfo resolveInfo = installedServices.get(i);
@@ -2132,40 +2296,60 @@
AccessibilityServiceInfo accessibilityServiceInfo;
try {
accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext);
- if (!accessibilityServiceInfo.isWithinParcelableSize()) {
- Slog.e(LOG_TAG, "Skipping service "
- + accessibilityServiceInfo.getResolveInfo().getComponentInfo()
- + " because service info size is larger than safe parcelable limits.");
- continue;
- }
- if (userState.mCrashedServices.contains(serviceInfo.getComponentName())) {
- // Restore the crashed attribute.
- accessibilityServiceInfo.crashed = true;
- }
- mTempAccessibilityServiceInfoList.add(accessibilityServiceInfo);
} catch (XmlPullParserException | IOException xppe) {
Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe);
+ continue;
+ }
+ if (!accessibilityServiceInfo.isWithinParcelableSize()) {
+ Slog.e(LOG_TAG, "Skipping service "
+ + accessibilityServiceInfo.getResolveInfo().getComponentInfo()
+ + " because service info size is larger than safe parcelable limits.");
+ continue;
+ }
+ result.add(accessibilityServiceInfo);
+ }
+ return result;
+ }
+
+ private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState,
+ @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos) {
+ for (int i = 0, count = parsedAccessibilityServiceInfos.size(); i < count; i++) {
+ AccessibilityServiceInfo accessibilityServiceInfo =
+ parsedAccessibilityServiceInfos.get(i);
+ if (userState.mCrashedServices.contains(accessibilityServiceInfo.getComponentName())) {
+ // Restore the crashed attribute.
+ accessibilityServiceInfo.crashed = true;
}
}
- if (!mTempAccessibilityServiceInfoList.equals(userState.mInstalledServices)) {
+ if (!parsedAccessibilityServiceInfos.equals(userState.mInstalledServices)) {
userState.mInstalledServices.clear();
- userState.mInstalledServices.addAll(mTempAccessibilityServiceInfoList);
- mTempAccessibilityServiceInfoList.clear();
+ userState.mInstalledServices.addAll(parsedAccessibilityServiceInfos);
return true;
}
-
- mTempAccessibilityServiceInfoList.clear();
return false;
}
- private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState) {
- final List<AccessibilityShortcutInfo> shortcutInfos = AccessibilityManager
- .getInstance(mContext).getInstalledAccessibilityShortcutListAsUser(
- mContext, mCurrentUserId);
- if (!shortcutInfos.equals(userState.mInstalledShortcuts)) {
+ /**
+ * Returns the {@link AccessibilityShortcutInfo}s of the installed
+ * accessibility shortcut targets for the given user.
+ *
+ * <p>
+ * <strong>Note:</strong> XML parsing is a resource-heavy operation that may
+ * stall, so this method should not be called while holding a lock.
+ * </p>
+ */
+ private List<AccessibilityShortcutInfo> parseAccessibilityShortcutInfos(int userId) {
+ // TODO: b/297279151 - This should be implemented here, not by AccessibilityManager.
+ return AccessibilityManager.getInstance(mContext)
+ .getInstalledAccessibilityShortcutListAsUser(mContext, userId);
+ }
+
+ private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState,
+ List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
+ if (!parsedAccessibilityShortcutInfos.equals(userState.mInstalledShortcuts)) {
userState.mInstalledShortcuts.clear();
- userState.mInstalledShortcuts.addAll(shortcutInfos);
+ userState.mInstalledShortcuts.addAll(parsedAccessibilityShortcutInfos);
return true;
}
return false;
@@ -2356,7 +2540,8 @@
* @param userId The user id.
* @param outComponentNames The output component names.
*/
- private void readComponentNamesFromSettingLocked(String settingName, int userId,
+ @VisibleForTesting
+ void readComponentNamesFromSettingLocked(String settingName, int userId,
Set<ComponentName> outComponentNames) {
readColonDelimitedSettingToSet(settingName, userId,
str -> ComponentName.unflattenFromString(str), outComponentNames);
@@ -2385,7 +2570,19 @@
componentName -> componentName.flattenToShortString());
}
- private <T> void readColonDelimitedSettingToSet(String settingName, int userId,
+ /**
+ * Reads a colon delimited setting,
+ * passes the values through a function,
+ * then stores the values in a provided set.
+ *
+ * @param settingName Name of setting.
+ * @param userId user id corresponding to setting.
+ * @param toItem function mapping values to the output set.
+ * @param outSet output set to write to.
+ * @param <T> type of output set.
+ */
+ @VisibleForTesting
+ <T> void readColonDelimitedSettingToSet(String settingName, int userId,
Function<String, T> toItem, Set<T> outSet) {
final String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
settingName, userId);
@@ -2597,8 +2794,9 @@
AccessibilityInputFilter inputFilter = null;
synchronized (mLock) {
int flags = 0;
- if (userState.isDisplayMagnificationEnabledLocked()) {
- flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
+ if (userState.isMagnificationSingleFingerTripleTapEnabledLocked()) {
+ flags |= AccessibilityInputFilter
+ .FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP;
}
if (userState.isShortcutMagnificationEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
@@ -2890,9 +3088,23 @@
userState.setFilterKeyEventsEnabledLocked(false);
}
+ // ErrorProne doesn't understand that this method is only called while locked,
+ // returning an error for accessing mCurrentUserId.
+ @SuppressWarnings("GuardedBy")
private boolean readConfigurationForUserStateLocked(AccessibilityUserState userState) {
- boolean somethingChanged = readInstalledAccessibilityServiceLocked(userState);
- somethingChanged |= readInstalledAccessibilityShortcutLocked(userState);
+ return readConfigurationForUserStateLocked(userState,
+ parseAccessibilityServiceInfos(mCurrentUserId),
+ parseAccessibilityShortcutInfos(mCurrentUserId));
+ }
+
+ private boolean readConfigurationForUserStateLocked(
+ AccessibilityUserState userState,
+ List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
+ List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
+ boolean somethingChanged = readInstalledAccessibilityServiceLocked(
+ userState, parsedAccessibilityServiceInfos);
+ somethingChanged |= readInstalledAccessibilityShortcutLocked(
+ userState, parsedAccessibilityShortcutInfos);
somethingChanged |= readEnabledAccessibilityServicesLocked(userState);
somethingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState);
somethingChanged |= readTouchExplorationEnabledSettingLocked(userState);
@@ -2937,12 +3149,14 @@
}
private boolean readMagnificationEnabledSettingsLocked(AccessibilityUserState userState) {
- final boolean displayMagnificationEnabled = Settings.Secure.getIntForUser(
+ final boolean magnificationSingleFingerTripleTapEnabled = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
0, userState.mUserId) == 1;
- if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())) {
- userState.setDisplayMagnificationEnabledLocked(displayMagnificationEnabled);
+ if ((magnificationSingleFingerTripleTapEnabled
+ != userState.isMagnificationSingleFingerTripleTapEnabledLocked())) {
+ userState.setMagnificationSingleFingerTripleTapEnabledLocked(
+ magnificationSingleFingerTripleTapEnabled);
return true;
}
return false;
@@ -3182,7 +3396,7 @@
// We would skip overlay display because it uses overlay window to simulate secondary
// displays in one display. It's not a real display and there's no input events for it.
final ArrayList<Display> displays = getValidDisplayList();
- if (userState.isDisplayMagnificationEnabledLocked()
+ if (userState.isMagnificationSingleFingerTripleTapEnabledLocked()
|| userState.isShortcutMagnificationEnabledLocked()) {
for (int i = 0; i < displays.size(); i++) {
final Display display = displays.get(i);
@@ -3211,7 +3425,7 @@
return;
}
final boolean connect = (userState.isShortcutMagnificationEnabledLocked()
- || userState.isDisplayMagnificationEnabledLocked())
+ || userState.isMagnificationSingleFingerTripleTapEnabledLocked())
&& (userState.getMagnificationCapabilitiesLocked()
!= Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)
|| userHasMagnificationServicesLocked(userState);
@@ -3362,7 +3576,7 @@
return true;
}
final boolean requestA11yButton = (serviceInfo.flags
- & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+ & FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
if (requestA11yButton && !userState.mEnabledServices.contains(componentName)) {
// An a11y service targeting sdk version > Q and request A11y button and is assigned
// to a11y btn should be in the enabled list.
@@ -3663,7 +3877,7 @@
final int targetSdk = installedServiceInfo.getResolveInfo()
.serviceInfo.applicationInfo.targetSdkVersion;
final boolean requestA11yButton = (installedServiceInfo.flags
- & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+ & FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
// Turns on / off the accessibility service
if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == ACCESSIBILITY_SHORTCUT_KEY)
|| (targetSdk > Build.VERSION_CODES.Q && !requestA11yButton)) {
@@ -4896,7 +5110,7 @@
updateWindowMagnificationConnectionIfNeeded(userState);
// Remove magnification button UI when the magnification capability is not all mode or
// magnification is disabled.
- if (!(userState.isDisplayMagnificationEnabledLocked()
+ if (!(userState.isMagnificationSingleFingerTripleTapEnabledLocked()
|| userState.isShortcutMagnificationEnabledLocked())
|| userState.getMagnificationCapabilitiesLocked()
!= Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 693526a..b4efec1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -109,7 +109,7 @@
private boolean mBindInstantServiceAllowed;
private boolean mIsAudioDescriptionByDefaultRequested;
private boolean mIsAutoclickEnabled;
- private boolean mIsDisplayMagnificationEnabled;
+ private boolean mIsMagnificationSingleFingerTripleTapEnabled;
private boolean mIsFilterKeyEventsEnabled;
private boolean mIsPerformGesturesEnabled;
private boolean mAccessibilityFocusOnlyInActiveWindow;
@@ -211,7 +211,7 @@
mRequestMultiFingerGestures = false;
mRequestTwoFingerPassthrough = false;
mSendMotionEventsEnabled = false;
- mIsDisplayMagnificationEnabled = false;
+ mIsMagnificationSingleFingerTripleTapEnabled = false;
mIsAutoclickEnabled = false;
mUserNonInteractiveUiTimeout = 0;
mUserInteractiveUiTimeout = 0;
@@ -520,7 +520,7 @@
.append(String.valueOf(mRequestTwoFingerPassthrough));
pw.append(", sendMotionEventsEnabled").append(String.valueOf(mSendMotionEventsEnabled));
pw.append(", displayMagnificationEnabled=").append(String.valueOf(
- mIsDisplayMagnificationEnabled));
+ mIsMagnificationSingleFingerTripleTapEnabled));
pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout));
pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout));
@@ -625,12 +625,12 @@
mIsAutoclickEnabled = enabled;
}
- public boolean isDisplayMagnificationEnabledLocked() {
- return mIsDisplayMagnificationEnabled;
+ public boolean isMagnificationSingleFingerTripleTapEnabledLocked() {
+ return mIsMagnificationSingleFingerTripleTapEnabled;
}
- public void setDisplayMagnificationEnabledLocked(boolean enabled) {
- mIsDisplayMagnificationEnabled = enabled;
+ public void setMagnificationSingleFingerTripleTapEnabledLocked(boolean enabled) {
+ mIsMagnificationSingleFingerTripleTapEnabled = enabled;
}
public boolean isFilterKeyEventsEnabledLocked() {
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index c418485..fc8d4f8 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -882,10 +882,22 @@
final int pointerIndex = event.findPointerIndex(pointerId);
switch (event.getPointerCount()) {
case 1:
- // Touch exploration.
+ // Touch exploration.
sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
- mDispatcher.sendMotionEvent(
- event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
+ if (Flags.reduceTouchExplorationSensitivity()
+ && mState.getLastInjectedHoverEvent() != null) {
+ final MotionEvent lastEvent = mState.getLastInjectedHoverEvent();
+ final float deltaX = lastEvent.getX() - rawEvent.getX();
+ final float deltaY = lastEvent.getY() - rawEvent.getY();
+ final double moveDelta = Math.hypot(deltaX, deltaY);
+ if (moveDelta > mTouchSlop) {
+ mDispatcher.sendMotionEvent(
+ event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
+ }
+ } else {
+ mDispatcher.sendMotionEvent(
+ event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
+ }
break;
case 2:
if (mGestureDetector.isMultiFingerGesturesEnabled()
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 72242d2..e4f1d3a 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -363,6 +363,7 @@
case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS:
case AutofillFeatureFlags.DEVICE_CONFIG_PREFER_PROVIDER_OVER_PCC:
case AutofillFeatureFlags.DEVICE_CONFIG_PCC_USE_FALLBACK:
+ case Flags.FLAG_AUTOFILL_CREDMAN_INTEGRATION:
setDeviceConfigProperties();
break;
case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES:
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 8a2aa61..4298c07 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -122,9 +122,9 @@
private static final String TAG = "VirtualDeviceImpl";
/**
- * Virtual displays created by a {@link VirtualDeviceManager.VirtualDevice} are more consistent
- * with virtual displays created via {@link DisplayManager} and allow for the creation of
- * private, auto-mirror, and fixed orientation displays since
+ * Virtual displays created by a {@code VirtualDeviceManager.VirtualDevice} are more consistent
+ * with virtual displays created via {@link android.hardware.display.DisplayManager} and allow
+ * for the creation of private, auto-mirror, and fixed orientation displays since
* {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}.
*
* @see DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC
@@ -632,6 +632,10 @@
public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
@VirtualDeviceParams.DevicePolicy int devicePolicy) {
super.setDevicePolicy_enforcePermission();
+ if (!Flags.dynamicPolicy()) {
+ return;
+ }
+
switch (policyType) {
case POLICY_TYPE_RECENTS:
synchronized (mVirtualDeviceLock) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 1a8dd3a..65975e4 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -28,6 +28,7 @@
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_SECURITY_EXCEPTION;
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_TRUE;
import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED;
+import static android.view.contentprotection.flags.Flags.parseGroupsConfigEnabled;
import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ACCEPT_DATA_SHARE_REQUEST;
import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CLIENT_PIPE_FAIL;
@@ -115,6 +116,7 @@
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -142,6 +144,9 @@
private static final int MAX_CONCURRENT_FILE_SHARING_REQUESTS = 10;
private static final int DATA_SHARE_BYTE_BUFFER_LENGTH = 1_024;
+ private static final String CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_GROUP = ";";
+ private static final String CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_VALUE = ",";
+
// Needed to pass checkstyle_hook as names are too long for one line.
private static final int EVENT__DATA_SHARE_ERROR_CONCURRENT_REQUEST =
CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CONCURRENT_REQUEST;
@@ -957,14 +962,40 @@
return mContentCaptureManagerServiceStub;
}
- /** @hide */
+ /**
+ * Parses a simple config in format "group;group" where each "group" is itself in the format of
+ * "string1,string2", eg:
+ *
+ * <p>"a" -> [["a"]]
+ *
+ * <p>"a,b" -> [["a", "b"]]
+ *
+ * <p>"a,b;c;d,e" -> [["a", "b"], ["c"], ["d", "e"]]
+ *
+ * @hide
+ */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@NonNull
protected List<List<String>> parseContentProtectionGroupsConfig(@Nullable String config) {
if (verbose) {
Slog.v(TAG, "parseContentProtectionGroupsConfig: " + config);
}
- return Collections.emptyList();
+ if (!parseGroupsConfigEnabled()) {
+ return Collections.emptyList();
+ }
+ if (config == null) {
+ return Collections.emptyList();
+ }
+ return Arrays.stream(config.split(CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_GROUP))
+ .map(this::parseContentProtectionGroupConfigValues)
+ .filter(group -> !group.isEmpty())
+ .toList();
+ }
+
+ private List<String> parseContentProtectionGroupConfigValues(@NonNull String group) {
+ return Arrays.stream(group.split(CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_VALUE))
+ .filter(value -> !value.isEmpty())
+ .toList();
}
final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
@@ -1406,6 +1437,10 @@
if (!mDevCfgEnableContentProtectionReceiver) {
return false;
}
+ if (mDevCfgContentProtectionRequiredGroups.isEmpty()
+ && mDevCfgContentProtectionOptionalGroups.isEmpty()) {
+ return false;
+ }
}
return mContentProtectionConsentManager.isConsentGranted(userId)
&& mContentProtectionBlocklistManager.isAllowed(packageName);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index e5225f6..898cdcc 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -96,11 +96,26 @@
out: ["com/android/server/location/contexthub/ContextHubStatsLog.java"],
}
+/*
+ * This module is used to refer aconfig flag libraries that are
+ * added to the framework via static_libs.
+ * These libraries are referred here via libs prevent duplication of classes in both
+ * the framework and the system server.
+*/
+java_defaults {
+ name: "shared-framework-aconfig-libs",
+ libs: [
+ "display_flags_lib",
+ "camera_platform_flags_core_java_lib",
+ ],
+}
+
java_library_static {
name: "services.core.unboosted",
defaults: [
"platform_service_defaults",
"android.hardware.power-java_static",
+ "shared-framework-aconfig-libs",
],
srcs: [
":android.hardware.biometrics.face-V3-java-source",
@@ -181,7 +196,6 @@
"android.hardware.power.stats-V2-java",
"android.hidl.manager-V1.2-java",
"cbor-java",
- "display_flags_lib",
"icu4j_calendar_astronomer",
"android.security.aaid_aidl-java",
"netd-client",
@@ -191,9 +205,8 @@
"ImmutabilityAnnotation",
"securebox",
"apache-commons-math",
- "power_optimization_flags_lib",
+ "backstage_power_flags_lib",
"notification_flags_lib",
- "camera_platform_flags_core_java_lib",
"biometrics_flags_lib",
"am_flags_lib",
],
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 8df5456..638abdb 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1387,11 +1387,12 @@
@UserIdInt int userId);
/**
- * Tells PackageManager when a component (except BroadcastReceivers) of the package is used
+ * Tells PackageManager when a component of the package is used
* and the package should get out of stopped state and be enabled.
*/
public abstract void notifyComponentUsed(@NonNull String packageName,
- @UserIdInt int userId, @NonNull String recentCallingPackage, @NonNull String debugInfo);
+ @UserIdInt int userId, @Nullable String recentCallingPackage,
+ @NonNull String debugInfo);
/** @deprecated For legacy shell command only. */
@Deprecated
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 962f38f..beea063 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -214,9 +214,12 @@
// external storage service.
public static final int FAILED_MOUNT_RESET_TIMEOUT_SECONDS = 10;
- /** Extended timeout for the system server watchdog. */
+ /** Extended timeout for the system server watchdog. */
private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 60 * 1000;
+ /** Extended timeout for the system server watchdog for vold#partition operation. */
+ private static final int PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS = 3 * 60 * 1000;
+
@GuardedBy("mLock")
private final Set<Integer> mFuseMountedUser = new ArraySet<>();
@@ -2338,6 +2341,8 @@
try {
// TODO(b/135341433): Remove cautious logging when FUSE is stable
Slog.i(TAG, "Mounting volume " + vol);
+ Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#mount might be slow");
mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
@Override
public boolean onVolumeChecking(FileDescriptor fd, String path,
@@ -2463,10 +2468,12 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS)
@Override
public void partitionPublic(String diskId) {
-
super.partitionPublic_enforcePermission();
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+
+ Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
try {
mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
waitForLatch(latch, "partitionPublic", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -2483,6 +2490,9 @@
enforceAdminUser();
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+
+ Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
try {
mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
waitForLatch(latch, "partitionPrivate", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -2499,6 +2509,9 @@
enforceAdminUser();
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+
+ Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
try {
mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
waitForLatch(latch, "partitionMixed", 3 * DateUtils.MINUTE_IN_MILLIS);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 5fb889a..1650a96 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5309,7 +5309,7 @@
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
}
- long flags = Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS | Context.BIND_AUTO_CREATE;
+ long flags = Context.BIND_AUTO_CREATE;
if (mAuthenticatorCache.getBindInstantServiceAllowed(mAccounts.userId)) {
flags |= Context.BIND_ALLOW_INSTANT;
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 553b085..5f1a7e7 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -409,6 +409,13 @@
AppWidgetManagerInternal mAppWidgetManagerInternal;
+ /**
+ * The available ANR timers.
+ */
+ private final ProcessAnrTimer mActiveServiceAnrTimer;
+ private final ServiceAnrTimer mShortFGSAnrTimer;
+ private final ServiceAnrTimer mServiceFGAnrTimer;
+
// allowlisted packageName.
ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>();
@@ -663,6 +670,15 @@
final IBinder b = ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
this.mFGSLogger = new ForegroundServiceTypeLoggerModule();
+ this.mActiveServiceAnrTimer = new ProcessAnrTimer(service,
+ ActivityManagerService.SERVICE_TIMEOUT_MSG,
+ "SERVICE_TIMEOUT");
+ this.mShortFGSAnrTimer = new ServiceAnrTimer(service,
+ ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG,
+ "FGS_TIMEOUT");
+ this.mServiceFGAnrTimer = new ServiceAnrTimer(service,
+ ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,
+ "SERVICE_FOREGROUND_TIMEOUT");
}
void systemServicesReady() {
@@ -2083,8 +2099,7 @@
r.fgRequired = false;
r.fgWaiting = false;
alreadyStartedOp = stopProcStatsOp = true;
- mAm.mHandler.removeMessages(
- ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
+ mServiceFGAnrTimer.cancel(r);
}
final ProcessServiceRecord psr = r.app.mServices;
@@ -3313,7 +3328,7 @@
}
void unscheduleShortFgsTimeoutLocked(ServiceRecord sr) {
- mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
+ mShortFGSAnrTimer.cancel(sr);
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_PROCSTATE_TIMEOUT_MSG,
sr);
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
@@ -3387,9 +3402,11 @@
Slog.d(TAG_SERVICE, "[STALE] Short FGS timed out: " + sr
+ " " + sr.getShortFgsTimedEventDescription(nowUptime));
}
+ mShortFGSAnrTimer.discard(sr);
return;
}
Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr);
+ mShortFGSAnrTimer.accept(sr);
traceInstant("short FGS timeout: ", sr);
logFGSStateChangeLocked(sr,
@@ -3413,11 +3430,10 @@
msg, sr.getShortFgsInfo().getProcStateDemoteTime());
}
- {
- final Message msg = mAm.mHandler.obtainMessage(
- ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
- mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getAnrTime());
- }
+ // ServiceRecord.getAnrTime() is an absolute time with a reference that is not "now".
+ // Compute the time from "now" when starting the anr timer.
+ mShortFGSAnrTimer.start(sr,
+ sr.getShortFgsInfo().getAnrTime() - SystemClock.uptimeMillis());
}
}
@@ -3662,8 +3678,8 @@
|| (flags & Context.BIND_EXTERNAL_SERVICE_LONG) != 0;
final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
final boolean inSharedIsolatedProcess = (flags & Context.BIND_SHARED_ISOLATED_PROCESS) != 0;
- final boolean filterOutQuarantined =
- (flags & Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS) != 0;
+ final boolean matchQuarantined =
+ (flags & Context.BIND_MATCH_QUARANTINED_COMPONENTS) != 0;
ProcessRecord attributedApp = null;
if (sdkSandboxClientAppUid > 0) {
@@ -3673,7 +3689,7 @@
isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg,
isBindExternal, allowInstant, null /* fgsDelegateOptions */,
- inSharedIsolatedProcess, filterOutQuarantined);
+ inSharedIsolatedProcess, matchQuarantined);
if (res == null) {
return 0;
}
@@ -4186,7 +4202,7 @@
sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage,
callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal,
allowInstant, fgsDelegateOptions, inSharedIsolatedProcess,
- false /* filterOutQuarantined */);
+ false /* matchQuarantined */);
}
private ServiceLookupResult retrieveServiceLocked(Intent service,
@@ -4195,7 +4211,7 @@
String callingPackage, int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions,
- boolean inSharedIsolatedProcess, boolean filterOutQuarantined) {
+ boolean inSharedIsolatedProcess, boolean matchQuarantined) {
if (isSdkSandboxService && instanceName == null) {
throw new IllegalArgumentException("No instanceName provided for sdk sandbox process");
}
@@ -4317,8 +4333,8 @@
if (allowInstant) {
flags |= PackageManager.MATCH_INSTANT;
}
- if (filterOutQuarantined) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
+ if (matchQuarantined) {
+ flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS;
}
// TODO: come back and remove this assumption to triage all services
ResolveInfo rInfo = mAm.getPackageManagerInternal().resolveService(service,
@@ -4847,8 +4863,7 @@
// a new SERVICE_FOREGROUND_TIMEOUT_MSG is scheduled in SERVICE_START_FOREGROUND_TIMEOUT
// again.
if (r.fgRequired && r.fgWaiting) {
- mAm.mHandler.removeMessages(
- ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
+ mServiceFGAnrTimer.cancel(r);
r.fgWaiting = false;
}
@@ -5691,8 +5706,7 @@
}
mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
- mAm.mHandler.removeMessages(
- ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
+ mServiceFGAnrTimer.cancel(r);
if (r.app != null) {
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
@@ -6128,7 +6142,7 @@
if (psr.numberOfExecutingServices() == 0) {
if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
"No more executingServices of " + r.shortInstanceName);
- mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
+ if (r.app.mPid != 0) mActiveServiceAnrTimer.cancel(r.app);
} else if (r.executeFg) {
// Need to re-evaluate whether the app still needs to be in the foreground.
for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) {
@@ -6816,13 +6830,16 @@
synchronized (mAm) {
if (proc.isDebugging()) {
// The app's being debugged, ignore timeout.
+ mActiveServiceAnrTimer.discard(proc);
return;
}
final ProcessServiceRecord psr = proc.mServices;
if (psr.numberOfExecutingServices() == 0 || proc.getThread() == null
|| proc.isKilled()) {
+ mActiveServiceAnrTimer.discard(proc);
return;
}
+ mActiveServiceAnrTimer.accept(proc);
final long now = SystemClock.uptimeMillis();
final long maxTime = now
- (psr.shouldExecServicesFg()
@@ -6855,12 +6872,11 @@
timeoutRecord = TimeoutRecord.forServiceExec(timeout.shortInstanceName,
waitedMillis);
} else {
- Message msg = mAm.mHandler.obtainMessage(
- ActivityManagerService.SERVICE_TIMEOUT_MSG);
- msg.obj = proc;
- mAm.mHandler.sendMessageAtTime(msg, psr.shouldExecServicesFg()
- ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) :
- (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT));
+ final long delay = psr.shouldExecServicesFg()
+ ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) :
+ (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT)
+ - SystemClock.uptimeMillis();
+ mActiveServiceAnrTimer.start(proc, delay);
}
}
@@ -6886,12 +6902,15 @@
synchronized (mAm) {
timeoutRecord.mLatencyTracker.waitingOnAMSLockEnded();
if (!r.fgRequired || !r.fgWaiting || r.destroying) {
+ mServiceFGAnrTimer.discard(r);
return;
}
+ mServiceFGAnrTimer.accept(r);
app = r.app;
if (app != null && app.isDebugging()) {
// The app's being debugged; let it ride
+ mServiceFGAnrTimer.discard(r);
return;
}
@@ -6948,26 +6967,46 @@
ForegroundServiceDidNotStartInTimeException.createExtrasForService(service));
}
+ private static class ProcessAnrTimer extends AnrTimer<ProcessRecord> {
+
+ ProcessAnrTimer(ActivityManagerService am, int msg, String label) {
+ super(Objects.requireNonNull(am).mHandler, msg, label);
+ }
+
+ void start(@NonNull ProcessRecord proc, long millis) {
+ start(proc, proc.getPid(), proc.uid, millis);
+ }
+ }
+
+ private static class ServiceAnrTimer extends AnrTimer<ServiceRecord> {
+
+ ServiceAnrTimer(ActivityManagerService am, int msg, String label) {
+ super(Objects.requireNonNull(am).mHandler, msg, label);
+ }
+
+ void start(@NonNull ServiceRecord service, long millis) {
+ start(service,
+ (service.app != null) ? service.app.getPid() : 0,
+ service.appInfo.uid,
+ millis);
+ }
+ }
+
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
if (proc.mServices.numberOfExecutingServices() == 0 || proc.getThread() == null) {
return;
}
- Message msg = mAm.mHandler.obtainMessage(
- ActivityManagerService.SERVICE_TIMEOUT_MSG);
- msg.obj = proc;
- mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg()
- ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT);
+ final long delay = proc.mServices.shouldExecServicesFg()
+ ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT;
+ mActiveServiceAnrTimer.start(proc, delay);
}
void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
if (r.app.mServices.numberOfExecutingServices() == 0 || r.app.getThread() == null) {
return;
}
- Message msg = mAm.mHandler.obtainMessage(
- ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);
- msg.obj = r;
r.fgWaiting = true;
- mAm.mHandler.sendMessageDelayed(msg, mAm.mConstants.mServiceStartForegroundTimeoutMs);
+ mServiceFGAnrTimer.start(r, mAm.mConstants.mServiceStartForegroundTimeoutMs);
}
final class ServiceDumper {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b43b986..31817f1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -58,7 +58,6 @@
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_PERSISTENT;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_SYSTEM;
import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
-import static android.content.pm.PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES;
import static android.content.pm.PackageManager.MATCH_ALL;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
@@ -14295,8 +14294,7 @@
private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType,
int callingUid, int[] users, int[] broadcastAllowList) {
// TODO: come back and remove this assumption to triage all broadcasts
- long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING
- | FILTER_OUT_QUARANTINED_COMPONENTS;
+ long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;
List<ResolveInfo> receivers = null;
HashSet<ComponentName> singleUserReceivers = null;
diff --git a/services/core/java/com/android/server/am/AnrTimer.java b/services/core/java/com/android/server/am/AnrTimer.java
index 9ba49ce..3e17930 100644
--- a/services/core/java/com/android/server/am/AnrTimer.java
+++ b/services/core/java/com/android/server/am/AnrTimer.java
@@ -28,6 +28,7 @@
import android.os.SystemClock;
import android.os.Trace;
import android.text.TextUtils;
+import android.text.format.TimeMigrationUtils;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.Log;
@@ -44,7 +45,6 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Date;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
@@ -150,7 +150,7 @@
/** A partial stack that localizes the caller of the operation. */
final StackTraceElement[] stack;
/** The date, in local time, the error was created. */
- final String date;
+ final long timestamp;
Error(@NonNull String issue, @NonNull String operation, @NonNull String tag,
@NonNull StackTraceElement[] stack, @NonNull String arg) {
@@ -159,7 +159,7 @@
this.tag = tag;
this.stack = stack;
this.arg = arg;
- this.date = new Date().toString();
+ this.timestamp = SystemClock.elapsedRealtime();
}
}
@@ -347,20 +347,23 @@
* main Looper.
*/
@NonNull
- Handler getHandler(@NonNull Handler.Callback callback) {
+ Handler newHandler(@NonNull Handler.Callback callback) {
Looper looper = mReferenceHandler.getLooper();
if (looper == null) looper = Looper.getMainLooper();
return new Handler(looper, callback);
- };
+ }
- /** Return a CpuTracker. */
+ /**
+ * Return a CpuTracker. The default behavior is to create a new CpuTracker but this changes
+ * for unit tests.
+ **/
@NonNull
- CpuTracker getTracker() {
+ CpuTracker newTracker() {
return new CpuTracker();
}
/** Return true if the feature is enabled. */
- boolean getFeatureEnabled() {
+ boolean isFeatureEnabled() {
return anrTimerServiceEnabled();
}
}
@@ -401,8 +404,8 @@
/** Create a HandlerTimerService that directly uses the supplied handler and tracker. */
@VisibleForTesting
HandlerTimerService(@NonNull Injector injector) {
- mHandler = injector.getHandler(this::expires);
- mCpu = injector.getTracker();
+ mHandler = injector.newHandler(this::expires);
+ mCpu = injector.newTracker();
}
/** Post a message with the specified timeout. The timer is not modified. */
@@ -513,7 +516,26 @@
private final FeatureSwitch mFeature;
/**
- * The common constructor. A null injector results in a normal, production timer.
+ * Create one AnrTimer instance. The instance is given a handler and a "what". Individual
+ * timers are started with {@link #start}. If a timer expires, then a {@link Message} is sent
+ * immediately to the handler with {@link Message.what} set to what and {@link Message.obj} set
+ * to the timer key.
+ *
+ * AnrTimer instances have a label, which must be unique. The label is used for reporting and
+ * debug.
+ *
+ * If an individual timer expires internally, and the "extend" parameter is true, then the
+ * AnrTimer may extend the individual timer rather than immediately delivering the timeout to
+ * the client. The extension policy is not part of the instance.
+ *
+ * This method accepts an {@link #Injector} to tune behavior for testing. This method should
+ * not be called directly by regular clients.
+ *
+ * @param handler The handler to which the expiration message will be delivered.
+ * @param what The "what" parameter for the expiration message.
+ * @param label A name for this instance.
+ * @param extend A flag to indicate if expired timers can be granted extensions.
+ * @param injector An {@link #Injector} to tune behavior for testing.
*/
@VisibleForTesting
AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend,
@@ -522,7 +544,7 @@
mWhat = what;
mLabel = label;
mExtend = extend;
- boolean enabled = injector.getFeatureEnabled();
+ boolean enabled = injector.isFeatureEnabled();
if (!enabled) {
mFeature = new FeatureDisabled();
mTimerService = null;
@@ -538,14 +560,25 @@
}
/**
- * Create one timer instance for production. The client can ask for extensible timeouts.
+ * Create an AnrTimer instance with the default {@link #Injector}. See {@link AnrTimer(Handler,
+ * int, String, boolean, Injector} for a functional description.
+ *
+ * @param handler The handler to which the expiration message will be delivered.
+ * @param what The "what" parameter for the expiration message.
+ * @param label A name for this instance.
+ * @param extend A flag to indicate if expired timers can be granted extensions.
*/
AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
this(handler, what, label, extend, new Injector(handler));
}
/**
- * Create one timer instance for production. There are no extensible timeouts.
+ * Create an AnrTimer instance with the default {@link #Injector} and with extensions disabled.
+ * See {@link AnrTimer(Handler, int, String, boolean, Injector} for a functional description.
+ *
+ * @param handler The handler to which the expiration message will be delivered.
+ * @param what The "what" parameter for the expiration message.
+ * @param label A name for this instance.
*/
AnrTimer(@NonNull Handler handler, int what, @NonNull String label) {
this(handler, what, label, false);
@@ -555,6 +588,8 @@
* Return true if the service is enabled on this instance. Clients should use this method to
* decide if the feature is enabled, and not read the flags directly. This method should be
* deleted if and when the feature is enabled permanently.
+ *
+ * @return true if the service is flag-enabled.
*/
boolean serviceEnabled() {
return mFeature.enabled();
@@ -642,7 +677,7 @@
}
/**
- * Report something about a timer.
+ * Generate a log message for a timer.
*/
private void report(@NonNull Timer timer, @NonNull String msg) {
Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg));
@@ -654,9 +689,13 @@
*/
private abstract class FeatureSwitch {
abstract boolean start(@NonNull V arg, int pid, int uid, long timeoutMs);
+
abstract boolean cancel(@NonNull V arg);
+
abstract boolean accept(@NonNull V arg);
+
abstract boolean discard(@NonNull V arg);
+
abstract boolean enabled();
}
@@ -666,6 +705,7 @@
*/
private class FeatureDisabled extends FeatureSwitch {
/** Start a timer by sending a message to the client's handler. */
+ @Override
boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
final Message msg = mHandler.obtainMessage(mWhat, arg);
mHandler.sendMessageDelayed(msg, timeoutMs);
@@ -673,22 +713,26 @@
}
/** Cancel a timer by removing the message from the client's handler. */
+ @Override
boolean cancel(@NonNull V arg) {
mHandler.removeMessages(mWhat, arg);
return true;
}
/** accept() is a no-op when the feature is disabled. */
+ @Override
boolean accept(@NonNull V arg) {
return true;
}
/** discard() is a no-op when the feature is disabled. */
+ @Override
boolean discard(@NonNull V arg) {
return true;
}
/** The feature is not enabled. */
+ @Override
boolean enabled() {
return false;
}
@@ -703,16 +747,17 @@
/**
* Start a timer.
*/
+ @Override
boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, AnrTimer.this);
synchronized (mLock) {
Timer old = mTimerMap.get(arg);
+ // There is an existing timer. If the timer was running, then cancel the running
+ // timer and restart it. If the timer was expired record a protocol error and
+ // discard the expired timer.
if (old != null) {
- // There is an existing timer. This is a protocol error in the client.
- // Record the error and then clean up by canceling running timers and
- // discarding expired timers.
- restartedLocked(old.status, arg);
if (old.status == TIMER_EXPIRED) {
+ restartedLocked(old.status, arg);
discard(arg);
} else {
cancel(arg);
@@ -735,6 +780,7 @@
/**
* Cancel a timer. Return false if the timer was not found.
*/
+ @Override
boolean cancel(@NonNull V arg) {
synchronized (mLock) {
Timer timer = removeLocked(arg);
@@ -755,6 +801,7 @@
* Accept a timer in the framework-level handler. The timeout has been accepted and the
* timeout handler is executing. Return false if the timer was not found.
*/
+ @Override
boolean accept(@NonNull V arg) {
synchronized (mLock) {
Timer timer = removeLocked(arg);
@@ -775,6 +822,7 @@
* longer interesting. No statistics are collected. Return false if the time was not
* found.
*/
+ @Override
boolean discard(@NonNull V arg) {
synchronized (mLock) {
Timer timer = removeLocked(arg);
@@ -791,40 +839,58 @@
}
/** The feature is enabled. */
+ @Override
boolean enabled() {
return true;
}
}
/**
- * Start a timer associated with arg. If a timer already exists with the same arg, then that
- * timer is canceled and a new timer is created. This returns false if the timer cannot be
- * created.
+ * Start a timer associated with arg. The same object must be used to cancel, accept, or
+ * discard a timer later. If a timer already exists with the same arg, then the existing timer
+ * is canceled and a new timer is created.
+ *
+ * @param arg The key by which the timer is known. This is never examined or modified.
+ * @param pid The Linux process ID of the target being timed.
+ * @param uid The Linux user ID of the target being timed.
+ * @param timeoutMs The timer timeout, in milliseconds.
+ * @return true if the timer was successfully created.
*/
boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
return mFeature.start(arg, pid, uid, timeoutMs);
}
/**
- * Cancel a running timer and remove it from any list. This returns true if the timer was
- * found and false otherwise. It is not an error to cancel a non-existent timer. It is also
- * not an error to cancel an expired timer.
+ * Cancel the running timer associated with arg. The timer is forgotten. If the timer has
+ * expired, the call is treated as a discard. No errors are reported if the timer does not
+ * exist or if the timer has expired.
+ *
+ * @return true if the timer was found and was running.
*/
boolean cancel(@NonNull V arg) {
return mFeature.cancel(arg);
}
/**
- * Accept an expired timer. This returns false if the timer was not found or if the timer was
- * not expired.
+ * Accept the expired timer associated with arg. This indicates that the caller considers the
+ * timer expiration to be a true ANR. (See {@link #discard} for an alternate response.) It is
+ * an error to accept a running timer, however the running timer will be canceled.
+ *
+ * @return true if the timer was found and was expired.
*/
boolean accept(@NonNull V arg) {
return mFeature.accept(arg);
}
/**
- * Discard an expired timer. This returns false if the timer was not found or if the timer was
- * not expired.
+ * Discard the expired timer associated with arg. This indicates that the caller considers the
+ * timer expiration to be a false ANR. ((See {@link #accept} for an alternate response.) One
+ * reason to discard an expired timer is if the process being timed was also being debugged:
+ * such a process could be stopped at a breakpoint and its failure to respond would not be an
+ * error. It is an error to discard a running timer, however the running timer will be
+ * canceled.
+ *
+ * @return true if the timer was found and was expired.
*/
boolean discard(@NonNull V arg) {
return mFeature.discard(arg);
@@ -913,7 +979,10 @@
private static void dump(IndentingPrintWriter ipw, int seq, Error err) {
ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag,
err.issue, err.arg);
- ipw.format(" date:%s\n", err.date);
+
+ final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime();
+ final long etime = offset + err.timestamp;
+ ipw.println(" date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime));
ipw.increaseIndent();
for (int i = 0; i < err.stack.length; i++) {
ipw.println(" " + err.stack[i].toString());
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 2249607..0ab81a5 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -91,6 +91,7 @@
import android.telephony.NetworkRegistrationInfo;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
+import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.StatsEvent;
@@ -99,8 +100,10 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats;
import com.android.internal.os.BinderCallsStats;
+import com.android.internal.os.Clock;
import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.CpuScalingPolicyReader;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
import com.android.internal.os.RailStats;
import com.android.internal.os.RpmStats;
@@ -114,16 +117,21 @@
import com.android.server.net.BaseNetworkObserver;
import com.android.server.pm.UserManagerInternal;
import com.android.server.power.optimization.Flags;
+import com.android.server.power.stats.AggregatedPowerStatsConfig;
import com.android.server.power.stats.BatteryExternalStatsWorker;
import com.android.server.power.stats.BatteryStatsImpl;
import com.android.server.power.stats.BatteryUsageStatsProvider;
-import com.android.server.power.stats.BatteryUsageStatsStore;
+import com.android.server.power.stats.PowerStatsAggregator;
+import com.android.server.power.stats.PowerStatsScheduler;
+import com.android.server.power.stats.PowerStatsStore;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import com.android.server.power.stats.wakeups.CpuWakeupStats;
import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
@@ -136,6 +144,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -153,20 +162,24 @@
static final String TAG = "BatteryStatsService";
static final String TRACE_TRACK_WAKEUP_REASON = "wakeup_reason";
static final boolean DBG = false;
- private static final boolean BATTERY_USAGE_STORE_ENABLED = true;
private static IBatteryStats sService;
private final PowerProfile mPowerProfile;
private final CpuScalingPolicies mCpuScalingPolicies;
+ private final MonotonicClock mMonotonicClock;
private final BatteryStatsImpl.BatteryStatsConfig mBatteryStatsConfig;
final BatteryStatsImpl mStats;
final CpuWakeupStats mCpuWakeupStats;
- private final BatteryUsageStatsStore mBatteryUsageStatsStore;
+ private final PowerStatsStore mPowerStatsStore;
+ private final PowerStatsAggregator mPowerStatsAggregator;
+ private final PowerStatsScheduler mPowerStatsScheduler;
private final BatteryStatsImpl.UserInfoProvider mUserManagerUserInfoProvider;
private final Context mContext;
private final BatteryExternalStatsWorker mWorker;
private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+ private final AtomicFile mConfigFile;
+
private volatile boolean mMonitorEnabled = true;
private native void getRailEnergyPowerStats(RailStats railStats);
@@ -376,6 +389,7 @@
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ mMonotonicClock = new MonotonicClock(new File(systemDir, "monotonic_clock.xml"));
mPowerProfile = new PowerProfile(context);
mCpuScalingPolicies = new CpuScalingPolicyReader().read();
@@ -391,23 +405,43 @@
.setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge)
.setPowerStatsThrottlePeriodCpu(powerStatsThrottlePeriodCpu)
.build();
- mStats = new BatteryStatsImpl(mBatteryStatsConfig, systemDir, handler, this,
- this, mUserManagerUserInfoProvider, mPowerProfile, mCpuScalingPolicies);
+ mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
+ systemDir, handler, this, this, mUserManagerUserInfoProvider, mPowerProfile,
+ mCpuScalingPolicies);
mWorker = new BatteryExternalStatsWorker(context, mStats);
mStats.setExternalStatsSyncLocked(mWorker);
mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
mStats.startTrackingSystemServerCpuTime();
- if (BATTERY_USAGE_STORE_ENABLED) {
- mBatteryUsageStatsStore =
- new BatteryUsageStatsStore(context, mStats, systemDir, mHandler);
- } else {
- mBatteryUsageStatsStore = null;
- }
+ AggregatedPowerStatsConfig aggregatedPowerStatsConfig = getAggregatedPowerStatsConfig();
+ mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, aggregatedPowerStatsConfig);
mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats,
- mBatteryUsageStatsStore);
+ mPowerStatsStore);
+ mPowerStatsAggregator = new PowerStatsAggregator(aggregatedPowerStatsConfig,
+ mStats.getHistory());
+ final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger(
+ com.android.internal.R.integer.config_aggregatedPowerStatsSpanDuration);
+ final long powerStatsAggregationPeriod = context.getResources().getInteger(
+ com.android.internal.R.integer.config_powerStatsAggregationPeriod);
+ mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator,
+ aggregatedPowerStatsSpanDuration, powerStatsAggregationPeriod, mPowerStatsStore,
+ Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats, mBatteryUsageStatsProvider);
mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler);
+ mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config"));
+ }
+
+ private AggregatedPowerStatsConfig getAggregatedPowerStatsConfig() {
+ AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
+ return config;
}
/**
@@ -466,9 +500,7 @@
*/
public void onSystemReady() {
mStats.onSystemReady();
- if (BATTERY_USAGE_STORE_ENABLED) {
- mBatteryUsageStatsStore.onSystemReady();
- }
+ mPowerStatsScheduler.start(Flags.streamlinedBatteryStats());
}
private final class LocalService extends BatteryStatsInternal {
@@ -639,6 +671,9 @@
// Shutdown the thread we made.
mWorker.shutdown();
+
+ // To insure continuity, write the monotonic timeshift after writing the last history event
+ mMonotonicClock.write();
}
public static IBatteryStats getService() {
@@ -892,12 +927,8 @@
bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
break;
case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET:
- if (!BATTERY_USAGE_STORE_ENABLED) {
- return StatsManager.PULL_SKIP;
- }
-
- final long sessionStart = mBatteryUsageStatsStore
- .getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
+ final long sessionStart =
+ getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
final long sessionEnd;
synchronized (mStats) {
sessionEnd = mStats.getStartClockTime();
@@ -910,8 +941,7 @@
.aggregateSnapshots(sessionStart, sessionEnd)
.build();
bus = getBatteryUsageStats(List.of(queryBeforeReset)).get(0);
- mBatteryUsageStatsStore
- .setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);
+ setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);
break;
default:
throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
@@ -2641,7 +2671,15 @@
}
private void dumpAggregatedStats(PrintWriter pw) {
- mStats.dumpAggregatedStats(pw, /* startTime */ 0, /* endTime */0);
+ mPowerStatsScheduler.aggregateAndDumpPowerStats(pw);
+ }
+
+ private void dumpPowerStatsStore(PrintWriter pw) {
+ mPowerStatsStore.dump(new IndentingPrintWriter(pw, " "));
+ }
+
+ private void dumpPowerStatsStoreTableOfContents(PrintWriter pw) {
+ mPowerStatsStore.dumpTableOfContents(new IndentingPrintWriter(pw, " "));
}
private void dumpMeasuredEnergyStats(PrintWriter pw) {
@@ -2789,7 +2827,7 @@
synchronized (mStats) {
mStats.resetAllStatsAndHistoryLocked(
BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
- mBatteryUsageStatsStore.removeAllSnapshots();
+ mPowerStatsStore.reset();
pw.println("Battery stats and history reset.");
noOutput = true;
}
@@ -2891,6 +2929,12 @@
} else if ("--aggregated".equals(arg)) {
dumpAggregatedStats(pw);
return;
+ } else if ("--store".equals(arg)) {
+ dumpPowerStatsStore(pw);
+ return;
+ } else if ("--store-toc".equals(arg)) {
+ dumpPowerStatsStoreTableOfContents(pw);
+ return;
} else if ("-a".equals(arg)) {
flags |= BatteryStats.DUMP_VERBOSE;
} else if (arg.length() > 0 && arg.charAt(0) == '-'){
@@ -2951,7 +2995,7 @@
in.unmarshall(raw, 0, raw.length);
in.setDataPosition(0);
BatteryStatsImpl checkinStats = new BatteryStatsImpl(
- mBatteryStatsConfig,
+ mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
null, mStats.mHandler, null, null,
mUserManagerUserInfoProvider, mPowerProfile,
mCpuScalingPolicies);
@@ -2993,7 +3037,7 @@
in.unmarshall(raw, 0, raw.length);
in.setDataPosition(0);
BatteryStatsImpl checkinStats = new BatteryStatsImpl(
- mBatteryStatsConfig,
+ mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
null, mStats.mHandler, null, null,
mUserManagerUserInfoProvider, mPowerProfile,
mCpuScalingPolicies);
@@ -3360,6 +3404,52 @@
}
}
+ private static final String BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY =
+ "BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP";
+
+ /**
+ * Saves the supplied timestamp of the BATTERY_USAGE_STATS_BEFORE_RESET statsd atom pull
+ * in persistent file.
+ */
+ public void setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(long timestamp) {
+ synchronized (mConfigFile) {
+ Properties props = new Properties();
+ try (InputStream in = mConfigFile.openRead()) {
+ props.load(in);
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
+ }
+ props.put(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY,
+ String.valueOf(timestamp));
+ FileOutputStream out = null;
+ try {
+ out = mConfigFile.startWrite();
+ props.store(out, "Statsd atom pull timestamps");
+ mConfigFile.finishWrite(out);
+ } catch (IOException e) {
+ mConfigFile.failWrite(out);
+ Slog.e(TAG, "Cannot save config file " + mConfigFile, e);
+ }
+ }
+ }
+
+ /**
+ * Retrieves the previously saved timestamp of the last BATTERY_USAGE_STATS_BEFORE_RESET
+ * statsd atom pull.
+ */
+ public long getLastBatteryUsageStatsBeforeResetAtomPullTimestamp() {
+ synchronized (mConfigFile) {
+ Properties props = new Properties();
+ try (InputStream in = mConfigFile.openRead()) {
+ props.load(in);
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
+ }
+ return Long.parseLong(
+ props.getProperty(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, "0"));
+ }
+ }
+
/**
* Sets battery AC charger to enabled/disabled, and freezes the battery state.
*/
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 127c5b3..3c56752 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -1440,10 +1440,9 @@
r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED);
}
- // Broadcast is being executed, its package can't be stopped.
try {
- mService.mPackageManagerInt.setPackageStoppedState(
- r.curComponent.getPackageName(), false, r.userId);
+ mService.mPackageManagerInt.notifyComponentUsed(
+ r.curComponent.getPackageName(), r.userId, r.callerPackage, r.toString());
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Failed trying to unstop package "
+ r.curComponent.getPackageName() + ": " + e);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a428907..b481697 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -258,7 +258,8 @@
private static final int MSG_PROCESS_FREEZABLE_CHANGED = 6;
private static final int MSG_UID_STATE_CHANGED = 7;
- // Required when Flags.anrTimerServiceEnabled is false.
+ // Required when Flags.anrTimerServiceEnabled is false. This constant should be deleted if and
+ // when the flag is fused on.
private static final int MSG_DELIVERY_TIMEOUT_SOFT = 8;
private void enqueueUpdateRunningList() {
@@ -274,7 +275,8 @@
updateRunningList();
return true;
}
- // Required when Flags.anrTimerServiceEnabled is false.
+ // Required when Flags.anrTimerServiceEnabled is false. This case should be deleted if
+ // and when the flag is fused on.
case MSG_DELIVERY_TIMEOUT_SOFT: {
synchronized (mService) {
deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj, msg.arg1);
@@ -1169,7 +1171,8 @@
r.resultTo = null;
}
- // Required when Flags.anrTimerServiceEnabled is false.
+ // Required when Flags.anrTimerServiceEnabled is false. This function can be replaced with a
+ // single call to {@code mAnrTimer.start()} if and when the flag is fused on.
private void startDeliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue,
int softTimeoutMillis) {
if (mAnrTimer.serviceEnabled()) {
@@ -1181,7 +1184,8 @@
}
}
- // Required when Flags.anrTimerServiceEnabled is false.
+ // Required when Flags.anrTimerServiceEnabled is false. This function can be replaced with a
+ // single call to {@code mAnrTimer.cancel()} if and when the flag is fused on.
private void cancelDeliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue) {
mAnrTimer.cancel(queue);
if (!mAnrTimer.serviceEnabled()) {
@@ -1189,7 +1193,8 @@
}
}
- // Required when Flags.anrTimerServiceEnabled is false.
+ // Required when Flags.anrTimerServiceEnabled is false. This function can be deleted entirely
+ // if and when the flag is fused on.
private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue,
int softTimeoutMillis) {
if (queue.app != null) {
@@ -1977,8 +1982,8 @@
mService.notifyPackageUse(receiverPackageName,
PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
- mService.mPackageManagerInt.setPackageStoppedState(
- receiverPackageName, false, r.userId);
+ mService.mPackageManagerInt.notifyComponentUsed(
+ receiverPackageName, r.userId, r.callerPackage, r.toString());
}
private void reportUsageStatsBroadcastDispatched(@NonNull ProcessRecord app,
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index a451f36..9bba08a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2373,8 +2373,7 @@
}
}
- if (ppr.getLastProviderTime() > 0
- && (ppr.getLastProviderTime() + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
+ if ((ppr.getLastProviderTime() + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
if (adj > PREVIOUS_APP_ADJ) {
adj = PREVIOUS_APP_ADJ;
schedGroup = SCHED_GROUP_BACKGROUND;
diff --git a/services/core/java/com/android/server/am/ProcessProviderRecord.java b/services/core/java/com/android/server/am/ProcessProviderRecord.java
index 751e8a82..9b72a3a 100644
--- a/services/core/java/com/android/server/am/ProcessProviderRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProviderRecord.java
@@ -34,7 +34,7 @@
/**
* The last time someone else was using a provider in this process.
*/
- private long mLastProviderTime;
+ private long mLastProviderTime = Long.MIN_VALUE;
/**
* class (String) -> ContentProviderRecord.
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 3771c05..f02b8c7 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -511,8 +511,8 @@
pw.print(prefix); pw.print("pid="); pw.println(mPid);
pw.print(prefix); pw.print("lastActivityTime=");
TimeUtils.formatDuration(mLastActivityTime, nowUptime, pw);
- pw.print(prefix); pw.print("startUptimeTime=");
- TimeUtils.formatDuration(mStartElapsedTime, nowUptime, pw);
+ pw.print(prefix); pw.print("startUpTime=");
+ TimeUtils.formatDuration(mStartUptime, nowUptime, pw);
pw.print(prefix); pw.print("startElapsedTime=");
TimeUtils.formatDuration(mStartElapsedTime, nowElapsedTime, pw);
pw.println();
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index a9c388c..27c0876 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -280,7 +280,7 @@
* The last time the process was in the TOP state or greater.
*/
@GuardedBy("mService")
- private long mLastTopTime;
+ private long mLastTopTime = Long.MIN_VALUE;
/**
* Is this an empty background process?
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 1ba1f55..aa788eb 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -118,6 +118,7 @@
// The list is sorted.
@VisibleForTesting
static final String[] sDeviceConfigAconfigScopes = new String[] {
+ "accessibility",
"android_core_networking",
"angle",
"arc_next",
@@ -152,6 +153,7 @@
"preload_safety",
"responsible_apis",
"rust",
+ "safety_center",
"system_performance",
"test_suites",
"text",
@@ -159,13 +161,23 @@
"tv_system_ui",
"vibrator",
"virtual_devices",
+ "wear_calling_messaging",
+ "wear_connectivity",
+ "wear_esim_carriers",
"wear_frameworks",
+ "wear_health_services",
+ "wear_media",
+ "wear_offload",
+ "wear_security",
"wear_system_health",
"wear_systems",
"window_surfaces",
- "windowing_frontend"
+ "windowing_frontend",
};
+ public static final String NAMESPACE_REBOOT_STAGING = "staged";
+ public static final String NAMESPACE_REBOOT_STAGING_DELIMITER = "*";
+
private final String[] mGlobalSettings;
private final String[] mDeviceConfigScopes;
@@ -261,6 +273,22 @@
}
});
}
+
+ // add sys prop sync callback for staged flag values
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_REBOOT_STAGING,
+ AsyncTask.THREAD_POOL_EXECUTOR,
+ (DeviceConfig.Properties properties) -> {
+ String scope = properties.getNamespace();
+ for (String key : properties.getKeyset()) {
+ String aconfigPropertyName = makeAconfigFlagStagedPropertyName(key);
+ if (aconfigPropertyName == null) {
+ log("unable to construct system property for " + scope + "/" + key);
+ return;
+ }
+ setProperty(aconfigPropertyName, properties.getString(key, null));
+ }
+ });
}
public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
@@ -332,6 +360,35 @@
}
/**
+ * system property name constructing rule for staged aconfig flags, the flag name
+ * is in the form of [namespace]*[actual flag name], we should push the following
+ * to system properties
+ * "next_boot.[actual sys prop name]".
+ * If the name contains invalid characters or substrings for system property name,
+ * will return null.
+ * @param flagName
+ * @return
+ */
+ @VisibleForTesting
+ static String makeAconfigFlagStagedPropertyName(String flagName) {
+ int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
+ if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
+ log("invalid staged flag: " + flagName);
+ return null;
+ }
+
+ String propertyName = "next_boot." + makeAconfigFlagPropertyName(
+ flagName.substring(0, idx), flagName.substring(idx+1));
+
+ if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
+ || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
+ return null;
+ }
+
+ return propertyName;
+ }
+
+ /**
* system property name constructing rule for aconfig flags:
* "persist.device_config.aconfig_flags.[category_name].[flag_name]".
* If the name contains invalid characters or substrings for system property name,
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 26d99d8..bb9ea28 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -2,7 +2,7 @@
flag {
name: "oomadjuster_correctness_rewrite"
- namespace: "android_platform_power_optimization"
+ namespace: "backstage_power"
description: "Utilize new OomAdjuster implementation"
bug: "298055811"
is_fixed_read_only: true
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 9cfac9a..eea3d38 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -68,6 +68,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
+import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -496,8 +497,9 @@
AudioDeviceInfo.TYPE_AUX_LINE
};
- /*package */ static boolean isValidCommunicationDevice(AudioDeviceInfo device) {
- return isValidCommunicationDeviceType(device.getType());
+ /*package */ static boolean isValidCommunicationDevice(@NonNull AudioDeviceInfo device) {
+ Objects.requireNonNull(device, "device must not be null");
+ return device.isSink() && isValidCommunicationDeviceType(device.getType());
}
private static boolean isValidCommunicationDeviceType(int deviceType) {
diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java
index 6c0fef5..5f4e4c3 100644
--- a/services/core/java/com/android/server/audio/MusicFxHelper.java
+++ b/services/core/java/com/android/server/audio/MusicFxHelper.java
@@ -157,7 +157,8 @@
Log.w(TAG, " inside handle MSG_EFFECT_CLIENT_GONE");
// Once the uid is no longer running, close all remain audio session(s) for this UID
if (mClientUidSessionMap.get(Integer.valueOf(uid)) != null) {
- final List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(uid));
+ final List<Integer> sessions =
+ new ArrayList(mClientUidSessionMap.get(Integer.valueOf(uid)));
Log.i(TAG, "UID " + uid + " gone, closing " + sessions.size() + " sessions");
for (Integer session : sessions) {
Intent intent = new Intent(
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index b537e0e..b12d831 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -6,3 +6,10 @@
description: "This flag controls tunscany virtual HAL feature"
bug: "294254230"
}
+
+flag {
+ name: "de_hidl"
+ namespace: "biometrics_framework"
+ description: "feature flag for biometrics de-hidl"
+ bug: "287332354"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 78c95ad..a47135f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -35,6 +35,7 @@
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -116,7 +117,24 @@
@LockoutTracker.LockoutMode
public int handleFailedAttempt(int userId) {
- return LockoutTracker.LOCKOUT_NONE;
+ if (Flags.deHidl()) {
+ if (mLockoutTracker != null) {
+ mLockoutTracker.addFailedAttemptForUser(getTargetUserId());
+ }
+ @LockoutTracker.LockoutMode final int lockoutMode =
+ getLockoutTracker().getLockoutModeForUser(userId);
+ final PerformanceTracker performanceTracker =
+ PerformanceTracker.getInstanceForSensorId(getSensorId());
+ if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
+ performanceTracker.incrementPermanentLockoutForUser(userId);
+ } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
+ performanceTracker.incrementTimedLockoutForUser(userId);
+ }
+
+ return lockoutMode;
+ } else {
+ return LockoutTracker.LOCKOUT_NONE;
+ }
}
protected long getStartTimeMs() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 8a54ae5..037ea38a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -586,4 +586,13 @@
}
}, 10000);
}
+
+ /**
+ * Handle stop user client when user switching occurs.
+ */
+ public void onUserStopped() {}
+
+ public Handler getHandler() {
+ return mHandler;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java
index 95c4903..35e9bcb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java
@@ -32,6 +32,7 @@
mUserLockoutStates = new SparseIntArray();
}
+ @Override
public void setLockoutModeForUser(int userId, @LockoutMode int mode) {
Slog.d(TAG, "Lockout for user: " + userId + " is " + mode);
synchronized (this) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java
index 4a59c9d..8271380 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java
@@ -35,4 +35,7 @@
@interface LockoutMode {}
@LockoutMode int getLockoutModeForUser(int userId);
+ void setLockoutModeForUser(int userId, @LockoutMode int mode);
+ default void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {}
+ default void addFailedAttemptForUser(int userId) {}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index 694dfd2..8075470 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -165,6 +165,7 @@
}
}
+ @Override
public void onUserStopped() {
if (mStopUserClient == null) {
Slog.e(getTag(), "Unexpected onUserStopped");
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
index cc00227..e5d4a63 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
@@ -31,6 +31,11 @@
return mCurrentUserLockoutMode;
}
+ @Override
+ public void setLockoutModeForUser(int userId, @LockoutMode int mode) {
+ setCurrentUserLockoutMode(mode);
+ }
+
public void setCurrentUserLockoutMode(@LockoutMode int lockoutMode) {
mCurrentUserLockoutMode = lockoutMode;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java
index 573c20f..d149f52 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java
@@ -38,7 +38,7 @@
/**
* Utilities for converting from hardware to framework-defined AIDL models.
*/
-final class AidlConversionUtils {
+public final class AidlConversionUtils {
private static final String TAG = "AidlConversionUtils";
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
new file mode 100644
index 0000000..57f5b34
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
@@ -0,0 +1,319 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.AuthenticationFrame;
+import android.hardware.biometrics.face.EnrollmentFrame;
+import android.hardware.biometrics.face.Error;
+import android.hardware.biometrics.face.ISessionCallback;
+import android.hardware.face.Face;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.util.Slog;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.EnumerateConsumer;
+import com.android.server.biometrics.sensors.ErrorConsumer;
+import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.face.FaceUtils;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Response handler for the {@link ISessionCallback} HAL AIDL interface.
+ */
+public class AidlResponseHandler extends ISessionCallback.Stub {
+ /**
+ * Interface to send results to the AidlResponseHandler's owner.
+ */
+ public interface HardwareUnavailableCallback {
+ /**
+ * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+ */
+ void onHardwareUnavailable();
+ }
+
+ private static final String TAG = "AidlResponseHandler";
+
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final BiometricScheduler mScheduler;
+ private final int mSensorId;
+ private final int mUserId;
+ @NonNull
+ private final LockoutTracker mLockoutCache;
+ @NonNull
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
+
+ @NonNull
+ private final AuthSessionCoordinator mAuthSessionCoordinator;
+ @NonNull
+ private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+
+ public AidlResponseHandler(@NonNull Context context,
+ @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+ @NonNull LockoutTracker lockoutTracker,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull AuthSessionCoordinator authSessionCoordinator,
+ @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+ mContext = context;
+ mScheduler = scheduler;
+ mSensorId = sensorId;
+ mUserId = userId;
+ mLockoutCache = lockoutTracker;
+ mLockoutResetDispatcher = lockoutResetDispatcher;
+ mAuthSessionCoordinator = authSessionCoordinator;
+ mHardwareUnavailableCallback = hardwareUnavailableCallback;
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ return this.HASH;
+ }
+
+ @Override
+ public void onChallengeGenerated(long challenge) {
+ handleResponse(FaceGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(mSensorId,
+ mUserId, challenge), null);
+ }
+
+ @Override
+ public void onChallengeRevoked(long challenge) {
+ handleResponse(FaceRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(mSensorId,
+ mUserId, challenge), null);
+ }
+
+ @Override
+ public void onAuthenticationFrame(AuthenticationFrame frame) {
+ handleResponse(FaceAuthenticationClient.class, (c) -> {
+ if (frame == null) {
+ Slog.e(TAG, "Received null enrollment frame for face authentication client.");
+ return;
+ }
+ c.onAuthenticationFrame(AidlConversionUtils.toFrameworkAuthenticationFrame(frame));
+ }, null);
+ }
+
+ @Override
+ public void onEnrollmentFrame(EnrollmentFrame frame) {
+ handleResponse(FaceEnrollClient.class, (c) -> {
+ if (frame == null) {
+ Slog.e(TAG, "Received null enrollment frame for face enroll client.");
+ return;
+ }
+ c.onEnrollmentFrame(AidlConversionUtils.toFrameworkEnrollmentFrame(frame));
+ }, null);
+ }
+
+ @Override
+ public void onError(byte error, int vendorCode) {
+ onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
+ }
+
+ /**
+ * Handle error messages from the HAL.
+ */
+ public void onError(int error, int vendorCode) {
+ handleResponse(ErrorConsumer.class, (c) -> {
+ c.onError(error, vendorCode);
+ if (error == Error.HW_UNAVAILABLE) {
+ mHardwareUnavailableCallback.onHardwareUnavailable();
+ }
+ }, null);
+ }
+
+ @Override
+ public void onEnrollmentProgress(int enrollmentId, int remaining) {
+ BaseClientMonitor client = mScheduler.getCurrentClient();
+ final int currentUserId;
+ if (client == null) {
+ return;
+ } else {
+ currentUserId = client.getTargetUserId();
+ }
+ final CharSequence name = FaceUtils.getInstance(mSensorId)
+ .getUniqueName(mContext, currentUserId);
+ final Face face = new Face(name, enrollmentId, mSensorId);
+
+ handleResponse(FaceEnrollClient.class, (c) -> c.onEnrollResult(face, remaining), null);
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
+ final Face face = new Face("" /* name */, enrollmentId, mSensorId);
+ final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
+ final ArrayList<Byte> byteList = new ArrayList<>();
+ for (byte b : byteArray) {
+ byteList.add(b);
+ }
+ handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
+ true /* authenticated */, byteList), null);
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId);
+ handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
+ false /* authenticated */, null /* hat */), null);
+ }
+
+ @Override
+ public void onLockoutTimed(long durationMillis) {
+ handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), null);
+ }
+
+ @Override
+ public void onLockoutPermanent() {
+ handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+ }
+
+ @Override
+ public void onLockoutCleared() {
+ handleResponse(FaceResetLockoutClient.class, FaceResetLockoutClient::onLockoutCleared,
+ (c) -> FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
+ mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
+ Utils.getCurrentStrength(mSensorId), -1 /* requestId */));
+ }
+
+ @Override
+ public void onInteractionDetected() {
+ handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected, null);
+ }
+
+ @Override
+ public void onEnrollmentsEnumerated(int[] enrollmentIds) {
+ if (enrollmentIds.length > 0) {
+ for (int i = 0; i < enrollmentIds.length; ++i) {
+ final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
+ final int finalI = i;
+ handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(face,
+ enrollmentIds.length - finalI - 1), null);
+ }
+ } else {
+ handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(
+ null /* identifier */, 0 /* remaining */), null);
+ }
+ }
+
+ @Override
+ public void onFeaturesRetrieved(byte[] features) {
+ handleResponse(FaceGetFeatureClient.class, (c) -> c.onFeatureGet(true /* success */,
+ features), null);
+ }
+
+ @Override
+ public void onFeatureSet(byte feature) {
+ handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */), null);
+ }
+
+ @Override
+ public void onEnrollmentsRemoved(int[] enrollmentIds) {
+ if (enrollmentIds.length > 0) {
+ for (int i = 0; i < enrollmentIds.length; i++) {
+ final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
+ final int finalI = i;
+ handleResponse(RemovalConsumer.class,
+ (c) -> c.onRemoved(face, enrollmentIds.length - finalI - 1),
+ null);
+ }
+ } else {
+ handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */,
+ 0 /* remaining */), null);
+ }
+ }
+
+ @Override
+ public void onAuthenticatorIdRetrieved(long authenticatorId) {
+ handleResponse(FaceGetAuthenticatorIdClient.class, (c) -> c.onAuthenticatorIdRetrieved(
+ authenticatorId), null);
+ }
+
+ @Override
+ public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
+ handleResponse(FaceInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
+ newAuthenticatorId), null);
+ }
+
+ /**
+ * Handles acquired messages sent by the HAL (specifically for HIDL HAL).
+ */
+ public void onAcquired(int acquiredInfo, int vendorCode) {
+ handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode),
+ null);
+ }
+
+ /**
+ * Handles lockout changed messages sent by the HAL (specifically for HIDL HAL).
+ */
+ public void onLockoutChanged(long duration) {
+ mScheduler.getHandler().post(() -> {
+ @LockoutTracker.LockoutMode final int lockoutMode;
+ if (duration == 0) {
+ lockoutMode = LockoutTracker.LOCKOUT_NONE;
+ } else if (duration == -1 || duration == Long.MAX_VALUE) {
+ lockoutMode = LockoutTracker.LOCKOUT_PERMANENT;
+ } else {
+ lockoutMode = LockoutTracker.LOCKOUT_TIMED;
+ }
+
+ mLockoutCache.setLockoutModeForUser(mUserId, lockoutMode);
+
+ if (duration == 0) {
+ mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
+ }
+ });
+ }
+
+ private <T> void handleResponse(@NonNull Class<T> className,
+ @NonNull Consumer<T> actionIfClassMatchesClient,
+ @Nullable Consumer<BaseClientMonitor> alternateAction) {
+ mScheduler.getHandler().post(() -> {
+ final BaseClientMonitor client = mScheduler.getCurrentClient();
+ if (className.isInstance(client)) {
+ actionIfClassMatchesClient.accept((T) client);
+ } else {
+ Slog.d(TAG, "Current client is not an instance of " + className.getName());
+ if (alternateAction != null) {
+ alternateAction.accept(client);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onSessionClosed() {
+ mScheduler.getHandler().post(mScheduler::onUserStopped);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
index 29eee6b..858bb86 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
@@ -16,10 +16,14 @@
package com.android.server.biometrics.sensors.face.aidl;
-import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback;
-
import android.annotation.NonNull;
+import android.content.Context;
import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+
+import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
+
+import java.util.function.Supplier;
/**
* A holder for an AIDL {@link ISession} with additional metadata about the current user
@@ -31,14 +35,22 @@
@NonNull
private final ISession mSession;
private final int mUserId;
- @NonNull private final HalSessionCallback mHalSessionCallback;
+ @NonNull private final AidlResponseHandler mAidlResponseHandler;
public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId,
- HalSessionCallback halSessionCallback) {
+ AidlResponseHandler aidlResponseHandler) {
mHalInterfaceVersion = halInterfaceVersion;
mSession = session;
mUserId = userId;
- mHalSessionCallback = halSessionCallback;
+ mAidlResponseHandler = aidlResponseHandler;
+ }
+
+ public AidlSession(Context context, Supplier<IBiometricsFace> session, int userId,
+ AidlResponseHandler aidlResponseHandler) {
+ mSession = new AidlToHidlAdapter(context, session, userId, aidlResponseHandler);
+ mHalInterfaceVersion = 0;
+ mUserId = userId;
+ mAidlResponseHandler = aidlResponseHandler;
}
/** The underlying {@link ISession}. */
@@ -52,8 +64,8 @@
}
/** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
- HalSessionCallback getHalSessionCallback() {
- return mHalSessionCallback;
+ AidlResponseHandler getHalSessionCallback() {
+ return mAidlResponseHandler;
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 35fc43a..470dc4b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -46,8 +46,8 @@
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.face.UsageStats;
@@ -57,7 +57,8 @@
/**
* Face-specific authentication client for the {@link IFace} AIDL HAL interface.
*/
-class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAuthenticateOptions>
+public class FaceAuthenticationClient
+ extends AuthenticationClient<AidlSession, FaceAuthenticateOptions>
implements LockoutConsumer {
private static final String TAG = "FaceAuthenticationClient";
@@ -74,11 +75,11 @@
@Nullable
private ICancellationSignal mCancellationSignal;
@Nullable
- private SensorPrivacyManager mSensorPrivacyManager;
+ private final SensorPrivacyManager mSensorPrivacyManager;
@FaceManager.FaceAcquired
private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN;
- FaceAuthenticationClient(@NonNull Context context,
+ public FaceAuthenticationClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, long operationId,
@@ -86,7 +87,7 @@
boolean requireConfirmation,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
boolean isStrongBiometric, @NonNull UsageStats usageStats,
- @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
+ @NonNull LockoutTracker lockoutCache, boolean allowBackgroundAuthentication,
@Authenticators.Types int sensorStrength) {
this(context, lazyDaemon, token, requestId, listener, operationId,
restricted, options, cookie, requireConfirmation, logger, biometricContext,
@@ -103,12 +104,12 @@
boolean requireConfirmation,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
boolean isStrongBiometric, @NonNull UsageStats usageStats,
- @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
+ @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
SensorPrivacyManager sensorPrivacyManager,
@Authenticators.Types int biometricStrength) {
super(context, lazyDaemon, token, listener, operationId, restricted,
options, cookie, requireConfirmation, logger, biometricContext,
- isStrongBiometric, null /* taskStackListener */, null /* lockoutCache */,
+ isStrongBiometric, null /* taskStackListener */, lockoutTracker,
allowBackgroundAuthentication, false /* shouldVibrate */,
biometricStrength);
setRequestId(requestId);
@@ -263,8 +264,13 @@
mLastAcquire = acquireInfo;
final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
- PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
- pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
+
+ //Check if it is AIDL (lockoutTracker = null) or if it there is no lockout for HIDL
+ if (getLockoutTracker() == null || getLockoutTracker().getLockoutModeForUser(
+ getTargetUserId()) == LockoutTracker.LOCKOUT_NONE) {
+ PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
+ pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
+ }
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index f55cf05..dbed1f7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -85,7 +85,7 @@
}
};
- FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+ public FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId,
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
index 165c3a2..e404bd2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
@@ -36,7 +36,7 @@
public class FaceGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {
private static final String TAG = "FaceGenerateChallengeClient";
- FaceGenerateChallengeClient(@NonNull Context context,
+ public FaceGenerateChallengeClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
int sensorId, @NonNull BiometricLogger logger,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
index ef3b345..c15049b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.ISession;
import android.os.IBinder;
import android.os.RemoteException;
import android.provider.Settings;
@@ -33,6 +34,7 @@
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.HalClientMonitor;
+import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
import java.util.HashMap;
import java.util.Map;
@@ -46,14 +48,16 @@
private static final String TAG = "FaceGetFeatureClient";
private final int mUserId;
+ private final int mFeature;
- FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+ public FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int sensorId, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext) {
+ @NonNull BiometricContext biometricContext, int feature) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
logger, biometricContext);
mUserId = userId;
+ mFeature = feature;
}
@Override
@@ -70,7 +74,11 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().getSession().getFeatures();
+ ISession session = getFreshDaemon().getSession();
+ if (session instanceof AidlToHidlAdapter) {
+ ((AidlToHidlAdapter) session).setFeature(mFeature);
+ }
+ session.getFeatures();
} catch (RemoteException e) {
Slog.e(TAG, "Unable to getFeature", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index f09d192..e75c6ab 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -38,9 +38,9 @@
/**
* Face-specific internal cleanup client for the {@link IFace} AIDL HAL interface.
*/
-class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> {
+public class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> {
- FaceInternalCleanupClient(@NonNull Context context,
+ public FaceInternalCleanupClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
int sensorId, @NonNull BiometricLogger logger,
@NonNull BiometricContext biometricContext,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index cc3118c..dd9c6d5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -493,7 +493,7 @@
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
- mUsageStats, mFaceSensors.get(sensorId).getLockoutCache(),
+ mUsageStats, null /* lockoutTracker */,
allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
@@ -619,7 +619,7 @@
final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), token, callback, userId,
mContext.getOpPackageName(), sensorId, BiometricLogger.ofUnknown(mContext),
- mBiometricContext);
+ mBiometricContext, feature);
scheduleForSensor(sensorId, client);
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
index 0512017..0793888 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
@@ -36,12 +36,12 @@
/**
* Face-specific removal client for the {@link IFace} AIDL HAL interface.
*/
-class FaceRemovalClient extends RemovalClient<Face, AidlSession> {
+public class FaceRemovalClient extends RemovalClient<Face, AidlSession> {
private static final String TAG = "FaceRemovalClient";
final int[] mBiometricIds;
- FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+ public FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
int[] biometricIds, int userId, @NonNull String owner,
@NonNull BiometricUtils<Face> utils, int sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index 1a12fcd..77b5592 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -32,7 +32,6 @@
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.HalClientMonitor;
-import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -48,14 +47,14 @@
private static final String TAG = "FaceResetLockoutClient";
private final HardwareAuthToken mHardwareAuthToken;
- private final LockoutCache mLockoutCache;
+ private final LockoutTracker mLockoutCache;
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final int mBiometricStrength;
- FaceResetLockoutClient(@NonNull Context context,
+ public FaceResetLockoutClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
+ @NonNull byte[] hardwareAuthToken, @NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@Authenticators.Types int biometricStrength) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
@@ -107,7 +106,7 @@
* be used instead.
*/
static void resetLocalLockoutStateToNone(int sensorId, int userId,
- @NonNull LockoutCache lockoutTracker,
+ @NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
@Authenticators.Types int biometricStrength, long requestId) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
index 8838345..0d6143a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
@@ -38,7 +38,7 @@
private final long mChallenge;
- FaceRevokeChallengeClient(@NonNull Context context,
+ public FaceRevokeChallengeClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
int userId, @NonNull String owner, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
index 6c14387..f6da872 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
@@ -46,7 +46,7 @@
private final boolean mEnabled;
private final HardwareAuthToken mHardwareAuthToken;
- FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+ public FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 2ad41c2..54e66eb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -23,16 +23,10 @@
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.face.AuthenticationFrame;
-import android.hardware.biometrics.face.EnrollmentFrame;
-import android.hardware.biometrics.face.Error;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
-import android.hardware.biometrics.face.ISessionCallback;
-import android.hardware.face.Face;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.keymaster.HardwareAuthToken;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -44,29 +38,22 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AuthSessionCoordinator;
-import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.LockoutCache;
-import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-import com.android.server.biometrics.sensors.RemovalConsumer;
import com.android.server.biometrics.sensors.StartUserClient;
import com.android.server.biometrics.sensors.StopUserClient;
import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.face.FaceUtils;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
@@ -91,397 +78,6 @@
@NonNull private final Supplier<AidlSession> mLazySession;
@Nullable AidlSession mCurrentSession;
- @VisibleForTesting
- public static class HalSessionCallback extends ISessionCallback.Stub {
- /**
- * Interface to sends results to the HalSessionCallback's owner.
- */
- public interface Callback {
- /**
- * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
- */
- void onHardwareUnavailable();
- }
-
- @NonNull
- private final Context mContext;
- @NonNull
- private final Handler mHandler;
- @NonNull
- private final String mTag;
- @NonNull
- private final UserAwareBiometricScheduler mScheduler;
- private final int mSensorId;
- private final int mUserId;
- @NonNull
- private final LockoutCache mLockoutCache;
- @NonNull
- private final LockoutResetDispatcher mLockoutResetDispatcher;
-
- @NonNull
- private AuthSessionCoordinator mAuthSessionCoordinator;
- @NonNull
- private final Callback mCallback;
-
- HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
- @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
- @NonNull LockoutCache lockoutTracker,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull AuthSessionCoordinator authSessionCoordinator,
- @NonNull Callback callback) {
- mContext = context;
- mHandler = handler;
- mTag = tag;
- mScheduler = scheduler;
- mSensorId = sensorId;
- mUserId = userId;
- mLockoutCache = lockoutTracker;
- mLockoutResetDispatcher = lockoutResetDispatcher;
- mAuthSessionCoordinator = authSessionCoordinator;
- mCallback = callback;
- }
-
- @Override
- public int getInterfaceVersion() {
- return this.VERSION;
- }
-
- @Override
- public String getInterfaceHash() {
- return this.HASH;
- }
-
- @Override
- public void onChallengeGenerated(long challenge) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceGenerateChallengeClient)) {
- Slog.e(mTag, "onChallengeGenerated for wrong client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FaceGenerateChallengeClient generateChallengeClient =
- (FaceGenerateChallengeClient) client;
- generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge);
- });
- }
-
- @Override
- public void onChallengeRevoked(long challenge) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceRevokeChallengeClient)) {
- Slog.e(mTag, "onChallengeRevoked for wrong client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FaceRevokeChallengeClient revokeChallengeClient =
- (FaceRevokeChallengeClient) client;
- revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge);
- });
- }
-
- @Override
- public void onAuthenticationFrame(AuthenticationFrame frame) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceAuthenticationClient)) {
- Slog.e(mTag, "onAuthenticationFrame for incompatible client: "
- + Utils.getClientName(client));
- return;
-
- }
- if (frame == null) {
- Slog.e(mTag, "Received null authentication frame for client: "
- + Utils.getClientName(client));
- return;
- }
- ((FaceAuthenticationClient) client).onAuthenticationFrame(
- AidlConversionUtils.toFrameworkAuthenticationFrame(frame));
- });
- }
-
- @Override
- public void onEnrollmentFrame(EnrollmentFrame frame) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceEnrollClient)) {
- Slog.e(mTag, "onEnrollmentFrame for incompatible client: "
- + Utils.getClientName(client));
- return;
- }
- if (frame == null) {
- Slog.e(mTag, "Received null enrollment frame for client: "
- + Utils.getClientName(client));
- return;
- }
- ((FaceEnrollClient) client).onEnrollmentFrame(
- AidlConversionUtils.toFrameworkEnrollmentFrame(frame));
- });
- }
-
- @Override
- public void onError(byte error, int vendorCode) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- Slog.d(mTag, "onError"
- + ", client: " + Utils.getClientName(client)
- + ", error: " + error
- + ", vendorCode: " + vendorCode);
- if (!(client instanceof ErrorConsumer)) {
- Slog.e(mTag, "onError for non-error consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final ErrorConsumer errorConsumer = (ErrorConsumer) client;
- errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
-
- if (error == Error.HW_UNAVAILABLE) {
- mCallback.onHardwareUnavailable();
- }
- });
- }
-
- @Override
- public void onEnrollmentProgress(int enrollmentId, int remaining) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceEnrollClient)) {
- Slog.e(mTag, "onEnrollmentProgress for non-enroll client: "
- + Utils.getClientName(client));
- return;
- }
-
- final int currentUserId = client.getTargetUserId();
- final CharSequence name = FaceUtils.getInstance(mSensorId)
- .getUniqueName(mContext, currentUserId);
- final Face face = new Face(name, enrollmentId, mSensorId);
-
- final FaceEnrollClient enrollClient = (FaceEnrollClient) client;
- enrollClient.onEnrollResult(face, remaining);
- });
- }
-
- @Override
- public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- final Face face = new Face("" /* name */, enrollmentId, mSensorId);
- final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
- final ArrayList<Byte> byteList = new ArrayList<>();
- for (byte b : byteArray) {
- byteList.add(b);
- }
- authenticationConsumer.onAuthenticated(face, true /* authenticated */, byteList);
- });
- }
-
- @Override
- public void onAuthenticationFailed() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId);
- authenticationConsumer.onAuthenticated(face, false /* authenticated */,
- null /* hat */);
- });
- }
-
- @Override
- public void onLockoutTimed(long durationMillis) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof LockoutConsumer)) {
- Slog.e(mTag, "onLockoutTimed for non-lockout consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
- lockoutConsumer.onLockoutTimed(durationMillis);
- });
- }
-
- @Override
- public void onLockoutPermanent() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof LockoutConsumer)) {
- Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
- lockoutConsumer.onLockoutPermanent();
- });
- }
-
- @Override
- public void onLockoutCleared() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceResetLockoutClient)) {
- Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL");
- // Given that onLockoutCleared() can happen at any time, and is not necessarily
- // coming from a specific client, set this to -1 to indicate it wasn't for a
- // specific request.
- FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
- mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- Utils.getCurrentStrength(mSensorId), -1 /* requestId */);
- } else {
- Slog.d(mTag, "onLockoutCleared after resetLockout");
- final FaceResetLockoutClient resetLockoutClient =
- (FaceResetLockoutClient) client;
- resetLockoutClient.onLockoutCleared();
- }
- });
- }
-
- @Override
- public void onInteractionDetected() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceDetectClient)) {
- Slog.e(mTag, "onInteractionDetected for wrong client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FaceDetectClient detectClient = (FaceDetectClient) client;
- detectClient.onInteractionDetected();
- });
- }
-
- @Override
- public void onEnrollmentsEnumerated(int[] enrollmentIds) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof EnumerateConsumer)) {
- Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final EnumerateConsumer enumerateConsumer =
- (EnumerateConsumer) client;
- if (enrollmentIds.length > 0) {
- for (int i = 0; i < enrollmentIds.length; ++i) {
- final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
- enumerateConsumer.onEnumerationResult(face, enrollmentIds.length - i - 1);
- }
- } else {
- enumerateConsumer.onEnumerationResult(null /* identifier */, 0 /* remaining */);
- }
- });
- }
-
- @Override
- public void onFeaturesRetrieved(byte[] features) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceGetFeatureClient)) {
- Slog.e(mTag, "onFeaturesRetrieved for non-get feature consumer: "
- + Utils.getClientName(client));
- return;
- }
- final FaceGetFeatureClient faceGetFeatureClient = (FaceGetFeatureClient) client;
- faceGetFeatureClient.onFeatureGet(true /* success */, features);
- });
-
- }
-
- @Override
- public void onFeatureSet(byte feature) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceSetFeatureClient)) {
- Slog.e(mTag, "onFeatureSet for non-set consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final FaceSetFeatureClient faceSetFeatureClient = (FaceSetFeatureClient) client;
- faceSetFeatureClient.onFeatureSet(true /* success */);
- });
- }
-
- @Override
- public void onEnrollmentsRemoved(int[] enrollmentIds) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof RemovalConsumer)) {
- Slog.e(mTag, "onRemoved for non-removal consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final RemovalConsumer removalConsumer = (RemovalConsumer) client;
- if (enrollmentIds.length > 0) {
- for (int i = 0; i < enrollmentIds.length; i++) {
- final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
- removalConsumer.onRemoved(face, enrollmentIds.length - i - 1);
- }
- } else {
- removalConsumer.onRemoved(null /* identifier */, 0 /* remaining */);
- }
- });
- }
-
- @Override
- public void onAuthenticatorIdRetrieved(long authenticatorId) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceGetAuthenticatorIdClient)) {
- Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final FaceGetAuthenticatorIdClient getAuthenticatorIdClient =
- (FaceGetAuthenticatorIdClient) client;
- getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId);
- });
- }
-
- @Override
- public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceInvalidationClient)) {
- Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final FaceInvalidationClient invalidationClient = (FaceInvalidationClient) client;
- invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId);
- });
- }
-
- @Override
- public void onSessionClosed() {
- mHandler.post(mScheduler::onUserStopped);
- }
- }
Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
@@ -511,9 +107,9 @@
public StartUserClient<?, ?> getStartUserClient(int newUserId) {
final int sensorId = mSensorProperties.sensorId;
- final HalSessionCallback resultController = new HalSessionCallback(mContext,
- mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
- lockoutResetDispatcher,
+ final AidlResponseHandler resultController = new AidlResponseHandler(
+ mContext, mScheduler, sensorId, newUserId,
+ mLockoutCache, lockoutResetDispatcher,
biometricContext.getAuthSessionCoordinator(), () -> {
Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
mCurrentSession = null;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java
new file mode 100644
index 0000000..eecf44b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java
@@ -0,0 +1,347 @@
+/*
+ * 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.biometrics.sensors.face.hidl;
+
+import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.face.EnrollmentStageConfig;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.OptionalBool;
+import android.hardware.biometrics.face.V1_0.Status;
+import android.hardware.common.NativeHandle;
+import android.hardware.face.Face;
+import android.hardware.face.FaceManager;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlConversionUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ */
+public class AidlToHidlAdapter implements ISession {
+
+ private final String TAG = "AidlToHidlAdapter";
+ private static final int CHALLENGE_TIMEOUT_SEC = 600;
+ @DurationMillisLong
+ private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000;
+ @DurationMillisLong
+ private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS = CHALLENGE_TIMEOUT_SEC * 1000;
+ private static final int INVALID_VALUE = -1;
+ private final Clock mClock;
+ private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
+ @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75;
+ private long mGenerateChallengeCreatedAt = INVALID_VALUE;
+ private long mGenerateChallengeResult = INVALID_VALUE;
+ @NonNull private Supplier<IBiometricsFace> mSession;
+ private final int mUserId;
+ private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
+ private final Context mContext;
+ private int mFeature = INVALID_VALUE;
+
+ public AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session,
+ int userId, AidlResponseHandler aidlResponseHandler) {
+ this(context, session, userId, aidlResponseHandler, Clock.systemUTC());
+ }
+
+ AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, int userId,
+ AidlResponseHandler aidlResponseHandler, Clock clock) {
+ mSession = session;
+ mUserId = userId;
+ mContext = context;
+ mClock = clock;
+ setCallback(aidlResponseHandler);
+ }
+
+ private void setCallback(AidlResponseHandler aidlResponseHandler) {
+ mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
+ try {
+ mSession.get().setCallback(mHidlToAidlCallbackConverter);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to set callback");
+ }
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ private boolean isGeneratedChallengeCacheValid() {
+ return mGenerateChallengeCreatedAt != INVALID_VALUE
+ && mGenerateChallengeResult != INVALID_VALUE
+ && mClock.millis() - mGenerateChallengeCreatedAt
+ < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
+ }
+
+ private void incrementChallengeCount() {
+ mGeneratedChallengeCount.add(0, mClock.millis());
+ }
+
+ private int decrementChallengeCount() {
+ final long now = mClock.millis();
+ // ignore values that are old in case generate/revoke calls are not matched
+ // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
+ mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
+ if (!mGeneratedChallengeCount.isEmpty()) {
+ mGeneratedChallengeCount.remove(0);
+ }
+ return mGeneratedChallengeCount.size();
+ }
+
+ @Override
+ public void generateChallenge() throws RemoteException {
+ incrementChallengeCount();
+ if (isGeneratedChallengeCacheValid()) {
+ Slog.d(TAG, "Current challenge is cached and will be reused");
+ mHidlToAidlCallbackConverter.onChallengeGenerated(mGenerateChallengeResult);
+ return;
+ }
+ mGenerateChallengeCreatedAt = mClock.millis();
+ mGenerateChallengeResult = mSession.get().generateChallenge(CHALLENGE_TIMEOUT_SEC).value;
+ mHidlToAidlCallbackConverter.onChallengeGenerated(mGenerateChallengeResult);
+ }
+
+ @Override
+ public void revokeChallenge(long challenge) throws RemoteException {
+ final boolean shouldRevoke = decrementChallengeCount() == 0;
+ if (!shouldRevoke) {
+ Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: "
+ + mGeneratedChallengeCount);
+ mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+ BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
+ return;
+ }
+ mGenerateChallengeCreatedAt = INVALID_VALUE;
+ mGenerateChallengeResult = INVALID_VALUE;
+ mSession.get().revokeChallenge();
+ mHidlToAidlCallbackConverter.onChallengeRevoked(0L);
+ }
+
+ @Override
+ public EnrollmentStageConfig[] getEnrollmentConfig(byte enrollmentType) throws RemoteException {
+ //unsupported in HIDL
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal enroll(HardwareAuthToken hat, byte type, byte[] features,
+ NativeHandle previewSurface) throws RemoteException {
+ final ArrayList<Byte> token = new ArrayList<>();
+ final byte[] hardwareAuthTokenArray = HardwareAuthTokenUtils.toByteArray(hat);
+ for (byte b : hardwareAuthTokenArray) {
+ token.add(b);
+ }
+ final ArrayList<Integer> disabledFeatures = new ArrayList<>();
+ for (byte b: features) {
+ disabledFeatures.add(AidlConversionUtils.convertAidlToFrameworkFeature(b));
+ }
+ mSession.get().enroll(token, ENROLL_TIMEOUT_SEC, disabledFeatures);
+ return new Cancellation();
+ }
+
+ @Override
+ public ICancellationSignal authenticate(long operationId) throws RemoteException {
+ mSession.get().authenticate(operationId);
+ return new Cancellation();
+ }
+
+ @Override
+ public ICancellationSignal detectInteraction() throws RemoteException {
+ mSession.get().authenticate(0);
+ return new Cancellation();
+ }
+
+ @Override
+ public void enumerateEnrollments() throws RemoteException {
+ mSession.get().enumerate();
+ }
+
+ @Override
+ public void removeEnrollments(int[] enrollmentIds) throws RemoteException {
+ mSession.get().remove(enrollmentIds[0]);
+ }
+
+ /**
+ * Needs to be called before getFeatures is invoked.
+ */
+ public void setFeature(int feature) {
+ mFeature = feature;
+ }
+
+ @Override
+ public void getFeatures() throws RemoteException {
+ final int faceId = getFaceId();
+ if (faceId == INVALID_VALUE || mFeature == INVALID_VALUE) {
+ return;
+ }
+
+ final OptionalBool result = mSession.get()
+ .getFeature(mFeature, faceId);
+
+ if (result.status == Status.OK && result.value) {
+ mHidlToAidlCallbackConverter.onFeatureGet(new byte[]{AidlConversionUtils
+ .convertFrameworkToAidlFeature(mFeature)});
+ } else if (result.status == Status.OK) {
+ mHidlToAidlCallbackConverter.onFeatureGet(new byte[]{});
+ } else {
+ mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+ BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0 /* vendorCode */);
+ }
+
+ mFeature = INVALID_VALUE;
+ }
+
+ @Override
+ public void setFeature(HardwareAuthToken hat, byte feature, boolean enabled)
+ throws RemoteException {
+ final int faceId = getFaceId();
+ if (faceId == INVALID_VALUE) {
+ return;
+ }
+ ArrayList<Byte> hardwareAuthTokenList = new ArrayList<>();
+ for (byte b: HardwareAuthTokenUtils.toByteArray(hat)) {
+ hardwareAuthTokenList.add(b);
+ }
+ final int result = mSession.get().setFeature(
+ AidlConversionUtils.convertAidlToFrameworkFeature(feature),
+ enabled, hardwareAuthTokenList, faceId);
+ if (result == Status.OK) {
+ mHidlToAidlCallbackConverter.onFeatureSet(feature);
+ } else {
+ mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+ BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0 /* vendorCode */);
+ }
+ }
+
+ private int getFaceId() {
+ FaceManager faceManager = mContext.getSystemService(FaceManager.class);
+ List<Face> faces = faceManager.getEnrolledFaces(mUserId);
+ if (faces.isEmpty()) {
+ Slog.d(TAG, "No faces to get feature from.");
+ mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+ BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */);
+ return INVALID_VALUE;
+ }
+
+ return faces.get(0).getBiometricId();
+ }
+
+ @Override
+ public void getAuthenticatorId() throws RemoteException {
+ long authenticatorId = mSession.get().getAuthenticatorId().value;
+ mHidlToAidlCallbackConverter.onAuthenticatorIdRetrieved(authenticatorId);
+ }
+
+ @Override
+ public void invalidateAuthenticatorId() throws RemoteException {
+ //unsupported in HIDL
+ }
+
+ @Override
+ public void resetLockout(HardwareAuthToken hat) throws RemoteException {
+ ArrayList<Byte> hardwareAuthToken = new ArrayList<>();
+ for (byte b : HardwareAuthTokenUtils.toByteArray(hat)) {
+ hardwareAuthToken.add(b);
+ }
+ mSession.get().resetLockout(hardwareAuthToken);
+ }
+
+ @Override
+ public void close() throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
+ throws RemoteException {
+ //Unsupported in HIDL
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal enrollWithContext(HardwareAuthToken hat, byte type, byte[] features,
+ NativeHandle previewSurface, OperationContext context) throws RemoteException {
+ //Unsupported in HIDL
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal detectInteractionWithContext(OperationContext context)
+ throws RemoteException {
+ //Unsupported in HIDL
+ return null;
+ }
+
+ @Override
+ public void onContextChanged(OperationContext context) throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ //Unsupported in HIDL
+ return 0;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ //Unsupported in HIDL
+ return null;
+ }
+
+ /**
+ * Cancellation in HIDL occurs for the entire session, instead of a specific client.
+ */
+ private class Cancellation extends ICancellationSignal.Stub {
+
+ Cancellation() {}
+ @Override
+ public void cancel() throws RemoteException {
+ try {
+ mSession.get().cancel();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting cancel", e);
+ }
+ }
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ return null;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 1499317..46ce0b6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -54,6 +54,7 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -61,6 +62,7 @@
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -78,6 +80,8 @@
import com.android.server.biometrics.sensors.face.LockoutHalImpl;
import com.android.server.biometrics.sensors.face.ServiceProvider;
import com.android.server.biometrics.sensors.face.UsageStats;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.face.aidl.AidlSession;
import org.json.JSONArray;
import org.json.JSONException;
@@ -131,7 +135,9 @@
private int mCurrentUserId = UserHandle.USER_NULL;
private final int mSensorId;
private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
private FaceGenerateChallengeClient mGeneratedChallengeCache = null;
+ private AidlSession mSession;
private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
@Override
@@ -361,6 +367,7 @@
mLockoutTracker = new LockoutHalImpl();
mHalResultController = new HalResultController(sensorProps.sensorId, context, mHandler,
mScheduler, mLockoutTracker, lockoutResetDispatcher);
+ mLockoutResetDispatcher = lockoutResetDispatcher;
mHalResultController.setCallback(() -> {
mDaemon = null;
mCurrentUserId = UserHandle.USER_NULL;
@@ -420,6 +427,24 @@
});
}
+ public int getCurrentUserId() {
+ return mCurrentUserId;
+ }
+
+ synchronized AidlSession getSession() {
+ if (mDaemon != null && mSession != null) {
+ return mSession;
+ } else {
+ return mSession = new AidlSession(mContext, this::getDaemon, mCurrentUserId,
+ new AidlResponseHandler(mContext, mScheduler, mSensorId,
+ mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher,
+ new AuthSessionCoordinator(), () -> {
+ mDaemon = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }));
+ }
+ }
+
private synchronized IBiometricsFace getDaemon() {
if (mTestHalEnabled) {
final TestHal testHal = new TestHal(mContext, mSensorId);
@@ -551,32 +576,63 @@
public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
- incrementChallengeCount();
-
- if (isGeneratedChallengeCacheValid()) {
- Slog.d(TAG, "Current challenge is cached and will be reused");
- mGeneratedChallengeCache.reuseResult(receiver);
- return;
- }
-
scheduleUpdateActiveUserWithoutHandler(userId);
- final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
- opPackageName, mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, sSystemClock.millis());
- mGeneratedChallengeCache = client;
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- if (client != clientMonitor) {
- Slog.e(TAG, "scheduleGenerateChallenge onClientStarted, mismatched client."
- + " Expecting: " + client + ", received: " + clientMonitor);
- }
+ if (Flags.deHidl()) {
+ scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName);
+ } else {
+ scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName);
+ }
+ });
+ }
+
+ private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient(
+ mContext, this::getSession, token,
+ new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+ mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+ if (client != clientMonitor) {
+ Slog.e(TAG,
+ "scheduleGenerateChallenge onClientStarted, mismatched client."
+ + " Expecting: " + client + ", received: "
+ + clientMonitor);
}
- });
+ }
+ });
+ }
+
+ private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+ incrementChallengeCount();
+ if (isGeneratedChallengeCacheValid()) {
+ Slog.d(TAG, "Current challenge is cached and will be reused");
+ mGeneratedChallengeCache.reuseResult(receiver);
+ return;
+ }
+
+ final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
+ mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
+ opPackageName, mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext, sSystemClock.millis());
+ mGeneratedChallengeCache = client;
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+ if (client != clientMonitor) {
+ Slog.e(TAG,
+ "scheduleGenerateChallenge onClientStarted, mismatched client."
+ + " Expecting: " + client + ", received: "
+ + clientMonitor);
+ }
+ }
});
}
@@ -584,31 +640,62 @@
public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull String opPackageName, long challenge) {
mHandler.post(() -> {
- final boolean shouldRevoke = decrementChallengeCount() == 0;
- if (!shouldRevoke) {
- Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: "
- + mGeneratedChallengeCount);
- return;
+ if (Flags.deHidl()) {
+ scheduleRevokeChallengeAidl(userId, token, opPackageName);
+ } else {
+ scheduleRevokeChallengeHidl(userId, token, opPackageName);
}
+ });
+ }
- Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients");
- mGeneratedChallengeCache = null;
-
- final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
- mLazyDaemon, token, userId, opPackageName, mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- if (client != clientMonitor) {
- Slog.e(TAG, "scheduleRevokeChallenge, mismatched client."
- + "Expecting: " + client + ", received: " + clientMonitor);
- }
+ private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token,
+ @NonNull String opPackageName) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient
+ client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient(
+ mContext, this::getSession, token, userId, opPackageName, mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector), mBiometricContext, 0L);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ if (client != clientMonitor) {
+ Slog.e(TAG,
+ "scheduleRevokeChallenge, mismatched client." + "Expecting: "
+ + client + ", received: " + clientMonitor);
}
- });
+ }
+ });
+ }
+
+ private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token,
+ @NonNull String opPackageName) {
+ final boolean shouldRevoke = decrementChallengeCount() == 0;
+ if (!shouldRevoke) {
+ Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: "
+ + mGeneratedChallengeCount);
+ return;
+ }
+
+ Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients");
+ mGeneratedChallengeCache = null;
+ final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
+ mLazyDaemon, token, userId, opPackageName, mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ if (client != clientMonitor) {
+ Slog.e(TAG,
+ "scheduleRevokeChallenge, mismatched client." + "Expecting: "
+ + client + ", received: " + clientMonitor);
+ }
+ }
});
}
@@ -620,7 +707,62 @@
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
+ if (Flags.deHidl()) {
+ scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver,
+ opPackageName, disabledFeatures, previewSurface, id);
+ } else {
+ scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver,
+ opPackageName, disabledFeatures, previewSurface, id);
+ }
+ });
+ return id;
+ }
+ private void scheduleEnrollAidl(@NonNull IBinder token,
+ @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
+ @NonNull String opPackageName, @NonNull int[] disabledFeatures,
+ @Nullable Surface previewSurface, long id) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient(
+ mContext, this::getSession, token,
+ new ClientMonitorCallbackConverter(receiver), userId,
+ hardwareAuthToken, opPackageName, id,
+ FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
+ ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector), mBiometricContext,
+ mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_faceMaxTemplatesPerUser),
+ false);
+
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+ mBiometricStateCallback.onClientStarted(clientMonitor);
+ }
+
+ @Override
+ public void onBiometricAction(int action) {
+ mBiometricStateCallback.onBiometricAction(action);
+ }
+
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ mBiometricStateCallback.onClientFinished(clientMonitor, success);
+ if (success) {
+ // Update authenticatorIds
+ scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
+ }
+ }
+ });
+ }
+
+ private void scheduleEnrollHidl(@NonNull IBinder token,
+ @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
+ @NonNull String opPackageName, @NonNull int[] disabledFeatures,
+ @Nullable Surface previewSurface, long id) {
final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
@@ -628,7 +770,6 @@
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext);
-
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -650,8 +791,6 @@
}
}
});
- });
- return id;
}
@Override
@@ -683,18 +822,46 @@
scheduleUpdateActiveUserWithoutHandler(userId);
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
- final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
- mLazyDaemon, token, requestId, receiver, operationId, restricted,
- options, cookie, false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
- mAuthenticationStatsCollector),
- mBiometricContext, isStrongBiometric, mLockoutTracker,
- mUsageStats, allowBackgroundAuthentication,
- Utils.getCurrentStrength(mSensorId));
- mScheduler.scheduleClientMonitor(client);
+ if (Flags.deHidl()) {
+ scheduleAuthenticateAidl(token, operationId, cookie, receiver, options, requestId,
+ restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
+ } else {
+ scheduleAuthenticateHidl(token, operationId, cookie, receiver, options, requestId,
+ restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
+ }
});
}
+ private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId,
+ int cookie, @NonNull ClientMonitorCallbackConverter receiver,
+ @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
+ int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient
+ client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient(
+ mContext, this::getSession, token, requestId, receiver, operationId,
+ restricted, options, cookie, false /* requireConfirmation */,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector), mBiometricContext,
+ isStrongBiometric, mUsageStats, mLockoutTracker,
+ allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+ mScheduler.scheduleClientMonitor(client);
+ }
+
+ private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId,
+ int cookie, @NonNull ClientMonitorCallbackConverter receiver,
+ @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
+ int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
+ final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
+ mLazyDaemon, token, requestId, receiver, operationId, restricted, options,
+ cookie, false /* requireConfirmation */,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector), mBiometricContext,
+ isStrongBiometric, mLockoutTracker, mUsageStats,
+ allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+ mScheduler.scheduleClientMonitor(client);
+ }
+
@Override
public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
int cookie, @NonNull ClientMonitorCallbackConverter receiver,
@@ -719,13 +886,11 @@
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
- final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
- new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
- FaceUtils.getLegacyInstance(mSensorId), mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ if (Flags.deHidl()) {
+ scheduleRemoveAidl(token, userId, receiver, opPackageName, faceId);
+ } else {
+ scheduleRemoveHidl(token, userId, receiver, opPackageName, faceId);
+ }
});
}
@@ -736,17 +901,39 @@
scheduleUpdateActiveUserWithoutHandler(userId);
// For IBiometricsFace@1.0, remove(0) means remove all enrollments
- final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
- new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId,
- opPackageName,
- FaceUtils.getLegacyInstance(mSensorId), mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ if (Flags.deHidl()) {
+ scheduleRemoveAidl(token, userId, receiver, opPackageName, 0);
+ } else {
+ scheduleRemoveHidl(token, userId, receiver, opPackageName, 0);
+ }
});
}
+ private void scheduleRemoveAidl(@NonNull IBinder token, int userId,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient(
+ mContext, this::getSession, token,
+ new ClientMonitorCallbackConverter(receiver), new int[]{faceId}, userId,
+ opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector), mBiometricContext,
+ mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ }
+
+ private void scheduleRemoveHidl(@NonNull IBinder token, int userId,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) {
+ final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
+ new ClientMonitorCallbackConverter(receiver), faceId, userId,
+ opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext, mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ }
+
@Override
public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
mHandler.post(() -> {
@@ -756,89 +943,180 @@
}
scheduleUpdateActiveUserWithoutHandler(userId);
-
- final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
- mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, hardwareAuthToken);
- mScheduler.scheduleClientMonitor(client);
+ if (Flags.deHidl()) {
+ scheduleResetLockoutAidl(userId, hardwareAuthToken);
+ } else {
+ scheduleResetLockoutHidl(userId, hardwareAuthToken);
+ }
});
}
+ private void scheduleResetLockoutAidl(int userId,
+ @NonNull byte[] hardwareAuthToken) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient(
+ mContext, this::getSession, userId, mContext.getOpPackageName(),
+ mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext, hardwareAuthToken, mLockoutTracker,
+ mLockoutResetDispatcher, mSensorProperties.sensorStrength);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
+ private void scheduleResetLockoutHidl(int userId,
+ @NonNull byte[] hardwareAuthToken) {
+ final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
+ mLazyDaemon,
+ userId, mContext.getOpPackageName(), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext, hardwareAuthToken);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
@Override
public void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
boolean enabled, @NonNull byte[] hardwareAuthToken,
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
- final List<Face> faces = getEnrolledFaces(sensorId, userId);
- if (faces.isEmpty()) {
- Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId);
- return;
- }
-
scheduleUpdateActiveUserWithoutHandler(userId);
-
- final int faceId = faces.get(0).getBiometricId();
- final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
- opPackageName, mSensorId, BiometricLogger.ofUnknown(mContext),
- mBiometricContext,
- feature, enabled, hardwareAuthToken, faceId);
- mScheduler.scheduleClientMonitor(client);
+ if (Flags.deHidl()) {
+ scheduleSetFeatureAidl(sensorId, token, userId, feature, enabled, hardwareAuthToken,
+ receiver, opPackageName);
+ } else {
+ scheduleSetFeatureHidl(sensorId, token, userId, feature, enabled, hardwareAuthToken,
+ receiver, opPackageName);
+ }
});
}
+ private void scheduleSetFeatureHidl(int sensorId, @NonNull IBinder token, int userId,
+ int feature, boolean enabled, @NonNull byte[] hardwareAuthToken,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+ final List<Face> faces = getEnrolledFaces(sensorId, userId);
+ if (faces.isEmpty()) {
+ Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId);
+ return;
+ }
+ final int faceId = faces.get(0).getBiometricId();
+ final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, mLazyDaemon,
+ token, new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+ mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, feature,
+ enabled, hardwareAuthToken, faceId);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
+ private void scheduleSetFeatureAidl(int sensorId, @NonNull IBinder token, int userId,
+ int feature, boolean enabled, @NonNull byte[] hardwareAuthToken,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient(
+ mContext, this::getSession, token,
+ new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+ mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext,
+ feature, enabled, hardwareAuthToken);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
+
@Override
public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
@Nullable ClientMonitorCallbackConverter listener, @NonNull String opPackageName) {
mHandler.post(() -> {
- final List<Face> faces = getEnrolledFaces(sensorId, userId);
- if (faces.isEmpty()) {
- Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId);
- return;
- }
-
scheduleUpdateActiveUserWithoutHandler(userId);
- final int faceId = faces.get(0).getBiometricId();
- final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
- token, listener, userId, opPackageName, mSensorId,
- BiometricLogger.ofUnknown(mContext), mBiometricContext,
- feature, faceId);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(
- @NonNull BaseClientMonitor clientMonitor, boolean success) {
- if (success && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) {
- final int settingsValue = client.getValue() ? 1 : 0;
- Slog.d(TAG, "Updating attention value for user: " + userId
- + " to value: " + settingsValue);
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED,
- settingsValue, userId);
- }
- }
- });
+ if (Flags.deHidl()) {
+ scheduleGetFeatureAidl(token, userId, feature, listener,
+ opPackageName);
+ } else {
+ scheduleGetFeatureHidl(sensorId, token, userId, feature, listener,
+ opPackageName);
+ }
});
}
+ private void scheduleGetFeatureHidl(int sensorId, @NonNull IBinder token, int userId,
+ int feature, @Nullable ClientMonitorCallbackConverter listener,
+ @NonNull String opPackageName) {
+ final List<Face> faces = getEnrolledFaces(sensorId, userId);
+ if (faces.isEmpty()) {
+ Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId);
+ return;
+ }
+
+ final int faceId = faces.get(0).getBiometricId();
+ final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
+ token, listener, userId, opPackageName, mSensorId,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext, feature, faceId);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ if (success
+ && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) {
+ final int settingsValue = client.getValue() ? 1 : 0;
+ Slog.d(TAG,
+ "Updating attention value for user: " + userId + " to value: "
+ + settingsValue);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, settingsValue,
+ userId);
+ }
+ }
+ });
+ }
+
+ private void scheduleGetFeatureAidl(@NonNull IBinder token, int userId,
+ int feature, @Nullable ClientMonitorCallbackConverter listener,
+ @NonNull String opPackageName) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient(
+ mContext, this::getSession, token, listener, userId, opPackageName,
+ mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext,
+ feature);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
private void scheduleInternalCleanup(int userId,
@Nullable ClientMonitorCallback callback) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
-
- final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
- mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext,
- FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback,
- mBiometricStateCallback));
+ if (Flags.deHidl()) {
+ scheduleInternalCleanupAidl(userId, callback);
+ } else {
+ scheduleInternalCleanupHidl(userId, callback);
+ }
});
}
+ private void scheduleInternalCleanupHidl(int userId,
+ @Nullable ClientMonitorCallback callback) {
+ final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
+ mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext, FaceUtils.getLegacyInstance(mSensorId),
+ mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client,
+ new ClientMonitorCompositeCallback(callback, mBiometricStateCallback));
+ }
+
+ private void scheduleInternalCleanupAidl(int userId,
+ @Nullable ClientMonitorCallback callback) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient
+ client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient(
+ mContext, this::getSession, userId, mContext.getOpPackageName(),
+ mSensorId, createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext, FaceUtils.getLegacyInstance(mSensorId),
+ mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client,
+ new ClientMonitorCompositeCallback(callback, mBiometricStateCallback));
+ }
+
@Override
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable ClientMonitorCallback callback) {
@@ -970,6 +1248,10 @@
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
if (success) {
+ if (mCurrentUserId != targetUserId) {
+ // Create new session with updated user ID
+ mSession = null;
+ }
mCurrentUserId = targetUserId;
} else {
Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
new file mode 100644
index 0000000..36a9790
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
@@ -0,0 +1,115 @@
+/*
+ * 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.biometrics.sensors.face.hidl;
+
+import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+
+import java.util.ArrayList;
+
+/**
+ * Convert HIDL-specific callback interface {@link IBiometricsFaceClientCallback} to AIDL
+ * response handler.
+ */
+public class HidlToAidlCallbackConverter extends IBiometricsFaceClientCallback.Stub {
+
+ private final AidlResponseHandler mAidlResponseHandler;
+
+ public HidlToAidlCallbackConverter(AidlResponseHandler aidlResponseHandler) {
+ mAidlResponseHandler = aidlResponseHandler;
+ }
+
+ @Override
+ public void onEnrollResult(
+ long deviceId, int faceId, int userId, int remaining) {
+ mAidlResponseHandler.onEnrollmentProgress(faceId, remaining);
+ }
+
+ @Override
+ public void onAuthenticated(long deviceId, int faceId, int userId,
+ ArrayList<Byte> token) {
+ final boolean authenticated = faceId != 0;
+ byte[] hardwareAuthToken = new byte[token.size()];
+
+ for (int i = 0; i < token.size(); i++) {
+ hardwareAuthToken[i] = token.get(i);
+ }
+
+ if (authenticated) {
+ mAidlResponseHandler.onAuthenticationSucceeded(faceId,
+ HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken));
+ } else {
+ mAidlResponseHandler.onAuthenticationFailed();
+ }
+ }
+
+ @Override
+ public void onAcquired(long deviceId, int userId, int acquiredInfo,
+ int vendorCode) {
+ mAidlResponseHandler.onAcquired(acquiredInfo, vendorCode);
+ }
+
+ @Override
+ public void onError(long deviceId, int userId, int error, int vendorCode) {
+ mAidlResponseHandler.onError(error, vendorCode);
+ }
+
+ @Override
+ public void onRemoved(long deviceId, ArrayList<Integer> removed, int userId) {
+ int[] enrollmentIds = new int[removed.size()];
+ for (int i = 0; i < removed.size(); i++) {
+ enrollmentIds[i] = removed.get(i);
+ }
+ mAidlResponseHandler.onEnrollmentsRemoved(enrollmentIds);
+ }
+
+ @Override
+ public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) {
+ int[] enrollmentIds = new int[faceIds.size()];
+ for (int i = 0; i < faceIds.size(); i++) {
+ enrollmentIds[i] = faceIds.get(i);
+ }
+ mAidlResponseHandler.onEnrollmentsEnumerated(enrollmentIds);
+ }
+
+ @Override
+ public void onLockoutChanged(long duration) {
+ mAidlResponseHandler.onLockoutChanged(duration);
+ }
+
+ void onChallengeGenerated(long challenge) {
+ mAidlResponseHandler.onChallengeGenerated(challenge);
+ }
+
+ void onChallengeRevoked(long challenge) {
+ mAidlResponseHandler.onChallengeRevoked(challenge);
+ }
+
+ void onFeatureGet(byte[] features) {
+ mAidlResponseHandler.onFeaturesRetrieved(features);
+ }
+
+ void onFeatureSet(byte feature) {
+ mAidlResponseHandler.onFeatureSet(feature);
+ }
+
+ void onAuthenticatorIdRetrieved(long authenticatorId) {
+ mAidlResponseHandler.onAuthenticatorIdRetrieved(authenticatorId);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
new file mode 100644
index 0000000..4a01943
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
@@ -0,0 +1,274 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.Error;
+import android.hardware.biometrics.fingerprint.ISessionCallback;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.util.Slog;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.EnumerateConsumer;
+import com.android.server.biometrics.sensors.ErrorConsumer;
+import com.android.server.biometrics.sensors.LockoutCache;
+import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+
+/**
+ * Response handler for the {@link ISessionCallback} HAL AIDL interface.
+ */
+public class AidlResponseHandler extends ISessionCallback.Stub {
+
+ /**
+ * Interface to send results to the AidlResponseHandler's owner.
+ */
+ public interface HardwareUnavailableCallback {
+ /**
+ * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+ */
+ void onHardwareUnavailable();
+ }
+
+ private static final String TAG = "AidlResponseHandler";
+
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final BiometricScheduler mScheduler;
+ private final int mSensorId;
+ private final int mUserId;
+ @NonNull
+ private final LockoutCache mLockoutCache;
+ @NonNull
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
+ @NonNull
+ private final AuthSessionCoordinator mAuthSessionCoordinator;
+ @NonNull
+ private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+
+ public AidlResponseHandler(@NonNull Context context,
+ @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+ @NonNull LockoutCache lockoutTracker,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull AuthSessionCoordinator authSessionCoordinator,
+ @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+ mContext = context;
+ mScheduler = scheduler;
+ mSensorId = sensorId;
+ mUserId = userId;
+ mLockoutCache = lockoutTracker;
+ mLockoutResetDispatcher = lockoutResetDispatcher;
+ mAuthSessionCoordinator = authSessionCoordinator;
+ mHardwareUnavailableCallback = hardwareUnavailableCallback;
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ return this.HASH;
+ }
+
+ @Override
+ public void onChallengeGenerated(long challenge) {
+ handleResponse(FingerprintGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(
+ mSensorId, mUserId, challenge), null);
+ }
+
+ @Override
+ public void onChallengeRevoked(long challenge) {
+ handleResponse(FingerprintRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(
+ challenge), null);
+ }
+
+ /**
+ * Handles acquired messages sent by the HAL (specifically for HIDL HAL).
+ */
+ public void onAcquired(int acquiredInfo, int vendorCode) {
+ handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode),
+ null);
+ }
+
+ @Override
+ public void onAcquired(byte info, int vendorCode) {
+ handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(
+ AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode), null);
+ }
+
+ /**
+ * Handle error messages from the HAL.
+ */
+ public void onError(int error, int vendorCode) {
+ handleResponse(ErrorConsumer.class, (c) -> {
+ c.onError(error, vendorCode);
+ if (error == Error.HW_UNAVAILABLE) {
+ mHardwareUnavailableCallback.onHardwareUnavailable();
+ }
+ }, null);
+ }
+
+ @Override
+ public void onError(byte error, int vendorCode) {
+ onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
+ }
+
+ @Override
+ public void onEnrollmentProgress(int enrollmentId, int remaining) {
+ BaseClientMonitor client = mScheduler.getCurrentClient();
+ final int currentUserId;
+ if (client == null) {
+ return;
+ } else {
+ currentUserId = client.getTargetUserId();
+ }
+ final CharSequence name = FingerprintUtils.getInstance(mSensorId)
+ .getUniqueName(mContext, currentUserId);
+ final Fingerprint fingerprint = new Fingerprint(name, currentUserId,
+ enrollmentId, mSensorId);
+ handleResponse(FingerprintEnrollClient.class, (c) -> c.onEnrollResult(fingerprint,
+ remaining), null);
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
+ final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
+ final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
+ final ArrayList<Byte> byteList = new ArrayList<>();
+ for (byte b : byteArray) {
+ byteList.add(b);
+ }
+ handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(fp,
+ true /* authenticated */, byteList), (c) -> onInteractionDetected());
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId);
+ handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(fp,
+ false /* authenticated */, null /* hardwareAuthToken */),
+ (c) -> onInteractionDetected());
+ }
+
+ @Override
+ public void onLockoutTimed(long durationMillis) {
+ handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis),
+ null);
+ }
+
+ @Override
+ public void onLockoutPermanent() {
+ handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+ }
+
+ @Override
+ public void onLockoutCleared() {
+ handleResponse(FingerprintResetLockoutClient.class,
+ FingerprintResetLockoutClient::onLockoutCleared,
+ (c) -> FingerprintResetLockoutClient.resetLocalLockoutStateToNone(
+ mSensorId, mUserId, mLockoutCache, mLockoutResetDispatcher,
+ mAuthSessionCoordinator, Utils.getCurrentStrength(mSensorId),
+ -1 /* requestId */));
+ }
+
+ @Override
+ public void onInteractionDetected() {
+ handleResponse(FingerprintDetectClient.class,
+ FingerprintDetectClient::onInteractionDetected, null);
+ }
+
+ @Override
+ public void onEnrollmentsEnumerated(int[] enrollmentIds) {
+ if (enrollmentIds.length > 0) {
+ for (int i = 0; i < enrollmentIds.length; i++) {
+ final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
+ int finalI = i;
+ handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp,
+ enrollmentIds.length - finalI - 1), null);
+ }
+ } else {
+ handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(null,
+ 0), null);
+ }
+ }
+
+ @Override
+ public void onEnrollmentsRemoved(int[] enrollmentIds) {
+ if (enrollmentIds.length > 0) {
+ for (int i = 0; i < enrollmentIds.length; i++) {
+ final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
+ int finalI = i;
+ handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp,
+ enrollmentIds.length - finalI - 1), null);
+ }
+ } else {
+ handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null, 0),
+ null);
+ }
+ }
+
+ @Override
+ public void onAuthenticatorIdRetrieved(long authenticatorId) {
+ handleResponse(FingerprintGetAuthenticatorIdClient.class,
+ (c) -> c.onAuthenticatorIdRetrieved(authenticatorId), null);
+ }
+
+ @Override
+ public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
+ handleResponse(FingerprintInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
+ newAuthenticatorId), null);
+ }
+
+ private <T> void handleResponse(@NonNull Class<T> className,
+ @NonNull Consumer<T> action,
+ @Nullable Consumer<BaseClientMonitor> alternateAction) {
+ mScheduler.getHandler().post(() -> {
+ final BaseClientMonitor client = mScheduler.getCurrentClient();
+ if (className.isInstance(client)) {
+ action.accept((T) client);
+ } else {
+ Slog.e(TAG, "Client monitor is not an instance of " + className.getName());
+ if (alternateAction != null) {
+ alternateAction.accept(client);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onSessionClosed() {
+ mScheduler.getHandler().post(mScheduler::onUserStopped);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
index 55861bb..299a310 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
@@ -16,10 +16,13 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
-import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback;
-
import android.annotation.NonNull;
import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+
+import com.android.server.biometrics.sensors.fingerprint.hidl.AidlToHidlAdapter;
+
+import java.util.function.Supplier;
/**
* A holder for an AIDL {@link ISession} with additional metadata about the current user
@@ -30,14 +33,22 @@
private final int mHalInterfaceVersion;
@NonNull private final ISession mSession;
private final int mUserId;
- @NonNull private final HalSessionCallback mHalSessionCallback;
+ @NonNull private final AidlResponseHandler mAidlResponseHandler;
public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId,
- HalSessionCallback halSessionCallback) {
+ AidlResponseHandler aidlResponseHandler) {
mHalInterfaceVersion = halInterfaceVersion;
mSession = session;
mUserId = userId;
- mHalSessionCallback = halSessionCallback;
+ mAidlResponseHandler = aidlResponseHandler;
+ }
+
+ public AidlSession(@NonNull Supplier<IBiometricsFingerprint> session,
+ int userId, AidlResponseHandler aidlResponseHandler) {
+ mSession = new AidlToHidlAdapter(session, userId, aidlResponseHandler);
+ mHalInterfaceVersion = 0;
+ mUserId = userId;
+ mAidlResponseHandler = aidlResponseHandler;
}
/** The underlying {@link ISession}. */
@@ -51,8 +62,8 @@
}
/** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
- HalSessionCallback getHalSessionCallback() {
- return mHalSessionCallback;
+ AidlResponseHandler getHalSessionCallback() {
+ return mAidlResponseHandler;
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 54d1faa..337c3c2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -21,6 +21,7 @@
import android.app.TaskStackListener;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
import android.hardware.biometrics.BiometricManager.Authenticators;
@@ -51,8 +52,8 @@
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.SensorOverlays;
import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
@@ -66,7 +67,7 @@
* Fingerprint-specific authentication client supporting the {@link
* android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
*/
-class FingerprintAuthenticationClient
+public class FingerprintAuthenticationClient
extends AuthenticationClient<AidlSession, FingerprintAuthenticateOptions>
implements Udfps, LockoutConsumer, PowerPressHandler {
private static final String TAG = "FingerprintAuthenticationClient";
@@ -93,7 +94,7 @@
private Runnable mAuthSuccessRunnable;
private final Clock mClock;
- FingerprintAuthenticationClient(
+ public FingerprintAuthenticationClient(
@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token,
@@ -108,14 +109,14 @@
@NonNull BiometricContext biometricContext,
boolean isStrongBiometric,
@Nullable TaskStackListener taskStackListener,
- @NonNull LockoutCache lockoutCache,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull Handler handler,
@Authenticators.Types int biometricStrength,
- @NonNull Clock clock) {
+ @NonNull Clock clock,
+ @Nullable LockoutTracker lockoutTracker) {
super(
context,
lazyDaemon,
@@ -130,7 +131,7 @@
biometricContext,
isStrongBiometric,
taskStackListener,
- null /* lockoutCache */,
+ lockoutTracker,
allowBackgroundAuthentication,
false /* shouldVibrate */,
biometricStrength);
@@ -211,6 +212,7 @@
boolean authenticated,
ArrayList<Byte> token) {
super.onAuthenticated(identifier, authenticated, token);
+ handleLockout(authenticated);
if (authenticated) {
mState = STATE_STOPPED;
mSensorOverlays.hide(getSensorId());
@@ -219,6 +221,32 @@
}
}
+ private void handleLockout(boolean authenticated) {
+ if (getLockoutTracker() == null) {
+ Slog.d(TAG, "Lockout is implemented by the HAL");
+ return;
+ }
+ if (authenticated) {
+ getLockoutTracker().resetFailedAttemptsForUser(true /* clearAttemptCounter */,
+ getTargetUserId());
+ } else {
+ @LockoutTracker.LockoutMode final int lockoutMode =
+ getLockoutTracker().getLockoutModeForUser(getTargetUserId());
+ if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
+ Slog.w(TAG, "Fingerprint locked out, lockoutMode(" + lockoutMode + ")");
+ final int errorCode = lockoutMode == LockoutTracker.LOCKOUT_TIMED
+ ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
+ : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
+ // Send the error, but do not invoke the FinishCallback yet. Since lockout is not
+ // controlled by the HAL, the framework must stop the sensor before finishing the
+ // client.
+ mSensorOverlays.hide(getSensorId());
+ onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
+ cancel();
+ }
+ }
+ }
+
@Override
public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) {
// For UDFPS, notify SysUI with acquiredInfo, so that the illumination can be turned off
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 4502e5d..e2413ee 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -43,7 +43,8 @@
* Performs fingerprint detection without exposing any matching information (e.g. accept/reject
* have the same haptic, lockout counter is not increased).
*/
-class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements DetectionConsumer {
+public class FingerprintDetectClient extends AcquisitionClient<AidlSession>
+ implements DetectionConsumer {
private static final String TAG = "FingerprintDetectClient";
@@ -52,7 +53,8 @@
@NonNull private final SensorOverlays mSensorOverlays;
@Nullable private ICancellationSignal mCancellationSignal;
- FingerprintDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+ public FingerprintDetectClient(@NonNull Context context,
+ @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener,
@NonNull FingerprintAuthenticateOptions options,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 46ff6b4..06550d8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -49,14 +49,13 @@
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.EnrollClient;
import com.android.server.biometrics.sensors.SensorOverlays;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
import java.util.function.Supplier;
-class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps,
+public class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps,
PowerPressHandler {
private static final String TAG = "FingerprintEnrollClient";
@@ -72,12 +71,16 @@
private static boolean shouldVibrateFor(Context context,
FingerprintSensorPropertiesInternal sensorProps) {
- final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
- final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled();
- return !sensorProps.isAnyUdfpsType() || isAccessbilityEnabled;
+ if (sensorProps != null) {
+ final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
+ final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled();
+ return !sensorProps.isAnyUdfpsType() || isAccessbilityEnabled;
+ } else {
+ return true;
+ }
}
- FingerprintEnrollClient(@NonNull Context context,
+ public FingerprintEnrollClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner,
@@ -89,8 +92,8 @@
int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
// UDFPS haptics occur when an image is acquired (instead of when the result is known)
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
- 0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps), logger,
- biometricContext);
+ 0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps),
+ logger, biometricContext);
setRequestId(requestId);
mSensorProps = sensorProps;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
@@ -136,7 +139,7 @@
acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD;
// For UDFPS, notify SysUI that the illumination can be turned off.
// See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE
- if (mSensorProps.isAnyUdfpsType()) {
+ if (mSensorProps != null && mSensorProps.isAnyUdfpsType()) {
if (acquiredGood && mShouldVibrate) {
vibrateSuccess();
}
@@ -162,8 +165,7 @@
@Override
protected boolean hasReachedEnrollmentLimit() {
- return FingerprintUtils.getInstance(getSensorId())
- .getBiometricsForUser(getContext(), getTargetUserId()).size()
+ return mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).size()
>= mMaxTemplatesPerUser;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
index ddae8be..ce693ff 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -33,10 +33,10 @@
/**
* Fingerprint-specific generateChallenge client for the {@link IFingerprint} AIDL HAL interface.
*/
-class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {
+public class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {
private static final String TAG = "FingerprintGenerateChallengeClient";
- FingerprintGenerateChallengeClient(@NonNull Context context,
+ public FingerprintGenerateChallengeClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index ff9127f..5edc2ca 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -39,9 +39,10 @@
* Fingerprint-specific internal cleanup client supporting the
* {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
*/
-class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint, AidlSession> {
+public class FingerprintInternalCleanupClient
+ extends InternalCleanupClient<Fingerprint, AidlSession> {
- FingerprintInternalCleanupClient(@NonNull Context context,
+ public FingerprintInternalCleanupClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
int userId, @NonNull String owner, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index e42b664..9985b06 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -428,8 +428,8 @@
final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
mFingerprintSensors.get(sensorId).getLazySession(), token, id,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
- opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
- createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ opPackageName, FingerprintUtils.getInstance(sensorId),
+ sensorId, createLogger(BiometricsProtoEnums.ACTION_ENROLL,
BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext,
mFingerprintSensors.get(sensorId).getSensorProperties(),
@@ -496,12 +496,13 @@
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
- mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(),
+ mTaskStackListener,
mUdfpsOverlayController, mSidefpsController,
allowBackgroundAuthentication,
mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
Utils.getCurrentStrength(sensorId),
- SystemClock.elapsedRealtimeClock());
+ SystemClock.elapsedRealtimeClock(),
+ null /* lockoutTracker */);
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
index d559bb1..4f08f6f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
@@ -37,12 +37,12 @@
* Fingerprint-specific removal client supporting the
* {@link android.hardware.biometrics.fingerprint.IFingerprint} interface.
*/
-class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> {
+public class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> {
private static final String TAG = "FingerprintRemovalClient";
private final int[] mBiometricIds;
- FingerprintRemovalClient(@NonNull Context context,
+ public FingerprintRemovalClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
@Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId,
@NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index 7a62034..ec225a6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -32,7 +32,6 @@
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.HalClientMonitor;
-import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -43,19 +42,20 @@
* Updates the framework's lockout cache and notifies clients such as Keyguard when lockout is
* cleared.
*/
-class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implements ErrorConsumer {
+public class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession>
+ implements ErrorConsumer {
private static final String TAG = "FingerprintResetLockoutClient";
private final HardwareAuthToken mHardwareAuthToken;
- private final LockoutCache mLockoutCache;
+ private final LockoutTracker mLockoutCache;
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final int mBiometricStrength;
- FingerprintResetLockoutClient(@NonNull Context context,
+ public FingerprintResetLockoutClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
- @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
+ @NonNull byte[] hardwareAuthToken, @NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@Authenticators.Types int biometricStrength) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
@@ -107,10 +107,11 @@
* be used instead.
*/
static void resetLocalLockoutStateToNone(int sensorId, int userId,
- @NonNull LockoutCache lockoutTracker,
+ @NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
@Authenticators.Types int biometricStrength, long requestId) {
+ lockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId);
lockoutTracker.setLockoutModeForUser(userId, LockoutTracker.LOCKOUT_NONE);
lockoutResetDispatcher.notifyLockoutResetCallbacks(sensorId);
authSessionCoordinator.resetLockoutFor(userId, biometricStrength, requestId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
index afa62e2..23d8e1e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
@@ -32,13 +32,13 @@
/**
* Fingerprint-specific revokeChallenge client for the {@link IFingerprint} AIDL HAL interface.
*/
-class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> {
+public class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> {
- private static final String TAG = "FingerpirntRevokeChallengeClient";
+ private static final String TAG = "FingerprintRevokeChallengeClient";
private final long mChallenge;
- FingerprintRevokeChallengeClient(@NonNull Context context,
+ public FingerprintRevokeChallengeClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
int userId, @NonNull String owner, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@@ -57,7 +57,7 @@
}
}
- void onChallengeRevoked(int sensorId, int userId, long challenge) {
+ void onChallengeRevoked(long challenge) {
final boolean success = challenge == mChallenge;
mCallback.onClientFinished(FingerprintRevokeChallengeClient.this, success);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 56b85ce..893cb8f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -23,13 +23,9 @@
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.fingerprint.Error;
import android.hardware.biometrics.fingerprint.ISession;
-import android.hardware.biometrics.fingerprint.ISessionCallback;
-import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.keymaster.HardwareAuthToken;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -39,34 +35,25 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AcquisitionClient;
-import com.android.server.biometrics.sensors.AuthSessionCoordinator;
-import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.LockoutCache;
-import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-import com.android.server.biometrics.sensors.RemovalConsumer;
import com.android.server.biometrics.sensors.StartUserClient;
import com.android.server.biometrics.sensors.StopUserClient;
import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
@@ -93,348 +80,6 @@
@Nullable AidlSession mCurrentSession;
@NonNull private final Supplier<AidlSession> mLazySession;
- @VisibleForTesting
- public static class HalSessionCallback extends ISessionCallback.Stub {
-
- /**
- * Interface to sends results to the HalSessionCallback's owner.
- */
- public interface Callback {
- /**
- * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
- */
- void onHardwareUnavailable();
- }
-
- @NonNull
- private final Context mContext;
- @NonNull
- private final Handler mHandler;
- @NonNull
- private final String mTag;
- @NonNull
- private final UserAwareBiometricScheduler mScheduler;
- private final int mSensorId;
- private final int mUserId;
- @NonNull
- private final LockoutCache mLockoutCache;
- @NonNull
- private final LockoutResetDispatcher mLockoutResetDispatcher;
- @NonNull
- private AuthSessionCoordinator mAuthSessionCoordinator;
- @NonNull
- private final Callback mCallback;
-
- HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
- @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
- @NonNull LockoutCache lockoutTracker,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull AuthSessionCoordinator authSessionCoordinator,
- @NonNull Callback callback) {
- mContext = context;
- mHandler = handler;
- mTag = tag;
- mScheduler = scheduler;
- mSensorId = sensorId;
- mUserId = userId;
- mLockoutCache = lockoutTracker;
- mLockoutResetDispatcher = lockoutResetDispatcher;
- mAuthSessionCoordinator = authSessionCoordinator;
- mCallback = callback;
- }
-
- @Override
- public int getInterfaceVersion() {
- return this.VERSION;
- }
-
- @Override
- public String getInterfaceHash() {
- return this.HASH;
- }
-
- @Override
- public void onChallengeGenerated(long challenge) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintGenerateChallengeClient)) {
- Slog.e(mTag, "onChallengeGenerated for wrong client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FingerprintGenerateChallengeClient generateChallengeClient =
- (FingerprintGenerateChallengeClient) client;
- generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge);
- });
- }
-
- @Override
- public void onChallengeRevoked(long challenge) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintRevokeChallengeClient)) {
- Slog.e(mTag, "onChallengeRevoked for wrong client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FingerprintRevokeChallengeClient revokeChallengeClient =
- (FingerprintRevokeChallengeClient) client;
- revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge);
- });
- }
-
- @Override
- public void onAcquired(byte info, int vendorCode) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AcquisitionClient)) {
- Slog.e(mTag, "onAcquired for non-acquisition client: "
- + Utils.getClientName(client));
- return;
- }
-
- final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
- acquisitionClient.onAcquired(AidlConversionUtils.toFrameworkAcquiredInfo(info),
- vendorCode);
- });
- }
-
- @Override
- public void onError(byte error, int vendorCode) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- Slog.d(mTag, "onError"
- + ", client: " + Utils.getClientName(client)
- + ", error: " + error
- + ", vendorCode: " + vendorCode);
- if (!(client instanceof ErrorConsumer)) {
- Slog.e(mTag, "onError for non-error consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final ErrorConsumer errorConsumer = (ErrorConsumer) client;
- errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
-
- if (error == Error.HW_UNAVAILABLE) {
- mCallback.onHardwareUnavailable();
- }
- });
- }
-
- @Override
- public void onEnrollmentProgress(int enrollmentId, int remaining) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintEnrollClient)) {
- Slog.e(mTag, "onEnrollmentProgress for non-enroll client: "
- + Utils.getClientName(client));
- return;
- }
-
- final int currentUserId = client.getTargetUserId();
- final CharSequence name = FingerprintUtils.getInstance(mSensorId)
- .getUniqueName(mContext, currentUserId);
- final Fingerprint fingerprint = new Fingerprint(name, enrollmentId, mSensorId);
-
- final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
- enrollClient.onEnrollResult(fingerprint, remaining);
- });
- }
-
- @Override
- public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
- final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
- final ArrayList<Byte> byteList = new ArrayList<>();
- for (byte b : byteArray) {
- byteList.add(b);
- }
-
- authenticationConsumer.onAuthenticated(fp, true /* authenticated */, byteList);
- });
- }
-
- @Override
- public void onAuthenticationFailed() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId);
- authenticationConsumer
- .onAuthenticated(fp, false /* authenticated */, null /* hat */);
- });
- }
-
- @Override
- public void onLockoutTimed(long durationMillis) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof LockoutConsumer)) {
- Slog.e(mTag, "onLockoutTimed for non-lockout consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
- lockoutConsumer.onLockoutTimed(durationMillis);
- });
- }
-
- @Override
- public void onLockoutPermanent() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof LockoutConsumer)) {
- Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
- lockoutConsumer.onLockoutPermanent();
- });
- }
-
- @Override
- public void onLockoutCleared() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintResetLockoutClient)) {
- Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL");
- // Given that onLockoutCleared() can happen at any time, and is not necessarily
- // coming from a specific client, set this to -1 to indicate it wasn't for a
- // specific request.
- FingerprintResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
- mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- Utils.getCurrentStrength(mSensorId), -1 /* requestId */);
- } else {
- Slog.d(mTag, "onLockoutCleared after resetLockout");
- final FingerprintResetLockoutClient resetLockoutClient =
- (FingerprintResetLockoutClient) client;
- resetLockoutClient.onLockoutCleared();
- }
- });
- }
-
- @Override
- public void onInteractionDetected() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintDetectClient)) {
- Slog.e(mTag, "onInteractionDetected for non-detect client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FingerprintDetectClient fingerprintDetectClient =
- (FingerprintDetectClient) client;
- fingerprintDetectClient.onInteractionDetected();
- });
- }
-
- @Override
- public void onEnrollmentsEnumerated(int[] enrollmentIds) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof EnumerateConsumer)) {
- Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final EnumerateConsumer enumerateConsumer =
- (EnumerateConsumer) client;
- if (enrollmentIds.length > 0) {
- for (int i = 0; i < enrollmentIds.length; i++) {
- final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
- enumerateConsumer.onEnumerationResult(fp, enrollmentIds.length - i - 1);
- }
- } else {
- enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
- }
- });
- }
-
- @Override
- public void onEnrollmentsRemoved(int[] enrollmentIds) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof RemovalConsumer)) {
- Slog.e(mTag, "onRemoved for non-removal consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final RemovalConsumer removalConsumer = (RemovalConsumer) client;
- if (enrollmentIds.length > 0) {
- for (int i = 0; i < enrollmentIds.length; i++) {
- final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
- removalConsumer.onRemoved(fp, enrollmentIds.length - i - 1);
- }
- } else {
- removalConsumer.onRemoved(null, 0);
- }
- });
- }
-
- @Override
- public void onAuthenticatorIdRetrieved(long authenticatorId) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintGetAuthenticatorIdClient)) {
- Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final FingerprintGetAuthenticatorIdClient getAuthenticatorIdClient =
- (FingerprintGetAuthenticatorIdClient) client;
- getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId);
- });
- }
-
- @Override
- public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintInvalidationClient)) {
- Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final FingerprintInvalidationClient invalidationClient =
- (FingerprintInvalidationClient) client;
- invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId);
- });
- }
-
- @Override
- public void onSessionClosed() {
- mHandler.post(mScheduler::onUserStopped);
- }
- }
-
Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@@ -466,9 +111,9 @@
public StartUserClient<?, ?> getStartUserClient(int newUserId) {
final int sensorId = mSensorProperties.sensorId;
- final HalSessionCallback resultController = new HalSessionCallback(mContext,
- mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
- lockoutResetDispatcher,
+ final AidlResponseHandler resultController = new AidlResponseHandler(
+ mContext, mScheduler, sensorId, newUserId,
+ mLockoutCache, lockoutResetDispatcher,
biometricContext.getAuthSessionCoordinator(), () -> {
Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
mCurrentSession = null;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
new file mode 100644
index 0000000..b48d232
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
@@ -0,0 +1,231 @@
+/*
+ * 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.biometrics.sensors.fingerprint.hidl;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+
+import java.util.function.Supplier;
+
+/**
+ * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ */
+public class AidlToHidlAdapter implements ISession {
+ private final String TAG = "AidlToHidlAdapter";
+ @VisibleForTesting
+ static final int ENROLL_TIMEOUT_SEC = 60;
+ @NonNull
+ private final Supplier<IBiometricsFingerprint> mSession;
+ private final int mUserId;
+ private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
+
+ public AidlToHidlAdapter(Supplier<IBiometricsFingerprint> session, int userId,
+ AidlResponseHandler aidlResponseHandler) {
+ mSession = session;
+ mUserId = userId;
+ setCallback(aidlResponseHandler);
+ }
+
+ private void setCallback(AidlResponseHandler aidlResponseHandler) {
+ mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
+ try {
+ mSession.get().setNotify(mHidlToAidlCallbackConverter);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to set callback");
+ }
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ @Override
+ public void generateChallenge() throws RemoteException {
+ long challenge = mSession.get().preEnroll();
+ mHidlToAidlCallbackConverter.onChallengeGenerated(challenge);
+ }
+
+ @Override
+ public void revokeChallenge(long challenge) throws RemoteException {
+ mSession.get().postEnroll();
+ mHidlToAidlCallbackConverter.onChallengeRevoked(0L);
+ }
+
+ @Override
+ public ICancellationSignal enroll(HardwareAuthToken hat) throws RemoteException {
+ mSession.get().enroll(HardwareAuthTokenUtils.toByteArray(hat), mUserId,
+ ENROLL_TIMEOUT_SEC);
+ return new Cancellation();
+ }
+
+ @Override
+ public ICancellationSignal authenticate(long operationId) throws RemoteException {
+ mSession.get().authenticate(operationId, mUserId);
+ return new Cancellation();
+ }
+
+ @Override
+ public ICancellationSignal detectInteraction() throws RemoteException {
+ mSession.get().authenticate(0, mUserId);
+ return new Cancellation();
+ }
+
+ @Override
+ public void enumerateEnrollments() throws RemoteException {
+ mSession.get().enumerate();
+ }
+
+ @Override
+ public void removeEnrollments(int[] enrollmentIds) throws RemoteException {
+ if (enrollmentIds.length > 1) {
+ mSession.get().remove(mUserId, 0);
+ } else {
+ mSession.get().remove(mUserId, enrollmentIds[0]);
+ }
+ }
+
+ @Override
+ public void onPointerDown(int pointerId, int x, int y, float minor, float major)
+ throws RemoteException {
+ UdfpsHelper.onFingerDown(mSession.get(), x, y, minor, major);
+ }
+
+ @Override
+ public void onPointerUp(int pointerId) throws RemoteException {
+ UdfpsHelper.onFingerUp(mSession.get());
+ }
+
+ @Override
+ public void getAuthenticatorId() throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public void invalidateAuthenticatorId() throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public void resetLockout(HardwareAuthToken hat) throws RemoteException {
+ mHidlToAidlCallbackConverter.onResetLockout();
+ }
+
+ @Override
+ public void close() throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public void onUiReady() throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
+ throws RemoteException {
+ //Unsupported in HIDL
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal enrollWithContext(HardwareAuthToken hat, OperationContext context)
+ throws RemoteException {
+ //Unsupported in HIDL
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal detectInteractionWithContext(OperationContext context)
+ throws RemoteException {
+ //Unsupported in HIDL
+ return null;
+ }
+
+ @Override
+ public void onPointerDownWithContext(PointerContext context) throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public void onPointerUpWithContext(PointerContext context) throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public void onContextChanged(OperationContext context) throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public void onPointerCancelWithContext(PointerContext context) throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public void setIgnoreDisplayTouches(boolean shouldIgnore) throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ //Unsupported in HIDL
+ return 0;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ //Unsupported in HIDL
+ return null;
+ }
+
+ private class Cancellation extends ICancellationSignal.Stub {
+
+ Cancellation() {}
+ @Override
+ public void cancel() throws RemoteException {
+ try {
+ mSession.get().cancel();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting cancel", e);
+ }
+ }
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ return null;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index a655f360..8bfa560 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -54,6 +54,7 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -64,6 +65,7 @@
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
@@ -74,6 +76,7 @@
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.ErrorConsumer;
+import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
@@ -82,6 +85,8 @@
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
import org.json.JSONArray;
import org.json.JSONException;
@@ -132,6 +137,7 @@
private final boolean mIsUdfps;
private final int mSensorId;
private final boolean mIsPowerbuttonFps;
+ private AidlSession mSession;
private final class BiometricTaskStackListener extends TaskStackListener {
@Override
@@ -413,6 +419,20 @@
});
}
+ synchronized AidlSession getSession() {
+ if (mDaemon != null && mSession != null) {
+ return mSession;
+ } else {
+ return mSession = new AidlSession(this::getDaemon,
+ mCurrentUserId, new AidlResponseHandler(mContext,
+ mScheduler, mSensorId, mCurrentUserId, new LockoutCache(),
+ mLockoutResetDispatcher, new AuthSessionCoordinator(), () -> {
+ mDaemon = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }));
+ }
+ }
+
@VisibleForTesting
synchronized IBiometricsFingerprint getDaemon() {
if (mTestHalEnabled) {
@@ -518,6 +538,10 @@
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
if (success) {
+ if (mCurrentUserId != targetUserId) {
+ // Create new session with updated user ID
+ mSession = null;
+ }
mCurrentUserId = targetUserId;
} else {
Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
@@ -554,47 +578,116 @@
// Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler
// thread.
mHandler.post(() -> {
- final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
- userId, mContext.getOpPackageName(), sensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext, mLockoutTracker);
- mScheduler.scheduleClientMonitor(client);
+ if (Flags.deHidl()) {
+ scheduleResetLockoutAidl(sensorId, userId, hardwareAuthToken);
+ } else {
+ scheduleResetLockoutHidl(sensorId, userId);
+ }
});
}
+ private void scheduleResetLockoutAidl(int sensorId, int userId,
+ @Nullable byte[] hardwareAuthToken) {
+ final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient client =
+ new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient(
+ mContext, this::getSession, userId, mContext.getOpPackageName(),
+ sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext, hardwareAuthToken, mLockoutTracker,
+ mLockoutResetDispatcher,
+ Utils.getCurrentStrength(sensorId));
+ mScheduler.scheduleClientMonitor(client);
+ }
+
+ private void scheduleResetLockoutHidl(int sensorId, int userId) {
+ final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
+ userId, mContext.getOpPackageName(), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext, mLockoutTracker);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
@Override
public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
- final FingerprintGenerateChallengeClient client =
- new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
- new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext);
- mScheduler.scheduleClientMonitor(client);
+ if (Flags.deHidl()) {
+ scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName);
+ } else {
+ scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName);
+ }
});
}
+ private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token,
+ @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
+ final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient client =
+ new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient(
+ mContext, this::getSession, token,
+ new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
+ private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token,
+ @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
+ final FingerprintGenerateChallengeClient client =
+ new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
+ new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
@Override
public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull String opPackageName, long challenge) {
mHandler.post(() -> {
- final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
- mContext, mLazyDaemon, token, userId, opPackageName,
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext);
- mScheduler.scheduleClientMonitor(client);
+ if (Flags.deHidl()) {
+ scheduleRevokeChallengeAidl(userId, token, opPackageName);
+ } else {
+ scheduleRevokeChallengeHidl(userId, token, opPackageName);
+ }
});
}
+ private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token,
+ @NonNull String opPackageName) {
+ final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient client =
+ new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient(
+ mContext, this::getSession,
+ token, userId, opPackageName,
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext, 0L);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
+ private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token,
+ @NonNull String opPackageName) {
+ final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
+ mContext, mLazyDaemon, token, userId, opPackageName,
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
@Override
public long scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId,
@@ -604,40 +697,98 @@
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
- final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
- mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
- userId, hardwareAuthToken, opPackageName,
- FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- mBiometricStateCallback.onClientStarted(clientMonitor);
- }
-
- @Override
- public void onBiometricAction(int action) {
- mBiometricStateCallback.onBiometricAction(action);
- }
-
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- mBiometricStateCallback.onClientFinished(clientMonitor, success);
- if (success) {
- // Update authenticatorIds
- scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
- true /* force */);
- }
- }
- });
+ if (Flags.deHidl()) {
+ scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver,
+ opPackageName, enrollReason, id);
+ } else {
+ scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver,
+ opPackageName, enrollReason, id);
+ }
});
return id;
}
+ private void scheduleEnrollHidl(@NonNull IBinder token,
+ @NonNull byte[] hardwareAuthToken, int userId,
+ @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
+ @FingerprintManager.EnrollReason int enrollReason, long id) {
+ final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
+ mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
+ userId, hardwareAuthToken, opPackageName,
+ FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+ mBiometricStateCallback.onClientStarted(clientMonitor);
+ }
+
+ @Override
+ public void onBiometricAction(int action) {
+ mBiometricStateCallback.onBiometricAction(action);
+ }
+
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ mBiometricStateCallback.onClientFinished(clientMonitor, success);
+ if (success) {
+ // Update authenticatorIds
+ scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
+ true /* force */);
+ }
+ }
+ });
+ }
+
+ private void scheduleEnrollAidl(@NonNull IBinder token,
+ @NonNull byte[] hardwareAuthToken, int userId,
+ @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
+ @FingerprintManager.EnrollReason int enrollReason, long id) {
+ final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient
+ client =
+ new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient(
+ mContext,
+ this::getSession, token, id,
+ new ClientMonitorCallbackConverter(receiver),
+ userId, hardwareAuthToken, opPackageName,
+ FingerprintUtils.getLegacyInstance(mSensorId),
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext, null /* sensorProps */,
+ mUdfpsOverlayController, mSidefpsController,
+ mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser),
+ enrollReason);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+ mBiometricStateCallback.onClientStarted(clientMonitor);
+ }
+
+ @Override
+ public void onBiometricAction(int action) {
+ mBiometricStateCallback.onBiometricAction(action);
+ }
+
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ mBiometricStateCallback.onClientFinished(clientMonitor, success);
+ if (success) {
+ // Update authenticatorIds
+ scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
+ true /* force */);
+ }
+ }
+ });
+ }
+
@Override
public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
@@ -653,17 +804,46 @@
scheduleUpdateActiveUserWithoutHandler(options.getUserId());
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
- final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
- mLazyDaemon, token, id, listener, options,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
- mAuthenticationStatsCollector),
- mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+
+ if (Flags.deHidl()) {
+ scheduleFingerDetectAidl(token, listener, options, statsClient, id,
+ isStrongBiometric);
+ } else {
+ scheduleFingerDetectHidl(token, listener, options, statsClient, id,
+ isStrongBiometric);
+ }
});
return id;
}
+ private void scheduleFingerDetectHidl(@NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull FingerprintAuthenticateOptions options,
+ int statsClient, long id, boolean isStrongBiometric) {
+ final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
+ mLazyDaemon, token, id, listener, options,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
+ mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ }
+
+ private void scheduleFingerDetectAidl(@NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull FingerprintAuthenticateOptions options,
+ int statsClient, long id, boolean isStrongBiometric) {
+ final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient
+ client =
+ new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient(
+ mContext,
+ this::getSession, token, id, listener, options,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
+ mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ }
+
@Override
public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
int cookie, @NonNull ClientMonitorCallbackConverter listener,
@@ -674,20 +854,55 @@
scheduleUpdateActiveUserWithoutHandler(options.getUserId());
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
- final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
- mContext, mLazyDaemon, token, requestId, listener, operationId,
- restricted, options, cookie, false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
- mAuthenticationStatsCollector),
- mBiometricContext, isStrongBiometric,
- mTaskStackListener, mLockoutTracker,
- mUdfpsOverlayController, mSidefpsController,
- allowBackgroundAuthentication, mSensorProperties,
- Utils.getCurrentStrength(mSensorId));
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+
+ if (Flags.deHidl()) {
+ scheduleAuthenticateAidl(token, operationId, cookie, listener, options, requestId,
+ restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
+ } else {
+ scheduleAuthenticateHidl(token, operationId, cookie, listener, options, requestId,
+ restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
+ }
});
}
+ private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId,
+ int cookie, @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull FingerprintAuthenticateOptions options,
+ long requestId, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
+ final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient
+ client =
+ new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient(
+ mContext, this::getSession, token, requestId, listener, operationId,
+ restricted, options, cookie, false /* requireConfirmation */,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
+ mBiometricContext, isStrongBiometric,
+ mTaskStackListener,
+ mUdfpsOverlayController, mSidefpsController,
+ allowBackgroundAuthentication, mSensorProperties, mHandler,
+ Utils.getCurrentStrength(mSensorId), null /* clock */, mLockoutTracker);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ }
+
+ private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId,
+ int cookie, @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull FingerprintAuthenticateOptions options,
+ long requestId, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
+ final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
+ mContext, mLazyDaemon, token, requestId, listener, operationId,
+ restricted, options, cookie, false /* requireConfirmation */,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
+ mBiometricContext, isStrongBiometric,
+ mTaskStackListener, mLockoutTracker,
+ mUdfpsOverlayController, mSidefpsController,
+ allowBackgroundAuthentication, mSensorProperties,
+ Utils.getCurrentStrength(mSensorId));
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ }
+
@Override
public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
int cookie, @NonNull ClientMonitorCallbackConverter listener,
@@ -719,17 +934,41 @@
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
- final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
- userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ if (Flags.deHidl()) {
+ scheduleRemoveAidl(token, receiver, fingerId, userId, opPackageName);
+ } else {
+ scheduleRemoveHidl(token, receiver, fingerId, userId, opPackageName);
+ }
});
}
+ private void scheduleRemoveHidl(@NonNull IBinder token,
+ @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
+ @NonNull String opPackageName) {
+ final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
+ mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
+ userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext, mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ }
+
+ private void scheduleRemoveAidl(@NonNull IBinder token,
+ @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
+ @NonNull String opPackageName) {
+ final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient client =
+ new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient(
+ mContext, this::getSession, token,
+ new ClientMonitorCallbackConverter(receiver), new int[]{fingerId}, userId,
+ opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext, mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ }
+
@Override
public void scheduleRemoveAll(int sensorId, @NonNull IBinder token,
@NonNull IFingerprintServiceReceiver receiver, int userId,
@@ -738,15 +977,11 @@
scheduleUpdateActiveUserWithoutHandler(userId);
// For IBiometricsFingerprint@2.1, remove(0) means remove all enrollments
- final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver),
- 0 /* fingerprintId */, userId, opPackageName,
- FingerprintUtils.getLegacyInstance(mSensorId),
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ if (Flags.deHidl()) {
+ scheduleRemoveAidl(token, receiver, 0 /* fingerId */, userId, opPackageName);
+ } else {
+ scheduleRemoveHidl(token, receiver, 0 /* fingerId */, userId, opPackageName);
+ }
});
}
@@ -755,17 +990,41 @@
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
- final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
- mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext,
- FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, callback);
+ if (Flags.deHidl()) {
+ scheduleInternalCleanupAidl(userId, callback);
+ } else {
+ scheduleInternalCleanupHidl(userId, callback);
+ }
});
}
+ private void scheduleInternalCleanupHidl(int userId,
+ @Nullable ClientMonitorCallback callback) {
+ final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
+ mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext,
+ FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, callback);
+ }
+
+ private void scheduleInternalCleanupAidl(int userId,
+ @Nullable ClientMonitorCallback callback) {
+ final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient
+ client =
+ new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient(
+ mContext, this::getSession, userId, mContext.getOpPackageName(),
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext,
+ FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, callback);
+ }
+
@Override
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable ClientMonitorCallback callback) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
new file mode 100644
index 0000000..c3e5cbe
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.hidl;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+
+import java.util.ArrayList;
+
+/**
+ * Convert HIDL-specific callback interface {@link IBiometricsFingerprintClientCallback} to AIDL
+ * response handler.
+ */
+public class HidlToAidlCallbackConverter extends IBiometricsFingerprintClientCallback.Stub {
+
+ final AidlResponseHandler mAidlResponseHandler;
+
+ public HidlToAidlCallbackConverter(@NonNull AidlResponseHandler aidlResponseHandler) {
+ mAidlResponseHandler = aidlResponseHandler;
+ }
+
+ @Override
+ public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
+ mAidlResponseHandler.onEnrollmentProgress(fingerId, remaining);
+ }
+
+ @Override
+ public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) {
+ onAcquired_2_2(deviceId, acquiredInfo, vendorCode);
+ }
+
+ @Override
+ public void onAcquired_2_2(long deviceId, int acquiredInfo, int vendorCode) {
+ mAidlResponseHandler.onAcquired(acquiredInfo, vendorCode);
+ }
+
+ @Override
+ public void onAuthenticated(long deviceId, int fingerId, int groupId,
+ ArrayList<Byte> token) {
+ if (fingerId != 0) {
+ byte[] hardwareAuthToken = new byte[token.size()];
+ for (int i = 0; i < token.size(); i++) {
+ hardwareAuthToken[i] = token.get(i);
+ }
+ mAidlResponseHandler.onAuthenticationSucceeded(fingerId,
+ HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken));
+ } else {
+ mAidlResponseHandler.onAuthenticationFailed();
+ }
+ }
+
+ @Override
+ public void onError(long deviceId, int error, int vendorCode) {
+ mAidlResponseHandler.onError(error, vendorCode);
+ }
+
+ @Override
+ public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
+ mAidlResponseHandler.onEnrollmentsRemoved(new int[]{fingerId});
+ }
+
+ @Override
+ public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
+ mAidlResponseHandler.onEnrollmentsEnumerated(new int[]{fingerId});
+ }
+
+ void onChallengeGenerated(long challenge) {
+ mAidlResponseHandler.onChallengeGenerated(challenge);
+ }
+
+ void onChallengeRevoked(long challenge) {
+ mAidlResponseHandler.onChallengeRevoked(challenge);
+ }
+
+ void onResetLockout() {
+ mAidlResponseHandler.onLockoutCleared();
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
index 36d56c8..0730c67 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
@@ -89,7 +89,8 @@
// Attempt counter should only be cleared when Keyguard goes away or when
// a biometric is successfully authenticated. Lockout should eventually be done below the HAL.
// See AuthenticationClient#shouldFrameworkHandleLockout().
- void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
+ @Override
+ public void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
if (getLockoutModeForUser(userId) != LOCKOUT_NONE) {
Slog.v(TAG, "Reset biometric lockout for user: " + userId
+ ", clearAttemptCounter: " + clearAttemptCounter);
@@ -104,7 +105,8 @@
mLockoutResetCallback.onLockoutReset(userId);
}
- void addFailedAttemptForUser(int userId) {
+ @Override
+ public void addFailedAttemptForUser(int userId) {
mFailedAttempts.put(userId, mFailedAttempts.get(userId, 0) + 1);
mTimedLockoutCleared.put(userId, false);
@@ -114,7 +116,8 @@
}
@Override
- public @LockoutMode int getLockoutModeForUser(int userId) {
+ @LockoutMode
+ public int getLockoutModeForUser(int userId) {
final int failedAttempts = mFailedAttempts.get(userId, 0);
if (failedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
return LOCKOUT_PERMANENT;
@@ -126,6 +129,19 @@
return LOCKOUT_NONE;
}
+ /**
+ * Clears lockout for Fingerprint HIDL HAL
+ */
+ @Override
+ public void setLockoutModeForUser(int userId, int mode) {
+ mFailedAttempts.put(userId, 0);
+ mTimedLockoutCleared.put(userId, true);
+ // If we're asked to reset failed attempts externally (i.e. from Keyguard),
+ // the alarm might still be pending; remove it.
+ cancelLockoutResetForUser(userId);
+ mLockoutResetCallback.onLockoutReset(userId);
+ }
+
private void cancelLockoutResetForUser(int userId) {
mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
}
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 906c66d..76dde54 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -1474,11 +1474,11 @@
.getDrawable(R.drawable.ic_safety_protection);
toastToShow = Toast.makeCustomToastWithIcon(toastContext,
UiThread.get().getLooper(), message,
- Toast.LENGTH_SHORT, safetyProtectionIcon);
+ Toast.LENGTH_LONG, safetyProtectionIcon);
} else {
toastToShow = Toast.makeText(
toastContext, UiThread.get().getLooper(), message,
- Toast.LENGTH_SHORT);
+ Toast.LENGTH_LONG);
}
toastToShow.show();
}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 8736a53..ac7d9c1 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -221,9 +221,8 @@
/** Flags used when connecting to a sync adapter service */
private static final Context.BindServiceFlags SYNC_ADAPTER_CONNECTION_FLAGS =
- Context.BindServiceFlags.of(
- Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS | Context.BIND_AUTO_CREATE
- | Context.BIND_NOT_FOREGROUND | Context.BIND_ALLOW_OOM_MANAGEMENT);
+ Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
+ | Context.BIND_ALLOW_OOM_MANAGEMENT);
/** Singleton instance. */
@GuardedBy("SyncManager.class")
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 472c1f5..1ac3a12 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -135,7 +135,8 @@
leadDisplayAddress,
d.getBrightnessThrottlingMapId(),
d.getRefreshRateZoneId(),
- d.getRefreshRateThermalThrottlingMapId());
+ d.getRefreshRateThermalThrottlingMapId(),
+ d.getPowerThrottlingMapId());
}
layout.postProcessLocked();
}
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index 425a1af..6695801 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -27,6 +27,8 @@
* the DisplayBrightnessModeStrategies when updating the brightness.
*/
public final class DisplayBrightnessState {
+ public static final float CUSTOM_ANIMATION_RATE_NOT_SET = -1f;
+
private final float mBrightness;
private final float mSdrBrightness;
@@ -37,6 +39,8 @@
private final boolean mIsSlowChange;
+ private final float mCustomAnimationRate;
+
private DisplayBrightnessState(Builder builder) {
mBrightness = builder.getBrightness();
mSdrBrightness = builder.getSdrBrightness();
@@ -45,6 +49,7 @@
mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness();
mIsSlowChange = builder.isSlowChange();
mMaxBrightness = builder.getMaxBrightness();
+ mCustomAnimationRate = builder.getCustomAnimationRate();
}
/**
@@ -97,7 +102,12 @@
return mMaxBrightness;
}
-
+ /**
+ * @return custom animation rate
+ */
+ public float getCustomAnimationRate() {
+ return mCustomAnimationRate;
+ }
@Override
public String toString() {
@@ -112,6 +122,7 @@
stringBuilder.append(getShouldUseAutoBrightness());
stringBuilder.append("\n isSlowChange:").append(mIsSlowChange);
stringBuilder.append("\n maxBrightness:").append(mMaxBrightness);
+ stringBuilder.append("\n customAnimationRate:").append(mCustomAnimationRate);
return stringBuilder.toString();
}
@@ -137,13 +148,14 @@
otherState.getDisplayBrightnessStrategyName())
&& mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness()
&& mIsSlowChange == otherState.isSlowChange()
- && mMaxBrightness == otherState.getMaxBrightness();
+ && mMaxBrightness == otherState.getMaxBrightness()
+ && mCustomAnimationRate == otherState.getCustomAnimationRate();
}
@Override
public int hashCode() {
return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
- mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness);
+ mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mCustomAnimationRate);
}
/**
@@ -164,6 +176,7 @@
private boolean mShouldUseAutoBrightness;
private boolean mIsSlowChange;
private float mMaxBrightness;
+ private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET;
/**
* Create a builder starting with the values from the specified {@link
@@ -180,6 +193,7 @@
builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness());
builder.setIsSlowChange(state.isSlowChange());
builder.setMaxBrightness(state.getMaxBrightness());
+ builder.setCustomAnimationRate(state.getCustomAnimationRate());
return builder;
}
@@ -303,6 +317,22 @@
return mMaxBrightness;
}
+
+ /**
+ * See {@link DisplayBrightnessState#getCustomAnimationRate()}.
+ */
+ public Builder setCustomAnimationRate(float animationRate) {
+ this.mCustomAnimationRate = animationRate;
+ return this;
+ }
+
+ /**
+ * See {@link DisplayBrightnessState#getCustomAnimationRate()}.
+ */
+ public float getCustomAnimationRate() {
+ return mCustomAnimationRate;
+ }
+
/**
* This is used to construct an immutable DisplayBrightnessState object from its builder
*/
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 098cb87..9f4f787 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -23,7 +23,7 @@
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
-import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.display.DisplayViewport;
import android.os.IBinder;
import android.util.Slog;
@@ -201,20 +201,6 @@
* @param state The new display state.
* @param brightnessState The new display brightnessState.
* @param sdrBrightnessState The new display brightnessState for SDR layers.
- * @return A runnable containing work to be deferred until after we have
- * exited the critical section, or null if none.
- */
- public Runnable requestDisplayStateLocked(int state, float brightnessState,
- float sdrBrightnessState) {
- return requestDisplayStateLocked(state, brightnessState, sdrBrightnessState, null);
- }
-
- /**
- * Sets the display state, if supported.
- *
- * @param state The new display state.
- * @param brightnessState The new display brightnessState.
- * @param sdrBrightnessState The new display brightnessState for SDR layers.
* @param displayOffloadSession {@link DisplayOffloadSession} associated with current device.
* @return A runnable containing work to be deferred until after we have exited the critical
* section, or null if none.
@@ -223,7 +209,7 @@
int state,
float brightnessState,
float sdrBrightnessState,
- @Nullable DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) {
+ @Nullable DisplayOffloadSession displayOffloadSession) {
return null;
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 0689478..a788968 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -494,6 +494,7 @@
// If we would like to keep a particular eye on a package, we can set the package name.
private final boolean mExtraDisplayEventLogging;
+ private final String mExtraDisplayLoggingPackageName;
private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() {
@Override
@@ -584,8 +585,9 @@
mOverlayProperties = SurfaceControl.getOverlaySupport();
mSystemReady = false;
mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
- final String name = DisplayProperties.debug_vri_package().orElse(null);
- mExtraDisplayEventLogging = !TextUtils.isEmpty(name);
+ mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null);
+ // TODO: b/306170135 - return TextUtils package name check instead
+ mExtraDisplayEventLogging = true;
}
public void setupSchedulerPolicies() {
@@ -757,7 +759,8 @@
mContext.registerReceiver(mIdleModeReceiver, filter);
- mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext);
+ mSmallAreaDetectionController = (mFlags.isSmallAreaDetectionEnabled())
+ ? SmallAreaDetectionController.create(mContext) : null;
}
@VisibleForTesting
@@ -2931,15 +2934,27 @@
// Send a display event if the display is enabled
private void sendDisplayEventIfEnabledLocked(@NonNull LogicalDisplay display,
@DisplayEvent int event) {
+ final boolean displayIsEnabled = display.isEnabledLocked();
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER,
+ "sendDisplayEventLocked#event=" + event + ",displayEnabled="
+ + displayIsEnabled);
+ }
+
// Only send updates outside of DisplayManagerService for enabled displays
- if (display.isEnabledLocked()) {
+ if (displayIsEnabled) {
sendDisplayEventLocked(display, event);
+ } else if (mExtraDisplayEventLogging) {
+ Slog.i(TAG, "Not Sending Display Event; display is not enabled: " + display);
}
}
private void sendDisplayEventLocked(@NonNull LogicalDisplay display, @DisplayEvent int event) {
int displayId = display.getDisplayIdLocked();
Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event);
+ if (mExtraDisplayEventLogging) {
+ Slog.i(TAG, "Deliver Display Event on Handler: " + event);
+ }
mHandler.sendMessage(msg);
}
@@ -2967,7 +2982,7 @@
// Check if the target app is in cached mode
private boolean isUidCached(int uid) {
- if (mActivityManagerInternal == null) {
+ if (mActivityManagerInternal == null || uid < FIRST_APPLICATION_UID) {
return false;
}
int procState = mActivityManagerInternal.getUidProcessState(uid);
@@ -2984,7 +2999,11 @@
+ displayId + ", event=" + event
+ (uids != null ? ", uids=" + uids : ""));
}
-
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER,
+ "deliverDisplayEvent#event=" + event + ",displayId="
+ + displayId + (uids != null ? ", uids=" + uids : ""));
+ }
// Grab the lock and copy the callbacks.
final int count;
synchronized (mSyncRoot) {
@@ -3005,6 +3024,10 @@
// For cached apps, save the pending event until it becomes non-cached
synchronized (mPendingCallbackSelfLocked) {
PendingCallback pendingCallback = mPendingCallbackSelfLocked.get(uid);
+ if (extraLogging(callbackRecord.mPackageName)) {
+ Slog.i(TAG,
+ "Uid is cached: " + uid + ", pendingCallback: " + pendingCallback);
+ }
if (pendingCallback == null) {
mPendingCallbackSelfLocked.put(uid,
new PendingCallback(callbackRecord, displayId, event));
@@ -3019,6 +3042,11 @@
mTempCallbacks.clear();
}
+ private boolean extraLogging(String packageName) {
+ // TODO: b/306170135 - return mExtraDisplayLoggingPackageName & package name check instead
+ return true;
+ }
+
// Runs on Handler thread.
// Delivers display group event notifications to callbacks.
private void deliverDisplayGroupEvent(int groupId, int event) {
@@ -3462,6 +3490,7 @@
public final int mUid;
private final IDisplayManagerCallback mCallback;
private @EventsMask AtomicLong mEventsMask;
+ private final String mPackageName;
public boolean mWifiDisplayScanRequested;
@@ -3471,6 +3500,9 @@
mUid = uid;
mCallback = callback;
mEventsMask = new AtomicLong(eventsMask);
+
+ String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
+ mPackageName = packageNames == null ? null : packageNames[0];
}
public void updateEventsMask(@EventsMask long eventsMask) {
@@ -3479,9 +3511,13 @@
@Override
public void binderDied() {
- if (DEBUG) {
+ if (DEBUG || extraLogging(mPackageName)) {
Slog.d(TAG, "Display listener for pid " + mPid + " died.");
}
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER,
+ "displayManagerBinderDied#mPid=" + mPid);
+ }
onCallbackDied(this);
}
@@ -3490,6 +3526,15 @@
*/
public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
if (!shouldSendEvent(event)) {
+ if (extraLogging(mPackageName)) {
+ Slog.i(TAG,
+ "Not sending displayEvent: " + event + " due to mask:" + mEventsMask);
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER,
+ "notifyDisplayEventAsync#notSendingEvent=" + event + ",mEventsMask="
+ + mEventsMask);
+ }
return true;
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 1652871..0f00027 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -506,7 +506,6 @@
mTag = "DisplayPowerController2[" + mDisplayId + "]";
mThermalBrightnessThrottlingDataId =
logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
-
mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
mDisplayStatsId = mUniqueDisplayId.hashCode();
@@ -562,12 +561,13 @@
brightnessSetting, () -> postBrightnessChangeRunnable(),
new HandlerExecutor(mHandler));
- mBrightnessClamperController = new BrightnessClamperController(mHandler,
- modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData(
+ mBrightnessClamperController = mInjector.getBrightnessClamperController(
+ mHandler, modeChangeCallback::run,
+ new BrightnessClamperController.DisplayDeviceData(
mUniqueDisplayId,
mThermalBrightnessThrottlingDataId,
- mDisplayDeviceConfig
- ), mContext);
+ logicalDisplay.getPowerThrottlingDataIdLocked(),
+ mDisplayDeviceConfig), mContext, flags);
// Seed the cached brightness
saveBrightnessInfo(getScreenBrightnessSetting());
mAutomaticBrightnessStrategy =
@@ -821,10 +821,8 @@
.getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL;
final String thermalBrightnessThrottlingDataId =
mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
-
- mBrightnessClamperController.onDisplayChanged(
- new BrightnessClamperController.DisplayDeviceData(mUniqueDisplayId,
- mThermalBrightnessThrottlingDataId, config));
+ final String powerThrottlingDataId =
+ mLogicalDisplay.getPowerThrottlingDataIdLocked();
mHandler.postAtTime(() -> {
boolean changed = false;
@@ -858,6 +856,14 @@
}
mIsDisplayInternal = isDisplayInternal;
+ // using local variables here, when mBrightnessThrottler is removed,
+ // mThermalBrightnessThrottlingDataId could be removed as well
+ // changed = true will be not needed - clampers are maintaining their state and
+ // will call updatePowerState if needed.
+ mBrightnessClamperController.onDisplayChanged(
+ new BrightnessClamperController.DisplayDeviceData(uniqueId,
+ thermalBrightnessThrottlingDataId, powerThrottlingDataId, config));
+
if (changed) {
updatePowerState();
}
@@ -1348,6 +1354,8 @@
float rawBrightnessState = displayBrightnessState.getBrightness();
mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
boolean slowChange = displayBrightnessState.isSlowChange();
+ // custom transition duration
+ float customAnimationRate = displayBrightnessState.getCustomAnimationRate();
// Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
// doesn't yet have a valid lux value to use with auto-brightness.
@@ -1480,6 +1488,9 @@
brightnessState = clampedState.getBrightness();
slowChange = clampedState.isSlowChange();
+ // faster rate wins, at this point customAnimationRate == -1, strategy does not control
+ // customAnimationRate. Should be revisited if strategy start setting this value
+ customAnimationRate = Math.max(customAnimationRate, clampedState.getCustomAnimationRate());
mBrightnessReasonTemp.addModifier(clampedState.getBrightnessReason().getModifier());
if (updateScreenBrightnessSetting) {
@@ -1548,9 +1559,6 @@
// allowed range.
float animateValue = clampScreenBrightness(brightnessState);
- // custom transition duration
- float customTransitionRate = -1f;
-
// If there are any HDR layers on the screen, we have a special brightness value that we
// use instead. We still preserve the calculated brightness for Standard Dynamic Range
// (SDR) layers, but the main brightness value will be the one for HDR.
@@ -1565,10 +1573,21 @@
// We want to scale HDR brightness level with the SDR level, we also need to restore
// SDR brightness immediately when entering dim or low power mode.
animateValue = mBrightnessRangeController.getHdrBrightnessValue();
- customTransitionRate = mBrightnessRangeController.getHdrTransitionRate();
+ customAnimationRate = Math.max(customAnimationRate,
+ mBrightnessRangeController.getHdrTransitionRate());
mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_HDR);
}
+ // if doze or suspend state is requested, we want to finish brightnes animation fast
+ // to allow state animation to start
+ if (mPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE
+ && (mPowerRequest.dozeScreenState == Display.STATE_UNKNOWN // dozing
+ || mPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
+ || mPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND)) {
+ customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+ slowChange = false;
+ }
+
final float currentBrightness = mPowerState.getScreenBrightness();
final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
@@ -1596,9 +1615,9 @@
if (skipAnimation) {
animateScreenBrightness(animateValue, sdrAnimateValue,
SCREEN_ANIMATION_RATE_MINIMUM);
- } else if (customTransitionRate > 0) {
+ } else if (customAnimationRate > 0) {
animateScreenBrightness(animateValue, sdrAnimateValue,
- customTransitionRate, /* ignoreAnimationLimits = */true);
+ customAnimationRate, /* ignoreAnimationLimits = */true);
} else {
boolean isIncreasing = animateValue > currentBrightness;
final float rampSpeed;
@@ -3054,6 +3073,15 @@
modeChangeCallback, displayDeviceConfig, handler, flags, displayToken, info);
}
+ BrightnessClamperController getBrightnessClamperController(Handler handler,
+ BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+ BrightnessClamperController.DisplayDeviceData data, Context context,
+ DisplayManagerFlags flags) {
+
+ return new BrightnessClamperController(handler, clamperChangeListener, data, context,
+ flags);
+ }
+
DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
SensorManager sensorManager, Resources resources) {
return DisplayWhiteBalanceFactory.create(handler,
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index bd82b81..3d4209e 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -190,6 +190,11 @@
private SurfaceControl.RefreshRateRange mLayoutLimitedRefreshRate;
/**
+ * The ID of the power throttling data that should be used.
+ */
+ private String mPowerThrottlingDataId;
+
+ /**
* RefreshRateRange limitation for @Temperature.ThrottlingStatus
*/
@NonNull
@@ -205,6 +210,7 @@
mIsEnabled = true;
mIsInTransition = false;
mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
+ mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;
}
@@ -911,6 +917,25 @@
}
/**
+ * @param powerThrottlingDataId The ID of the brightness throttling data that this
+ * display should use.
+ */
+ public void setPowerThrottlingDataIdLocked(String powerThrottlingDataId) {
+ if (!Objects.equals(powerThrottlingDataId, mPowerThrottlingDataId)) {
+ mPowerThrottlingDataId = powerThrottlingDataId;
+ mDirty = true;
+ }
+ }
+
+ /**
+ * Returns powerThrottlingDataId which is the ID of the brightness
+ * throttling data that this display should use.
+ */
+ public String getPowerThrottlingDataIdLocked() {
+ return mPowerThrottlingDataId;
+ }
+
+ /**
* Sets the display of which this display is a follower, regarding brightness or other
* properties. If set to {@link Layout#NO_LEAD_DISPLAY}, this display does not follow any
* others, and has the potential to be a lead display to others.
@@ -976,6 +1001,7 @@
pw.println("mLeadDisplayId=" + mLeadDisplayId);
pw.println("mLayoutLimitedRefreshRate=" + mLayoutLimitedRefreshRate);
pw.println("mThermalRefreshRateThrottling=" + mThermalRefreshRateThrottling);
+ pw.println("mPowerThrottlingDataId=" + mPowerThrottlingDataId);
}
@Override
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index b3b16ad..c55bc62 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -1123,13 +1123,15 @@
displayLayout.getRefreshRateThermalThrottlingMapId()
)
);
-
setEnabledLocked(newDisplay, displayLayout.isEnabled());
newDisplay.setThermalBrightnessThrottlingDataIdLocked(
displayLayout.getThermalBrightnessThrottlingMapId() == null
? DisplayDeviceConfig.DEFAULT_ID
: displayLayout.getThermalBrightnessThrottlingMapId());
-
+ newDisplay.setPowerThrottlingDataIdLocked(
+ displayLayout.getPowerThrottlingMapId() == null
+ ? DisplayDeviceConfig.DEFAULT_ID
+ : displayLayout.getPowerThrottlingMapId());
newDisplay.setDisplayGroupNameLocked(displayLayout.getDisplayGroupName());
}
}
diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
index adaa539..bf384b0 100644
--- a/services/core/java/com/android/server/display/SmallAreaDetectionController.java
+++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
+import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
import android.util.ArrayMap;
@@ -30,15 +31,14 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.pkg.PackageStateInternal;
import java.io.PrintWriter;
-import java.util.Arrays;
import java.util.Map;
final class SmallAreaDetectionController {
- private static native void nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds);
- private static native void nativeSetSmallAreaDetectionThreshold(int uid, float threshold);
+ private static native void nativeUpdateSmallAreaDetection(int[] appIds, float[] thresholds);
+ private static native void nativeSetSmallAreaDetectionThreshold(int appId, float threshold);
// TODO(b/281720315): Move this to DeviceConfig once server side ready.
private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST =
@@ -47,12 +47,8 @@
private final Object mLock = new Object();
private final Context mContext;
private final PackageManagerInternal mPackageManager;
- private final UserManagerInternal mUserManager;
@GuardedBy("mLock")
private final Map<String, Float> mAllowPkgMap = new ArrayMap<>();
- // TODO(b/298722189): Update allowlist when user changes
- @GuardedBy("mLock")
- private int[] mUserIds;
static SmallAreaDetectionController create(@NonNull Context context) {
final SmallAreaDetectionController controller =
@@ -67,7 +63,6 @@
SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) {
mContext = context;
mPackageManager = LocalServices.getService(PackageManagerInternal.class);
- mUserManager = LocalServices.getService(UserManagerInternal.class);
deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
BackgroundThread.getExecutor(),
new SmallAreaDetectionController.OnPropertiesChangedListener());
@@ -76,6 +71,7 @@
@VisibleForTesting
void updateAllowlist(@Nullable String property) {
+ final Map<String, Float> allowPkgMap = new ArrayMap<>();
synchronized (mLock) {
mAllowPkgMap.clear();
if (property != null) {
@@ -86,8 +82,11 @@
.getStringArray(R.array.config_smallAreaDetectionAllowlist);
for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString);
}
- updateSmallAreaDetection();
+
+ if (mAllowPkgMap.isEmpty()) return;
+ allowPkgMap.putAll(mAllowPkgMap);
}
+ updateSmallAreaDetection(allowPkgMap);
}
@GuardedBy("mLock")
@@ -105,43 +104,32 @@
}
}
- @GuardedBy("mLock")
- private void updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold) {
- for (int i = 0; i < mUserIds.length; i++) {
- final int userId = mUserIds[i];
- final int uid = mPackageManager.getPackageUid(pkg, 0, userId);
- if (uid > 0) list.put(uid, threshold);
- }
- }
-
- @GuardedBy("mLock")
- private void updateSmallAreaDetection() {
- if (mAllowPkgMap.isEmpty()) return;
-
- mUserIds = mUserManager.getUserIds();
-
- final SparseArray<Float> uidThresholdList = new SparseArray<>();
- for (String pkg : mAllowPkgMap.keySet()) {
- final float threshold = mAllowPkgMap.get(pkg);
- updateUidListForAllUsers(uidThresholdList, pkg, threshold);
+ private void updateSmallAreaDetection(Map<String, Float> allowPkgMap) {
+ final SparseArray<Float> appIdThresholdList = new SparseArray(allowPkgMap.size());
+ for (String pkg : allowPkgMap.keySet()) {
+ final float threshold = allowPkgMap.get(pkg);
+ final PackageStateInternal stage = mPackageManager.getPackageStateInternal(pkg);
+ if (stage != null) {
+ appIdThresholdList.put(stage.getAppId(), threshold);
+ }
}
- final int[] uids = new int[uidThresholdList.size()];
- final float[] thresholds = new float[uidThresholdList.size()];
- for (int i = 0; i < uidThresholdList.size(); i++) {
- uids[i] = uidThresholdList.keyAt(i);
- thresholds[i] = uidThresholdList.valueAt(i);
+ final int[] appIds = new int[appIdThresholdList.size()];
+ final float[] thresholds = new float[appIdThresholdList.size()];
+ for (int i = 0; i < appIdThresholdList.size(); i++) {
+ appIds[i] = appIdThresholdList.keyAt(i);
+ thresholds[i] = appIdThresholdList.valueAt(i);
}
- updateSmallAreaDetection(uids, thresholds);
+ updateSmallAreaDetection(appIds, thresholds);
}
@VisibleForTesting
- void updateSmallAreaDetection(int[] uids, float[] thresholds) {
- nativeUpdateSmallAreaDetection(uids, thresholds);
+ void updateSmallAreaDetection(int[] appIds, float[] thresholds) {
+ nativeUpdateSmallAreaDetection(appIds, thresholds);
}
- void setSmallAreaDetectionThreshold(int uid, float threshold) {
- nativeSetSmallAreaDetectionThreshold(uid, threshold);
+ void setSmallAreaDetectionThreshold(int appId, float threshold) {
+ nativeSetSmallAreaDetectionThreshold(appId, threshold);
}
void dump(PrintWriter pw) {
@@ -151,7 +139,6 @@
for (String pkg : mAllowPkgMap.keySet()) {
pw.println(" " + pkg + " threshold = " + mAllowPkgMap.get(pkg));
}
- pw.println(" mUserIds=" + Arrays.toString(mUserIds));
}
}
@@ -167,11 +154,15 @@
private final class PackageReceiver implements PackageManagerInternal.PackageListObserver {
@Override
public void onPackageAdded(@NonNull String packageName, int uid) {
+ float threshold = 0.0f;
synchronized (mLock) {
if (mAllowPkgMap.containsKey(packageName)) {
- setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName));
+ threshold = mAllowPkgMap.get(packageName);
}
}
+ if (threshold > 0.0f) {
+ setSmallAreaDetectionThreshold(UserHandle.getAppId(uid), threshold);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index d910e16..b002587 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -43,6 +43,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Point;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.media.projection.IMediaProjection;
@@ -395,7 +396,7 @@
@Override
public Runnable requestDisplayStateLocked(int state, float brightnessState,
- float sdrBrightnessState) {
+ float sdrBrightnessState, DisplayOffloadSession displayOffloadSession) {
if (state != mDisplayState) {
mDisplayState = state;
if (state == Display.STATE_OFF) {
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index 54a280f..dfcda40 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -19,6 +19,8 @@
import android.annotation.NonNull;
import android.os.PowerManager;
+import com.android.server.display.DisplayBrightnessState;
+
import java.io.PrintWriter;
/**
@@ -33,6 +35,10 @@
return mBrightnessCap;
}
+ float getCustomAnimationRate() {
+ return DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+ }
+
boolean isActive() {
return mIsActive;
}
@@ -52,7 +58,8 @@
abstract void stop();
- enum Type {
- THERMAL
+ protected enum Type {
+ THERMAL,
+ POWER
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 14637af..b574919 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -34,9 +34,12 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.feature.DisplayManagerFlags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -52,25 +55,28 @@
private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
private final Handler mHandler;
private final ClamperChangeListener mClamperChangeListenerExternal;
-
private final Executor mExecutor;
private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers;
private final List<BrightnessModifier> mModifiers;
private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener;
private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+
+ private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
@Nullable
private Type mClamperType = null;
private boolean mClamperApplied = false;
public BrightnessClamperController(Handler handler,
- ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) {
- this(new Injector(), handler, clamperChangeListener, data, context);
+ ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
+ DisplayManagerFlags flags) {
+ this(new Injector(), handler, clamperChangeListener, data, context, flags);
}
@VisibleForTesting
BrightnessClamperController(Injector injector, Handler handler,
- ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) {
+ ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
+ DisplayManagerFlags flags) {
mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
mHandler = handler;
mClamperChangeListenerExternal = clamperChangeListener;
@@ -84,7 +90,7 @@
}
};
- mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data);
+ mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags);
mModifiers = injector.getModifiers(context);
mOnPropertiesChangedListener =
properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
@@ -110,6 +116,7 @@
builder.setIsSlowChange(slowChange);
builder.setBrightness(cappedBrightness);
builder.setMaxBrightness(mBrightnessCap);
+ builder.setCustomAnimationRate(mCustomAnimationRate);
if (mClamperType != null) {
builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
@@ -144,6 +151,8 @@
return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
} else if (mClamperType == Type.THERMAL) {
return BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
+ } else if (mClamperType == Type.POWER) {
+ return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC;
} else {
Slog.wtf(TAG, "BrightnessMaxReason not mapped for type=" + mClamperType);
return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
@@ -177,6 +186,7 @@
private void recalculateBrightnessCap() {
float brightnessCap = PowerManager.BRIGHTNESS_MAX;
Type clamperType = null;
+ float customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
BrightnessClamper<?> minClamper = mClampers.stream()
.filter(BrightnessClamper::isActive)
@@ -186,13 +196,17 @@
if (minClamper != null) {
brightnessCap = minClamper.getBrightnessCap();
clamperType = minClamper.getType();
+ customAnimationRate = minClamper.getCustomAnimationRate();
}
- if (mBrightnessCap != brightnessCap || mClamperType != clamperType) {
+ if (mBrightnessCap != brightnessCap || mClamperType != clamperType
+ || mCustomAnimationRate != customAnimationRate) {
mBrightnessCap = brightnessCap;
mClamperType = clamperType;
+ mCustomAnimationRate = customAnimationRate;
mClamperChangeListenerExternal.onChanged();
}
+
}
private void start() {
@@ -219,10 +233,15 @@
}
List<BrightnessClamper<? super DisplayDeviceData>> getClampers(Handler handler,
- ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
+ ClamperChangeListener clamperChangeListener, DisplayDeviceData data,
+ DisplayManagerFlags flags) {
List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>();
clampers.add(
new BrightnessThermalClamper(handler, clamperChangeListener, data));
+ if (flags.isPowerThrottlingClamperEnabled()) {
+ clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener,
+ data));
+ }
return clampers;
}
@@ -235,21 +254,26 @@
}
/**
- * Data for clampers
+ * Config Data for clampers
*/
- public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData {
+ public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData,
+ BrightnessPowerClamper.PowerData {
@NonNull
private final String mUniqueDisplayId;
@NonNull
private final String mThermalThrottlingDataId;
+ @NonNull
+ private final String mPowerThrottlingDataId;
private final DisplayDeviceConfig mDisplayDeviceConfig;
public DisplayDeviceData(@NonNull String uniqueDisplayId,
@NonNull String thermalThrottlingDataId,
+ @NonNull String powerThrottlingDataId,
@NonNull DisplayDeviceConfig displayDeviceConfig) {
mUniqueDisplayId = uniqueDisplayId;
mThermalThrottlingDataId = thermalThrottlingDataId;
+ mPowerThrottlingDataId = powerThrottlingDataId;
mDisplayDeviceConfig = displayDeviceConfig;
}
@@ -272,5 +296,24 @@
return mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId().get(
mThermalThrottlingDataId);
}
+
+ @NonNull
+ @Override
+ public String getPowerThrottlingDataId() {
+ return mPowerThrottlingDataId;
+ }
+
+ @Nullable
+ @Override
+ public PowerThrottlingData getPowerThrottlingData() {
+ return mDisplayDeviceConfig.getPowerThrottlingDataMapByThrottlingId().get(
+ mPowerThrottlingDataId);
+ }
+
+ @Nullable
+ @Override
+ public PowerThrottlingConfigData getPowerThrottlingConfigData() {
+ return mDisplayDeviceConfig.getPowerThrottlingConfigData();
+ }
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
new file mode 100644
index 0000000..339b589
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
@@ -0,0 +1,295 @@
+/*
+ * 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.display.brightness.clamper;
+
+import static com.android.server.display.DisplayDeviceConfig.DEFAULT_ID;
+import static com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.Temperature;
+import android.provider.DeviceConfigInterface;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.utils.DeviceConfigParsingUtils;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+
+class BrightnessPowerClamper extends
+ BrightnessClamper<BrightnessPowerClamper.PowerData> {
+
+ private static final String TAG = "BrightnessPowerClamper";
+ @NonNull
+ private final Injector mInjector;
+ @NonNull
+ private final DeviceConfigParameterProvider mConfigParameterProvider;
+ @NonNull
+ private final Handler mHandler;
+ @NonNull
+ private final ClamperChangeListener mChangeListener;
+ @Nullable
+ private PmicMonitor mPmicMonitor;
+ // data from DeviceConfig, for all displays, for all dataSets
+ // mapOf(uniqueDisplayId to mapOf(dataSetId to PowerThrottlingData))
+ @NonNull
+ private Map<String, Map<String, PowerThrottlingData>>
+ mPowerThrottlingDataOverride = Map.of();
+ // data from DisplayDeviceConfig, for particular display+dataSet
+ @Nullable
+ private PowerThrottlingData mPowerThrottlingDataFromDDC = null;
+ // Active data, if mPowerThrottlingDataOverride contains data for mUniqueDisplayId,
+ // mDataId, then use it, otherwise mPowerThrottlingDataFromDDC.
+ @Nullable
+ private PowerThrottlingData mPowerThrottlingDataActive = null;
+ @Nullable
+ private PowerThrottlingConfigData mPowerThrottlingConfigData = null;
+
+ private @Temperature.ThrottlingStatus int mCurrentThermalLevel = Temperature.THROTTLING_NONE;
+ private float mCurrentAvgPowerConsumed = 0;
+ @Nullable
+ private String mUniqueDisplayId = null;
+ @Nullable
+ private String mDataId = null;
+
+ private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
+ try {
+ int status = DeviceConfigParsingUtils.parseThermalStatus(key);
+ float powerQuota = Float.parseFloat(value);
+ return new ThrottlingLevel(status, powerQuota);
+ } catch (IllegalArgumentException iae) {
+ return null;
+ }
+ };
+
+ private final Function<List<ThrottlingLevel>, PowerThrottlingData>
+ mDataSetMapper = PowerThrottlingData::create;
+
+
+ BrightnessPowerClamper(Handler handler, ClamperChangeListener listener,
+ PowerData powerData) {
+ this(new Injector(), handler, listener, powerData);
+ }
+
+ @VisibleForTesting
+ BrightnessPowerClamper(Injector injector, Handler handler, ClamperChangeListener listener,
+ PowerData powerData) {
+ mInjector = injector;
+ mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+ mHandler = handler;
+ mChangeListener = listener;
+
+ mHandler.post(() -> {
+ setDisplayData(powerData);
+ loadOverrideData();
+ start();
+ });
+
+ }
+
+ @Override
+ @NonNull
+ BrightnessClamper.Type getType() {
+ return Type.POWER;
+ }
+
+ @Override
+ void onDeviceConfigChanged() {
+ mHandler.post(() -> {
+ loadOverrideData();
+ recalculateActiveData();
+ });
+ }
+
+ @Override
+ void onDisplayChanged(PowerData data) {
+ mHandler.post(() -> {
+ setDisplayData(data);
+ recalculateActiveData();
+ });
+ }
+
+ @Override
+ void stop() {
+ if (mPmicMonitor != null) {
+ mPmicMonitor.shutdown();
+ }
+ }
+
+ /**
+ * Dumps the state of BrightnessPowerClamper.
+ */
+ public void dump(PrintWriter pw) {
+ pw.println("BrightnessPowerClamper:");
+ pw.println(" mCurrentAvgPowerConsumed=" + mCurrentAvgPowerConsumed);
+ pw.println(" mUniqueDisplayId=" + mUniqueDisplayId);
+ pw.println(" mCurrentThermalLevel=" + mCurrentThermalLevel);
+ pw.println(" mPowerThrottlingDataFromDDC=" + (mPowerThrottlingDataFromDDC == null ? "null"
+ : mPowerThrottlingDataFromDDC.toString()));
+ super.dump(pw);
+ }
+
+ private void recalculateActiveData() {
+ if (mUniqueDisplayId == null || mDataId == null) {
+ return;
+ }
+ mPowerThrottlingDataActive = mPowerThrottlingDataOverride
+ .getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId,
+ mPowerThrottlingDataFromDDC);
+ if (mPowerThrottlingDataActive != null) {
+ if (mPmicMonitor != null) {
+ mPmicMonitor.stop();
+ mPmicMonitor.start();
+ }
+ } else {
+ if (mPmicMonitor != null) {
+ mPmicMonitor.stop();
+ }
+ }
+ recalculateBrightnessCap();
+ }
+
+ private void loadOverrideData() {
+ String throttlingDataOverride = mConfigParameterProvider.getPowerThrottlingData();
+ mPowerThrottlingDataOverride = DeviceConfigParsingUtils.parseDeviceConfigMap(
+ throttlingDataOverride, mDataPointMapper, mDataSetMapper);
+ }
+
+ private void setDisplayData(@NonNull PowerData data) {
+ mUniqueDisplayId = data.getUniqueDisplayId();
+ mDataId = data.getPowerThrottlingDataId();
+ mPowerThrottlingDataFromDDC = data.getPowerThrottlingData();
+ if (mPowerThrottlingDataFromDDC == null && !DEFAULT_ID.equals(mDataId)) {
+ Slog.wtf(TAG,
+ "Power throttling data is missing for powerThrottlingDataId=" + mDataId);
+ }
+
+ mPowerThrottlingConfigData = data.getPowerThrottlingConfigData();
+ if (mPowerThrottlingConfigData == null) {
+ Slog.d(TAG,
+ "Power throttling data is missing for configuration data.");
+ }
+ }
+
+ private void recalculateBrightnessCap() {
+ boolean isActive = false;
+ float targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+ float powerQuota = getPowerQuotaForThermalStatus(mCurrentThermalLevel);
+ if (mPowerThrottlingDataActive == null) {
+ return;
+ }
+ if (powerQuota > 0 && mCurrentAvgPowerConsumed > powerQuota) {
+ isActive = true;
+ // calculate new brightness Cap.
+ // Brightness has a linear relation to power-consumed.
+ targetBrightnessCap =
+ (powerQuota / mCurrentAvgPowerConsumed) * PowerManager.BRIGHTNESS_MAX;
+ // Cap to lowest allowed brightness on device.
+ targetBrightnessCap = Math.max(targetBrightnessCap,
+ mPowerThrottlingConfigData.brightnessLowestCapAllowed);
+ }
+
+ if (mBrightnessCap != targetBrightnessCap || mIsActive != isActive) {
+ mIsActive = isActive;
+ mBrightnessCap = targetBrightnessCap;
+ mChangeListener.onChanged();
+ }
+ }
+
+ private float getPowerQuotaForThermalStatus(@Temperature.ThrottlingStatus int thermalStatus) {
+ float powerQuota = 0f;
+ if (mPowerThrottlingDataActive != null) {
+ // Throttling levels are sorted by increasing severity
+ for (ThrottlingLevel level : mPowerThrottlingDataActive.throttlingLevels) {
+ if (level.thermalStatus <= thermalStatus) {
+ powerQuota = level.powerQuotaMilliWatts;
+ } else {
+ // Throttling levels that are greater than the current status are irrelevant
+ break;
+ }
+ }
+ }
+ return powerQuota;
+ }
+
+ private void recalculatePowerQuotaChange(float avgPowerConsumed, int thermalStatus) {
+ mHandler.post(() -> {
+ mCurrentThermalLevel = thermalStatus;
+ mCurrentAvgPowerConsumed = avgPowerConsumed;
+ recalculateBrightnessCap();
+ });
+ }
+
+ private void start() {
+ if (mPowerThrottlingConfigData == null) {
+ return;
+ }
+ PowerChangeListener listener = (powerConsumed, thermalStatus) -> {
+ recalculatePowerQuotaChange(powerConsumed, thermalStatus);
+ };
+ mPmicMonitor =
+ mInjector.getPmicMonitor(listener, mPowerThrottlingConfigData.pollingWindowMillis);
+ mPmicMonitor.start();
+ }
+
+ public interface PowerData {
+ @NonNull
+ String getUniqueDisplayId();
+
+ @NonNull
+ String getPowerThrottlingDataId();
+
+ @Nullable
+ PowerThrottlingData getPowerThrottlingData();
+
+ @Nullable
+ PowerThrottlingConfigData getPowerThrottlingConfigData();
+ }
+
+ /**
+ * Power change listener
+ */
+ @FunctionalInterface
+ public interface PowerChangeListener {
+ /**
+ * Notifies that power state changed from power controller.
+ */
+ void onChanged(float avgPowerConsumed, @Temperature.ThrottlingStatus int thermalStatus);
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ PmicMonitor getPmicMonitor(PowerChangeListener listener, int pollingTime) {
+ return new PmicMonitor(listener, pollingTime);
+ }
+
+ DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+ return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 39f0b13..200d88a 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -24,7 +24,6 @@
import android.view.SurfaceControlHdrLayerInfoListener;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.display.BrightnessUtils;
import com.android.server.display.config.HdrBrightnessData;
import java.io.PrintWriter;
@@ -176,21 +175,14 @@
} else if (mDesiredMaxBrightness != expectedMaxBrightness) {
mDesiredMaxBrightness = expectedMaxBrightness;
long debounceTime;
- long transitionDuration;
if (mDesiredMaxBrightness > mMaxBrightness) {
debounceTime = mHdrBrightnessData.mBrightnessIncreaseDebounceMillis;
- transitionDuration = mHdrBrightnessData.mBrightnessIncreaseDurationMillis;
+ mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampIncrease;
} else {
debounceTime = mHdrBrightnessData.mBrightnessDecreaseDebounceMillis;
- transitionDuration = mHdrBrightnessData.mBrightnessDecreaseDurationMillis;
+ mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampDecrease;
}
- float maxHlg = BrightnessUtils.convertLinearToGamma(mMaxBrightness);
- float desiredMaxHlg = BrightnessUtils.convertLinearToGamma(mDesiredMaxBrightness);
-
- mDesiredTransitionRate = Math.abs(
- (maxHlg - desiredMaxHlg) * 1000f / transitionDuration);
-
mHandler.removeCallbacks(mDebouncer);
mHandler.postDelayed(mDebouncer, debounceTime);
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
new file mode 100644
index 0000000..26784f23
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
@@ -0,0 +1,192 @@
+/*
+ * 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.display.brightness.clamper;
+
+import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.IThermalService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Temperature;
+import android.power.PowerStatsInternal;
+import android.util.IntArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Monitors the display consumed power and helps make informed decision,
+ * regarding overconsumption.
+ */
+public class PmicMonitor {
+ private static final String TAG = "PmicMonitor";
+
+ // The executor to periodically monitor the display power.
+ private final ScheduledExecutorService mExecutor;
+ @NonNull
+ private final PowerChangeListener mPowerChangeListener;
+ private final long mPowerMonitorPeriodConfigSecs;
+ private final PowerStatsInternal mPowerStatsInternal;
+ @VisibleForTesting final IThermalService mThermalService;
+ private ScheduledFuture<?> mPmicMonitorFuture;
+ private float mLastEnergyConsumed = 0;
+ private float mCurrentAvgPower = 0;
+ private Temperature mCurrentTemperature;
+ private long mCurrentTimestampMillis = 0;
+
+ PmicMonitor(PowerChangeListener listener, int powerMonitorPeriodConfigSecs) {
+ mPowerChangeListener = listener;
+ mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
+ mThermalService = IThermalService.Stub.asInterface(
+ ServiceManager.getService(Context.THERMAL_SERVICE));
+ // start a periodic worker thread.
+ mExecutor = Executors.newSingleThreadScheduledExecutor();
+ mPowerMonitorPeriodConfigSecs = (long) powerMonitorPeriodConfigSecs;
+ }
+
+ @Nullable
+ private Temperature getDisplayTemperature() {
+ Temperature retTemperature = null;
+ try {
+ Temperature[] temperatures;
+ // TODO b/279114539 Try DISPLAY first and then fallback to SKIN.
+ temperatures = mThermalService.getCurrentTemperaturesWithType(
+ Temperature.TYPE_SKIN);
+ if (temperatures.length > 1) {
+ Slog.w(TAG, "Multiple skin temperatures not allowed!");
+ }
+ if (temperatures.length > 0) {
+ retTemperature = temperatures[0];
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "getDisplayTemperature failed" + e);
+ }
+ return retTemperature;
+ }
+
+ private void capturePeriodicDisplayPower() {
+ final EnergyConsumer[] energyConsumers = mPowerStatsInternal.getEnergyConsumerInfo();
+ if (energyConsumers == null || energyConsumers.length == 0) {
+ return;
+ }
+ final IntArray energyConsumerIds = new IntArray();
+ for (int i = 0; i < energyConsumers.length; i++) {
+ if (energyConsumers[i].type == EnergyConsumerType.DISPLAY) {
+ energyConsumerIds.add(energyConsumers[i].id);
+ }
+ }
+
+ if (energyConsumerIds.size() == 0) {
+ Slog.w(TAG, "DISPLAY energyConsumerIds size is null");
+ return;
+ }
+ CompletableFuture<EnergyConsumerResult[]> futureECRs =
+ mPowerStatsInternal.getEnergyConsumedAsync(energyConsumerIds.toArray());
+ if (futureECRs == null) {
+ Slog.w(TAG, "Energy consumers results are null");
+ return;
+ }
+
+ EnergyConsumerResult[] displayResults;
+ try {
+ displayResults = futureECRs.get();
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "timeout or interrupt reading getEnergyConsumedAsync failed", e);
+ displayResults = null;
+ } catch (ExecutionException e) {
+ Slog.wtf(TAG, "exception reading getEnergyConsumedAsync: ", e);
+ displayResults = null;
+ }
+
+ if (displayResults == null || displayResults.length == 0) {
+ Slog.w(TAG, "displayResults are null");
+ return;
+ }
+ // Support for only 1 display rail.
+ float energyConsumed = (displayResults[0].energyUWs - mLastEnergyConsumed);
+ float timeIntervalSeconds =
+ (displayResults[0].timestampMs - mCurrentTimestampMillis) / 1000.f;
+ // energy consumed is received in microwatts-seconds.
+ float currentPower = energyConsumed / timeIntervalSeconds;
+ // convert power received in microwatts to milliwatts.
+ currentPower = currentPower / 1000.f;
+
+ // capture thermal state.
+ Temperature displayTemperature = getDisplayTemperature();
+ mCurrentAvgPower = currentPower;
+ mCurrentTemperature = displayTemperature;
+ mLastEnergyConsumed = displayResults[0].energyUWs;
+ mCurrentTimestampMillis = displayResults[0].timestampMs;
+ if (mCurrentTemperature != null) {
+ mPowerChangeListener.onChanged(mCurrentAvgPower, mCurrentTemperature.getStatus());
+ }
+ }
+
+ /**
+ * Start polling the power IC.
+ */
+ public void start() {
+ if (mPowerStatsInternal == null) {
+ Slog.w(TAG, "Power stats service not found for monitoring.");
+ return;
+ }
+ if (mThermalService == null) {
+ Slog.w(TAG, "Thermal service not found.");
+ return;
+ }
+ if (mPmicMonitorFuture == null) {
+ mPmicMonitorFuture = mExecutor.scheduleAtFixedRate(
+ this::capturePeriodicDisplayPower,
+ mPowerMonitorPeriodConfigSecs,
+ mPowerMonitorPeriodConfigSecs,
+ TimeUnit.SECONDS);
+ } else {
+ Slog.e(TAG, "already scheduled, stop() called before start.");
+ }
+ }
+
+ /**
+ * Stop polling to power IC.
+ */
+ public void stop() {
+ if (mPmicMonitorFuture != null) {
+ mPmicMonitorFuture.cancel(true);
+ mPmicMonitorFuture = null;
+ }
+ }
+
+ /**
+ * Shutdown power IC service and worker thread.
+ */
+ public void shutdown() {
+ mExecutor.shutdownNow();
+ }
+}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 03b0cfc..e3aa161 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -234,7 +234,9 @@
}
}
- @VisibleForTesting void onUserChanged(int userHandle) {
+ // should be called in handler thread (same thread that started animation)
+ @VisibleForTesting
+ void onUserChanged(int userHandle) {
final ContentResolver cr = getContext().getContentResolver();
if (mCurrentUser != UserHandle.USER_NULL) {
@@ -473,6 +475,15 @@
}
}
+ // should be called in handler thread (same thread that started animation)
+ @VisibleForTesting
+ void cancelAllAnimators() {
+ mNightDisplayTintController.cancelAnimator();
+ mGlobalSaturationTintController.cancelAnimator();
+ mReduceBrightColorsTintController.cancelAnimator();
+ mDisplayWhiteBalanceTintController.cancelAnimator();
+ }
+
private boolean resetReduceBrightColors() {
if (mCurrentUser == UserHandle.USER_NULL) {
return false;
diff --git a/services/core/java/com/android/server/display/config/HdrBrightnessData.java b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
index 48d671d..837fbf7 100644
--- a/services/core/java/com/android/server/display/config/HdrBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
@@ -40,9 +40,9 @@
public final long mBrightnessIncreaseDebounceMillis;
/**
- * Brightness increase animation duration
+ * Brightness increase animation speed
*/
- public final long mBrightnessIncreaseDurationMillis;
+ public final float mScreenBrightnessRampIncrease;
/**
* Debounce time for brightness decrease
@@ -50,19 +50,19 @@
public final long mBrightnessDecreaseDebounceMillis;
/**
- * Brightness decrease animation duration
+ * Brightness decrease animation speed
*/
- public final long mBrightnessDecreaseDurationMillis;
+ public final float mScreenBrightnessRampDecrease;
@VisibleForTesting
public HdrBrightnessData(Map<Float, Float> maxBrightnessLimits,
- long brightnessIncreaseDebounceMillis, long brightnessIncreaseDurationMillis,
- long brightnessDecreaseDebounceMillis, long brightnessDecreaseDurationMillis) {
+ long brightnessIncreaseDebounceMillis, float screenBrightnessRampIncrease,
+ long brightnessDecreaseDebounceMillis, float screenBrightnessRampDecrease) {
mMaxBrightnessLimits = maxBrightnessLimits;
mBrightnessIncreaseDebounceMillis = brightnessIncreaseDebounceMillis;
- mBrightnessIncreaseDurationMillis = brightnessIncreaseDurationMillis;
+ mScreenBrightnessRampIncrease = screenBrightnessRampIncrease;
mBrightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis;
- mBrightnessDecreaseDurationMillis = brightnessDecreaseDurationMillis;
+ mScreenBrightnessRampDecrease = screenBrightnessRampDecrease;
}
@Override
@@ -70,9 +70,9 @@
return "HdrBrightnessData {"
+ "mMaxBrightnessLimits: " + mMaxBrightnessLimits
+ ", mBrightnessIncreaseDebounceMillis: " + mBrightnessIncreaseDebounceMillis
- + ", mBrightnessIncreaseDurationMillis: " + mBrightnessIncreaseDurationMillis
+ + ", mScreenBrightnessRampIncrease: " + mScreenBrightnessRampIncrease
+ ", mBrightnessDecreaseDebounceMillis: " + mBrightnessDecreaseDebounceMillis
- + ", mBrightnessDecreaseDurationMillis: " + mBrightnessDecreaseDurationMillis
+ + ", mScreenBrightnessRampDecrease: " + mScreenBrightnessRampDecrease
+ "} ";
}
@@ -94,8 +94,8 @@
return new HdrBrightnessData(brightnessLimits,
hdrConfig.getBrightnessIncreaseDebounceMillis().longValue(),
- hdrConfig.getBrightnessIncreaseDurationMillis().longValue(),
+ hdrConfig.getScreenBrightnessRampIncrease().floatValue(),
hdrConfig.getBrightnessDecreaseDebounceMillis().longValue(),
- hdrConfig.getBrightnessDecreaseDurationMillis().longValue());
+ hdrConfig.getScreenBrightnessRampDecrease().floatValue());
}
}
diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
index 23ffe59..465584c 100644
--- a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
+++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
@@ -77,6 +77,12 @@
// Test parameters
// usage e.g.: adb shell device_config put display_manager refresh_rate_in_hbm_sunlight 90
+ // allows to customize power throttling data
+ public String getPowerThrottlingData() {
+ return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_POWER_THROTTLING_DATA, null);
+ }
+
// allows to customize brightness throttling data
public String getBrightnessThrottlingData() {
return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 7050c5a..d953e8e 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -67,20 +67,35 @@
Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE,
Flags::backUpSmoothDisplayAndForcePeakRefreshRate);
+ private final FlagState mPowerThrottlingClamperFlagState = new FlagState(
+ Flags.FLAG_ENABLE_POWER_THROTTLING_CLAMPER,
+ Flags::enablePowerThrottlingClamper);
+
+ private final FlagState mSmallAreaDetectionFlagState = new FlagState(
+ Flags.FLAG_ENABLE_SMALL_AREA_DETECTION,
+ Flags::enableSmallAreaDetection);
+
/** Returns whether connected display management is enabled or not. */
public boolean isConnectedDisplayManagementEnabled() {
return mConnectedDisplayManagementFlagState.isEnabled();
}
- /** Returns whether hdr clamper is enabled on not*/
+ /** Returns whether NBM Controller is enabled or not. */
public boolean isNbmControllerEnabled() {
return mNbmControllerFlagState.isEnabled();
}
+ /** Returns whether hdr clamper is enabled on not. */
public boolean isHdrClamperEnabled() {
return mHdrClamperFlagState.isEnabled();
}
+ /** Returns whether power throttling clamper is enabled on not. */
+ public boolean isPowerThrottlingClamperEnabled() {
+ return mPowerThrottlingClamperFlagState.isEnabled();
+ }
+
+
/**
* Returns whether adaptive tone improvements are enabled
*/
@@ -136,6 +151,10 @@
return mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState.isEnabled();
}
+ public boolean isSmallAreaDetectionEnabled() {
+ return mSmallAreaDetectionFlagState.isEnabled();
+ }
+
private static class FlagState {
private final String mName;
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index a85e10d..9141814 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -27,6 +27,14 @@
}
flag {
+ name: "enable_power_throttling_clamper"
+ namespace: "display_manager"
+ description: "Feature flag for Power Throttling Clamper"
+ bug: "294777007"
+ is_fixed_read_only: true
+}
+
+flag {
name: "enable_adaptive_tone_improvements_1"
namespace: "display_manager"
description: "Feature flag for Adaptive Tone Improvements"
@@ -96,3 +104,12 @@
bug: "211737588"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_small_area_detection"
+ namespace: "display_manager"
+ description: "Feature flag for SmallAreaDetection"
+ bug: "298722189"
+ is_fixed_read_only: true
+}
+
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index d9ec3de..40cb3303 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -80,7 +80,8 @@
createDisplayLocked(address, /* isDefault= */ true, /* isEnabled= */ true,
DEFAULT_DISPLAY_GROUP_NAME, idProducer, POSITION_UNKNOWN,
/* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null,
- /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+ /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null,
+ /* powerThrottlingMapId= */ null);
}
/**
@@ -97,6 +98,7 @@
* @param refreshRateZoneId Layout limited refresh rate zone name.
* @param refreshRateThermalThrottlingMapId Name of which refresh rate throttling
* policy should be used.
+ * @param powerThrottlingMapId Name of which power throttling policy should be used.
*
* @exception IllegalArgumentException When a default display owns a display group other than
* DEFAULT_DISPLAY_GROUP.
@@ -106,7 +108,8 @@
String displayGroupName, DisplayIdProducer idProducer, int position,
@Nullable DisplayAddress leadDisplayAddress, String brightnessThrottlingMapId,
@Nullable String refreshRateZoneId,
- @Nullable String refreshRateThermalThrottlingMapId) {
+ @Nullable String refreshRateThermalThrottlingMapId,
+ @Nullable String powerThrottlingMapId) {
if (contains(address)) {
Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
return;
@@ -139,7 +142,7 @@
final Display display = new Display(address, logicalDisplayId, isEnabled, displayGroupName,
brightnessThrottlingMapId, position, leadDisplayAddress, refreshRateZoneId,
- refreshRateThermalThrottlingMapId);
+ refreshRateThermalThrottlingMapId, powerThrottlingMapId);
mDisplays.add(display);
}
@@ -311,6 +314,9 @@
@Nullable
private final String mThermalRefreshRateThrottlingMapId;
+ @Nullable
+ private final String mPowerThrottlingMapId;
+
// The ID of the lead display that this display will follow in a layout. -1 means no lead.
// This is determined using {@code mLeadDisplayAddress}.
private int mLeadDisplayId;
@@ -318,7 +324,8 @@
private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
@NonNull String displayGroupName, String brightnessThrottlingMapId, int position,
@Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId,
- @Nullable String refreshRateThermalThrottlingMapId) {
+ @Nullable String refreshRateThermalThrottlingMapId,
+ @Nullable String powerThrottlingMapId) {
mAddress = address;
mLogicalDisplayId = logicalDisplayId;
mIsEnabled = isEnabled;
@@ -328,6 +335,7 @@
mLeadDisplayAddress = leadDisplayAddress;
mRefreshRateZoneId = refreshRateZoneId;
mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId;
+ mPowerThrottlingMapId = powerThrottlingMapId;
mLeadDisplayId = NO_LEAD_DISPLAY;
}
@@ -344,6 +352,7 @@
+ ", mLeadDisplayId: " + mLeadDisplayId
+ ", mLeadDisplayAddress: " + mLeadDisplayAddress
+ ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId
+ + ", mPowerThrottlingMapId: " + mPowerThrottlingMapId
+ "}";
}
@@ -366,7 +375,9 @@
&& this.mLeadDisplayId == otherDisplay.mLeadDisplayId
&& Objects.equals(mLeadDisplayAddress, otherDisplay.mLeadDisplayAddress)
&& Objects.equals(mThermalRefreshRateThrottlingMapId,
- otherDisplay.mThermalRefreshRateThrottlingMapId);
+ otherDisplay.mThermalRefreshRateThrottlingMapId)
+ && Objects.equals(mPowerThrottlingMapId,
+ otherDisplay.mPowerThrottlingMapId);
}
@Override
@@ -382,6 +393,7 @@
result = 31 * result + mLeadDisplayId;
result = 31 * result + Objects.hashCode(mLeadDisplayAddress);
result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId);
+ result = 31 * result + Objects.hashCode(mPowerThrottlingMapId);
return result;
}
@@ -441,6 +453,15 @@
return mThermalRefreshRateThrottlingMapId;
}
+ /**
+ * Gets the id of the power throttling map that should be used.
+ * @return The ID of the power throttling map that this display should use,
+ * null if unspecified, will fall back to default.
+ */
+ public String getPowerThrottlingMapId() {
+ return mPowerThrottlingMapId;
+ }
+
private void setLeadDisplayId(int id) {
mLeadDisplayId = id;
}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index ca23844..d023913 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -723,7 +723,9 @@
if (mode.getPhysicalWidth() > maxAllowedWidth
|| mode.getPhysicalHeight() > maxAllowedHeight
|| mode.getPhysicalWidth() < outSummary.minWidth
- || mode.getPhysicalHeight() < outSummary.minHeight) {
+ || mode.getPhysicalHeight() < outSummary.minHeight
+ || mode.getRefreshRate() < outSummary.minPhysicalRefreshRate
+ || mode.getRefreshRate() > outSummary.maxPhysicalRefreshRate) {
continue;
}
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index b090334..27e1b9a 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -60,84 +60,99 @@
private static final long TIMEOUT_MS = 10_000;
/** Handler for registering timeouts for live entries. */
+ @GuardedBy("mLock")
private final Handler mHandler;
/** Singleton instance of the History. */
- @GuardedBy("ImeTrackerService.this")
+ @GuardedBy("mLock")
private final History mHistory = new History();
+ private final Object mLock = new Object();
+
ImeTrackerService(@NonNull Looper looper) {
mHandler = new Handler(looper, null /* callback */, true /* async */);
}
@NonNull
@Override
- public synchronized ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+ public ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
@ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
final var binder = new Binder();
final var token = new ImeTracker.Token(binder, tag);
final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_SHOW, ImeTracker.STATUS_RUN,
origin, reason);
- mHistory.addEntry(binder, entry);
+ synchronized (mLock) {
+ mHistory.addEntry(binder, entry);
- // Register a delayed task to handle the case where the new entry times out.
- mHandler.postDelayed(() -> {
- synchronized (ImeTrackerService.this) {
- mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
- }
- }, TIMEOUT_MS);
-
+ // Register a delayed task to handle the case where the new entry times out.
+ mHandler.postDelayed(() -> {
+ synchronized (mLock) {
+ mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT,
+ ImeTracker.PHASE_NOT_SET);
+ }
+ }, TIMEOUT_MS);
+ }
return token;
}
@NonNull
@Override
- public synchronized ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
+ public ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
@ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
final var binder = new Binder();
final var token = new ImeTracker.Token(binder, tag);
final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_HIDE, ImeTracker.STATUS_RUN,
origin, reason);
- mHistory.addEntry(binder, entry);
+ synchronized (mLock) {
+ mHistory.addEntry(binder, entry);
- // Register a delayed task to handle the case where the new entry times out.
- mHandler.postDelayed(() -> {
- synchronized (ImeTrackerService.this) {
- mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
- }
- }, TIMEOUT_MS);
-
+ // Register a delayed task to handle the case where the new entry times out.
+ mHandler.postDelayed(() -> {
+ synchronized (mLock) {
+ mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT,
+ ImeTracker.PHASE_NOT_SET);
+ }
+ }, TIMEOUT_MS);
+ }
return token;
}
@Override
- public synchronized void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
- final var entry = mHistory.getEntry(binder);
- if (entry == null) return;
+ public void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
+ synchronized (mLock) {
+ final var entry = mHistory.getEntry(binder);
+ if (entry == null) return;
- entry.mPhase = phase;
+ entry.mPhase = phase;
+ }
}
@Override
- public synchronized void onFailed(@NonNull ImeTracker.Token statsToken,
- @ImeTracker.Phase int phase) {
- mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
+ public void onFailed(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
+ synchronized (mLock) {
+ mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
+ }
}
@Override
- public synchronized void onCancelled(@NonNull ImeTracker.Token statsToken,
- @ImeTracker.Phase int phase) {
- mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
+ public void onCancelled(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
+ synchronized (mLock) {
+ mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
+ }
}
@Override
- public synchronized void onShown(@NonNull ImeTracker.Token statsToken) {
- mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+ public void onShown(@NonNull ImeTracker.Token statsToken) {
+ synchronized (mLock) {
+ mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+ }
}
@Override
- public synchronized void onHidden(@NonNull ImeTracker.Token statsToken) {
- mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+ public void onHidden(@NonNull ImeTracker.Token statsToken) {
+ synchronized (mLock) {
+ mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+ }
}
/**
@@ -146,25 +161,30 @@
* @param statsToken the token corresponding to the current IME request.
* @param requestWindowName the name of the window that created the IME request.
*/
- public synchronized void onImmsUpdate(@NonNull ImeTracker.Token statsToken,
+ public void onImmsUpdate(@NonNull ImeTracker.Token statsToken,
@NonNull String requestWindowName) {
- final var entry = mHistory.getEntry(statsToken.getBinder());
- if (entry == null) return;
+ synchronized (mLock) {
+ final var entry = mHistory.getEntry(statsToken.getBinder());
+ if (entry == null) return;
- entry.mRequestWindowName = requestWindowName;
+ entry.mRequestWindowName = requestWindowName;
+ }
}
/** Dumps the contents of the history. */
- public synchronized void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- mHistory.dump(pw, prefix);
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ synchronized (mLock) {
+ mHistory.dump(pw, prefix);
+ }
}
@EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
@Override
- public synchronized boolean hasPendingImeVisibilityRequests() {
+ public boolean hasPendingImeVisibilityRequests() {
super.hasPendingImeVisibilityRequests_enforcePermission();
-
- return !mHistory.mLiveEntries.isEmpty();
+ synchronized (mLock) {
+ return !mHistory.mLiveEntries.isEmpty();
+ }
}
/**
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index f168f43..f35b045 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -240,6 +240,10 @@
private static final String LSKF_LAST_CHANGED_TIME_KEY = "sp-handle-ts";
private static final String USER_SERIAL_NUMBER_KEY = "serial-number";
+ private static final String MIGRATED_FRP2 = "migrated_frp2";
+ private static final String MIGRATED_KEYSTORE_NS = "migrated_keystore_namespace";
+ private static final String MIGRATED_SP_CE_ONLY = "migrated_all_users_to_sp_and_bound_ce";
+
// Duration that LockSettingsService will store the gatekeeper password for. This allows
// multiple biometric enrollments without prompting the user to enter their password via
// ConfirmLockPassword/ConfirmLockPattern multiple times. This needs to be at least the duration
@@ -909,14 +913,14 @@
}
private void migrateOldData() {
- if (getString("migrated_keystore_namespace", null, 0) == null) {
+ if (getString(MIGRATED_KEYSTORE_NS, null, 0) == null) {
boolean success = true;
synchronized (mSpManager) {
success &= mSpManager.migrateKeyNamespace();
}
success &= migrateProfileLockKeys();
if (success) {
- setString("migrated_keystore_namespace", "true", 0);
+ setString(MIGRATED_KEYSTORE_NS, "true", 0);
Slog.i(TAG, "Migrated keys to LSS namespace");
} else {
Slog.w(TAG, "Failed to migrate keys to LSS namespace");
@@ -936,9 +940,9 @@
// "migrated_frp" to "migrated_frp2" to cause migrateFrpCredential() to run again on devices
// where it had run before.
if (LockPatternUtils.frpCredentialEnabled(mContext)
- && !getBoolean("migrated_frp2", false, 0)) {
+ && !getBoolean(MIGRATED_FRP2, false, 0)) {
migrateFrpCredential();
- setBoolean("migrated_frp2", true, 0);
+ setBoolean(MIGRATED_FRP2, true, 0);
}
}
@@ -1028,14 +1032,14 @@
// If this gets interrupted (e.g. by the device powering off), there shouldn't be a
// problem since this will run again on the next boot, and setUserKeyProtection() is
// okay with the key being already protected by the given secret.
- if (getString("migrated_all_users_to_sp_and_bound_ce", null, 0) == null) {
+ if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null) {
for (UserInfo user : mUserManager.getAliveUsers()) {
removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber);
synchronized (mSpManager) {
migrateUserToSpWithBoundCeKeyLocked(user.id);
}
}
- setString("migrated_all_users_to_sp_and_bound_ce", "true", 0);
+ setString(MIGRATED_SP_CE_ONLY, "true", 0);
}
mThirdPartyAppsStarted = true;
@@ -1062,7 +1066,7 @@
Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
return;
}
- setUserKeyProtection(userId, result.syntheticPassword.deriveFileBasedEncryptionKey());
+ setUserKeyProtection(userId, result.syntheticPassword);
}
}
@@ -1347,8 +1351,8 @@
AndroidKeyStoreMaintenance.onUserPasswordChanged(userHandle, password);
}
- private void unlockKeystore(byte[] password, int userHandle) {
- Authorization.onLockScreenEvent(false, userHandle, password, null);
+ private void unlockKeystore(int userId, SyntheticPassword sp) {
+ Authorization.onLockScreenEvent(false, userId, sp.deriveKeyStorePassword(), null);
}
@VisibleForTesting /** Note: this method is overridden in unit tests */
@@ -2001,7 +2005,8 @@
mStorage.writeChildProfileLock(profileUserId, ArrayUtils.concat(iv, ciphertext));
}
- private void setUserKeyProtection(@UserIdInt int userId, byte[] secret) {
+ private void setUserKeyProtection(@UserIdInt int userId, SyntheticPassword sp) {
+ final byte[] secret = sp.deriveFileBasedEncryptionKey();
final long callingId = Binder.clearCallingIdentity();
try {
mStorageManager.setUserKeyProtection(userId, secret);
@@ -2045,7 +2050,9 @@
}
}
- private void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
+ @Override
+ public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
+ checkPasswordReadPermission();
synchronized (mSpManager) {
if (isUserKeyUnlocked(userId)) {
Slogf.d(TAG, "CE storage for user %d is already unlocked", userId);
@@ -2768,7 +2775,7 @@
final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
LockscreenCredential.createNone(), sp, userId);
setCurrentLskfBasedProtectorId(protectorId, userId);
- setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
+ setUserKeyProtection(userId, sp);
onSyntheticPasswordCreated(userId, sp);
Slogf.i(TAG, "Successfully initialized synthetic password for user %d", userId);
return sp;
@@ -2827,7 +2834,7 @@
}
}
- unlockKeystore(sp.deriveKeyStorePassword(), userId);
+ unlockKeystore(userId, sp);
unlockUserKey(userId, sp);
@@ -2894,7 +2901,7 @@
mSpManager.clearSidForUser(userId);
gateKeeperClearSecureUserId(userId);
unlockUserKey(userId, sp);
- unlockKeystore(sp.deriveKeyStorePassword(), userId);
+ unlockKeystore(userId, sp);
setKeystorePassword(null, userId);
removeBiometricsForUser(userId);
}
@@ -3454,11 +3461,6 @@
}
@Override
- public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
- LockSettingsService.this.unlockUserKeyIfUnsecured(userId);
- }
-
- @Override
public void createNewUser(@UserIdInt int userId, int userSerialNumber) {
LockSettingsService.this.createNewUser(userId, userSerialNumber);
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index df95c69..4bac872 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -174,7 +174,7 @@
pw.println(" Sets the lock screen as PIN, using the given PIN to unlock.");
pw.println("");
pw.println(" set-password [--old <CREDENTIAL>] [--user USER_ID] <PASSWORD>");
- pw.println(" Sets the lock screen as password, using the given PASSOWRD to unlock.");
+ pw.println(" Sets the lock screen as password, using the given PASSWORD to unlock.");
pw.println("");
pw.println(" clear [--old <CREDENTIAL>] [--user USER_ID]");
pw.println(" Clears the lock credentials.");
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 1c5ecb7..1e8b387 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -45,7 +45,7 @@
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
index eb997ba..8bc69c2 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
@@ -345,7 +345,7 @@
}
private void notifyBluetoothRoutesUpdated() {
- mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes());
+ mListener.onBluetoothRoutesUpdated();
}
private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 93f6ff3..33190ad 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -231,7 +231,7 @@
}
if (isDeviceRouteChanged) {
- mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
+ mOnDeviceRouteChangedListener.onDeviceRouteChanged();
}
}
}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java
index ddeeacc..2b01001 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteController.java
@@ -136,12 +136,8 @@
*/
interface BluetoothRoutesUpdatedListener {
- /**
- * Called when Bluetooth routes have changed.
- *
- * @param routes updated Bluetooth routes list.
- */
- void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
+ /** Called when Bluetooth routes have changed. */
+ void onBluetoothRoutesUpdated();
}
/**
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index e17f4a3..7876095 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -93,12 +93,8 @@
*/
interface OnDeviceRouteChangedListener {
- /**
- * Called when device route has changed.
- *
- * @param deviceRoute non-null device route.
- */
- void onDeviceRouteChanged(@NonNull MediaRoute2Info deviceRoute);
+ /** Called when device route has changed. */
+ void onDeviceRouteChanged();
}
}
diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
index 1980403..ba3cecf 100644
--- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
@@ -280,7 +280,7 @@
private void notifyBluetoothRoutesUpdated() {
if (mListener != null) {
- mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes());
+ mListener.onBluetoothRoutesUpdated();
}
}
diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
index 971d11f..6ba40ae 100644
--- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
@@ -165,8 +165,8 @@
}
}
- private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) {
- mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
+ private void notifyDeviceRouteUpdate() {
+ mOnDeviceRouteChangedListener.onDeviceRouteChanged();
}
private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
@@ -177,7 +177,7 @@
synchronized (LegacyDeviceRouteController.this) {
mDeviceRoute = deviceRoute;
}
- notifyDeviceRouteUpdate(deviceRoute);
+ notifyDeviceRouteUpdate();
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index cc261a4..44719f8 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -515,8 +515,7 @@
// Binder call
@Override
- public RoutingSessionInfo getSystemSessionInfoForPackage(IMediaRouter2Manager manager,
- String packageName) {
+ public RoutingSessionInfo getSystemSessionInfoForPackage(@Nullable String packageName) {
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
boolean setDeviceRouteSelected = false;
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 95ca08c..a158b18 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -97,20 +97,23 @@
public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl {
/**
- * {@link MediaSession#setMediaButtonBroadcastReceiver(ComponentName)} throws an {@link
- * IllegalArgumentException} if the provided {@link ComponentName} does not resolve to a valid
- * {@link android.content.BroadcastReceiver broadcast receiver} for apps targeting Android U and
- * above. For apps targeting Android T and below, the request will be ignored.
+ * {@link android.media.session.MediaSession#setMediaButtonBroadcastReceiver(
+ * android.content.ComponentName)} throws an {@link
+ * java.lang.IllegalArgumentException} if the provided {@link android.content.ComponentName}
+ * does not resolve to a valid {@link android.content.BroadcastReceiver broadcast receiver}
+ * for apps targeting Android U and above. For apps targeting Android T and below, the request
+ * will be ignored.
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
static final long THROW_FOR_INVALID_BROADCAST_RECEIVER = 270049379L;
/**
- * {@link MediaSession#setMediaButtonReceiver(PendingIntent)} throws an {@link
- * IllegalArgumentException} if the provided {@link PendingIntent} targets an {@link
- * android.app.Activity activity} for apps targeting Android V and above. For apps targeting
- * Android U and below, the request will be ignored.
+ * {@link android.media.session.MediaSession#setMediaButtonReceiver(android.app.PendingIntent)}
+ * throws an {@link java.lang.IllegalArgumentException} if the provided
+ * {@link android.app.PendingIntent} targets an {@link android.app.Activity activity} for
+ * apps targeting Android V and above. For apps targeting Android U and below, the request will
+ * be ignored.
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 803ab28..2c59511 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.os.UserHandle.ALL;
import static android.os.UserHandle.CURRENT;
+
import static com.android.server.media.MediaKeyDispatcher.KEY_EVENT_LONG_PRESS;
import static com.android.server.media.MediaKeyDispatcher.isDoubleTapOverridden;
import static com.android.server.media.MediaKeyDispatcher.isLongPressOverridden;
@@ -267,7 +268,14 @@
}
if (record.isSystemPriority()) {
if (DEBUG_KEY_EVENT) {
- Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
+ Log.d(
+ TAG,
+ "Global priority session updated - user id="
+ + record.getUserId()
+ + " package="
+ + record.getPackageName()
+ + " active="
+ + record.isActive());
}
user.pushAddressedPlayerChangedLocked();
} else {
@@ -1904,6 +1912,15 @@
keyEvent, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
return;
}
+ if (Flags.fallbackToDefaultHandlingWhenMediaSessionHasFixedVolumeHandling()
+ && !record.canHandleVolumeKey()) {
+ Log.d(TAG, "Session with packageName=" + record.getPackageName()
+ + " doesn't support volume adjustment."
+ + " Fallbacks to the default handling.");
+ dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid, true,
+ keyEvent, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
+ return;
+ }
switch (keyEvent.getAction()) {
case KeyEvent.ACTION_DOWN: {
int direction = 0;
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 6c9aa4b..67a1ccd 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -109,21 +109,28 @@
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- mBluetoothRouteController = BluetoothRouteController.createInstance(context, (routes) -> {
- publishProviderState();
- if (updateSessionInfosIfNeeded()) {
- notifySessionInfoUpdated();
- }
- });
+ mBluetoothRouteController =
+ BluetoothRouteController.createInstance(
+ context,
+ () -> {
+ publishProviderState();
+ if (updateSessionInfosIfNeeded()) {
+ notifySessionInfoUpdated();
+ }
+ });
- mDeviceRouteController = DeviceRouteController.createInstance(context, (deviceRoute) -> {
- mHandler.post(() -> {
- publishProviderState();
- if (updateSessionInfosIfNeeded()) {
- notifySessionInfoUpdated();
- }
- });
- });
+ mDeviceRouteController =
+ DeviceRouteController.createInstance(
+ context,
+ () -> {
+ mHandler.post(
+ () -> {
+ publishProviderState();
+ if (updateSessionInfosIfNeeded()) {
+ notifySessionInfoUpdated();
+ }
+ });
+ });
mAudioManager.addOnDevicesForAttributesChangedListener(
AudioAttributesUtils.ATTRIBUTES_MEDIA, mContext.getMainExecutor(),
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 13d1662..8cbc368 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -76,6 +76,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.Watchdog;
@@ -134,6 +135,7 @@
private final MediaRouter mMediaRouter;
private final MediaRouterCallback mMediaRouterCallback;
+ private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
private MediaRouter.RouteInfo mMediaRouteInfo;
@GuardedBy("mLock")
@@ -160,6 +162,7 @@
mWmInternal = LocalServices.getService(WindowManagerInternal.class);
mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mMediaRouterCallback = new MediaRouterCallback();
+ mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger();
Watchdog.getInstance().addMonitor(this);
}
@@ -193,6 +196,10 @@
Looper createCallbackLooper() {
return Looper.getMainLooper();
}
+
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger() {
+ return MediaProjectionMetricsLogger.getInstance();
+ }
}
@Override
@@ -372,6 +379,10 @@
if (mProjectionGrant != null) {
// Cache the session details.
mProjectionGrant.mSession = incomingSession;
+ mMediaProjectionMetricsLogger.notifyProjectionStateChange(
+ mProjectionGrant.uid,
+ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
+ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession);
}
return true;
@@ -818,6 +829,19 @@
}
@Override // Binder call
+ @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+ public void notifyPermissionRequestStateChange(int hostUid, int state,
+ int sessionCreationSource) {
+ notifyPermissionRequestStateChange_enforcePermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mMediaProjectionMetricsLogger.notifyProjectionStateChange(hostUid, state, sessionCreationSource);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
new file mode 100644
index 0000000..f18ecad
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
@@ -0,0 +1,50 @@
+/*
+ * 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.media.projection;
+
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * Class for emitting logs describing a MediaProjection session.
+ */
+public class MediaProjectionMetricsLogger {
+ private static MediaProjectionMetricsLogger sSingleton = null;
+
+ public static MediaProjectionMetricsLogger getInstance() {
+ if (sSingleton == null) {
+ sSingleton = new MediaProjectionMetricsLogger();
+ }
+ return sSingleton;
+ }
+
+ void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) {
+ write(hostUid, state, sessionCreationSource);
+ }
+
+ private void write(int hostUid, int state, int sessionCreationSource) {
+ FrameworkStatsLog.write(
+ /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED,
+ /* session_id */ 123,
+ /* state */ state,
+ /* previous_state */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN,
+ /* host_uid */ hostUid,
+ /* target_uid */ -1,
+ /* time_since_last_active */ 0,
+ /* creation_source */ sessionCreationSource);
+ }
+}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java
new file mode 100644
index 0000000..ff70cb3
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java
@@ -0,0 +1,91 @@
+/*
+ * 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.media.projection;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Environment;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+
+public class MediaProjectionSessionIdGenerator {
+
+ private static final String PREFERENCES_FILE_NAME = "media_projection_session_id";
+ private static final String SESSION_ID_PREF_KEY = "media_projection_session_id_key";
+ private static final int SESSION_ID_DEFAULT_VALUE = 0;
+
+ private static final Object sInstanceLock = new Object();
+
+ @GuardedBy("sInstanceLock")
+ private static MediaProjectionSessionIdGenerator sInstance;
+
+ private final Object mSessionIdLock = new Object();
+
+ @GuardedBy("mSessionIdLock")
+ private final SharedPreferences mSharedPreferences;
+
+ /** Creates or returns an existing instance of {@link MediaProjectionSessionIdGenerator}. */
+ public static MediaProjectionSessionIdGenerator getInstance(Context context) {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ File preferencesFile =
+ new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME);
+ SharedPreferences preferences =
+ context.getSharedPreferences(preferencesFile, Context.MODE_PRIVATE);
+ sInstance = new MediaProjectionSessionIdGenerator(preferences);
+ }
+ return sInstance;
+ }
+ }
+
+ @VisibleForTesting
+ public MediaProjectionSessionIdGenerator(SharedPreferences sharedPreferences) {
+ this.mSharedPreferences = sharedPreferences;
+ }
+
+ /** Returns the current session ID. This value is persisted across reboots. */
+ public int getCurrentSessionId() {
+ synchronized (mSessionIdLock) {
+ return getCurrentSessionIdInternal();
+ }
+ }
+
+ /**
+ * Creates and returns a new session ID. This value will be persisted as the new current session
+ * ID, and will be persisted across reboots.
+ */
+ public int createAndGetNewSessionId() {
+ synchronized (mSessionIdLock) {
+ int newSessionId = getCurrentSessionId() + 1;
+ setSessionIdInternal(newSessionId);
+ return newSessionId;
+ }
+ }
+
+ @GuardedBy("mSessionIdLock")
+ private void setSessionIdInternal(int value) {
+ mSharedPreferences.edit().putInt(SESSION_ID_PREF_KEY, value).apply();
+ }
+
+ @GuardedBy("mSessionIdLock")
+ private int getCurrentSessionIdInternal() {
+ return mSharedPreferences.getInt(SESSION_ID_PREF_KEY, SESSION_ID_DEFAULT_VALUE);
+ }
+}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java b/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java
new file mode 100644
index 0000000..4026d0c
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java
@@ -0,0 +1,109 @@
+/*
+ * 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.media.projection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Environment;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.InstantSource;
+
+/** Stores timestamps of media projection sessions. */
+public class MediaProjectionTimestampStore {
+ private static final String PREFERENCES_FILE_NAME = "media_projection_timestamp";
+ private static final String TIMESTAMP_PREF_KEY = "media_projection_timestamp_key";
+ private static final Object sInstanceLock = new Object();
+
+ @GuardedBy("sInstanceLock")
+ private static MediaProjectionTimestampStore sInstance;
+
+ private final Object mTimestampLock = new Object();
+
+ @GuardedBy("mTimestampLock")
+ private final SharedPreferences mSharedPreferences;
+
+ private final InstantSource mInstantSource;
+
+ @VisibleForTesting
+ public MediaProjectionTimestampStore(
+ SharedPreferences sharedPreferences, InstantSource instantSource) {
+ this.mSharedPreferences = sharedPreferences;
+ this.mInstantSource = instantSource;
+ }
+
+ /** Creates or returns an existing instance of {@link MediaProjectionTimestampStore}. */
+ public static MediaProjectionTimestampStore getInstance(Context context) {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ File preferencesFile =
+ new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME);
+ SharedPreferences preferences =
+ context.getSharedPreferences(preferencesFile, Context.MODE_PRIVATE);
+ sInstance = new MediaProjectionTimestampStore(preferences, InstantSource.system());
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * Returns the time that has passed since the last active session, or {@code null} if there was
+ * no last active session.
+ */
+ @Nullable
+ public Duration timeSinceLastActiveSession() {
+ synchronized (mTimestampLock) {
+ Instant lastActiveSessionTimestamp = getLastActiveSessionTimestamp();
+ if (lastActiveSessionTimestamp == null) {
+ return null;
+ }
+ Instant now = mInstantSource.instant();
+ return Duration.between(lastActiveSessionTimestamp, now);
+ }
+ }
+
+ /** Registers that the current active session ended now. */
+ public void registerActiveSessionEnded() {
+ synchronized (mTimestampLock) {
+ Instant now = mInstantSource.instant();
+ setLastActiveSessionTimestamp(now);
+ }
+ }
+
+ @GuardedBy("mTimestampLock")
+ @Nullable
+ private Instant getLastActiveSessionTimestamp() {
+ long lastActiveSessionEpochMilli =
+ mSharedPreferences.getLong(TIMESTAMP_PREF_KEY, /* defValue= */ -1);
+ if (lastActiveSessionEpochMilli == -1) {
+ return null;
+ }
+ return Instant.ofEpochMilli(lastActiveSessionEpochMilli);
+ }
+
+ @GuardedBy("mTimestampLock")
+ private void setLastActiveSessionTimestamp(@NonNull Instant timestamp) {
+ mSharedPreferences.edit().putLong(TIMESTAMP_PREF_KEY, timestamp.toEpochMilli()).apply();
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 837b761..b4d36db 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3500,8 +3500,19 @@
null /* options */);
record = getToastRecord(callingUid, callingPid, pkg, isSystemToast, token,
text, callback, duration, windowToken, displayId, textCallback);
- mToastQueue.add(record);
- index = mToastQueue.size() - 1;
+
+ // Insert system toasts at the front of the queue
+ int systemToastInsertIdx = mToastQueue.size();
+ if (isSystemToast) {
+ systemToastInsertIdx = getInsertIndexForSystemToastLocked();
+ }
+ if (systemToastInsertIdx < mToastQueue.size()) {
+ index = systemToastInsertIdx;
+ mToastQueue.add(index, record);
+ } else {
+ mToastQueue.add(record);
+ index = mToastQueue.size() - 1;
+ }
keepProcessAliveForToastIfNeededLocked(callingPid);
}
// If it's at index 0, it's the current toast. It doesn't matter if it's
@@ -3517,6 +3528,23 @@
}
}
+ @GuardedBy("mToastQueue")
+ private int getInsertIndexForSystemToastLocked() {
+ // If there are other system toasts: insert after the last one
+ int idx = 0;
+ for (ToastRecord r : mToastQueue) {
+ if (idx == 0 && mIsCurrentToastShown) {
+ idx++;
+ continue;
+ }
+ if (!r.isSystemToast) {
+ return idx;
+ }
+ idx++;
+ }
+ return idx;
+ }
+
private boolean checkCanEnqueueToast(String pkg, int callingUid, int displayId,
boolean isAppRenderedToast, boolean isSystemToast) {
final boolean isPackageSuspended = isPackagePaused(pkg);
diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java
index 4c6110b..d002688 100644
--- a/services/core/java/com/android/server/oemlock/OemLockService.java
+++ b/services/core/java/com/android/server/oemlock/OemLockService.java
@@ -35,8 +35,8 @@
import android.util.Slog;
import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.SystemService;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
import com.android.server.pm.UserRestrictionsUtils;
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index c7c8136..79c1346 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -569,6 +569,10 @@
@Override
public void onEvent(int event, @Nullable String path) {
+ if (path == null) {
+ Slog.w(TAG, "path is null at TombstoneWatcher.onEvent()");
+ return;
+ }
mHandler.post(() -> {
// Ignore .tmp files.
if (path.endsWith(".tmp")) {
diff --git a/services/core/java/com/android/server/pdb/OWNERS b/services/core/java/com/android/server/pdb/OWNERS
new file mode 100644
index 0000000..6f322ee
--- /dev/null
+++ b/services/core/java/com/android/server/pdb/OWNERS
@@ -0,0 +1,2 @@
+victorhsieh@google.com
+swillden@google.com
diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java
similarity index 98%
rename from services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
rename to services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java
index 21fa9f9..66ad716 100644
--- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.pdb;
/**
* Internal interface for storing and retrieving persistent data.
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
similarity index 99%
rename from services/core/java/com/android/server/PersistentDataBlockService.java
rename to services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index 754a7ed..b006ac8 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.pdb;
import static com.android.internal.util.Preconditions.checkArgument;
@@ -37,6 +37,9 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.SystemServerInitThreadPool;
+import com.android.server.SystemService;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 7db7bf5..30017be 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -505,6 +505,10 @@
int filterCallingUid, int userId, boolean resolveForStart,
boolean allowDynamicSplits) {
if (!mUserManager.exists(userId)) return Collections.emptyList();
+
+ // Allow to match activities of quarantined packages.
+ flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS;
+
final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */,
@@ -647,11 +651,6 @@
flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps,
false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
- // Only if the query is coming from the system process,
- // it should be allowed to match quarantined components
- if (callingUid != Process.SYSTEM_UID) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
- }
Intent originalIntent = null;
ComponentName comp = intent.getComponent();
if (comp == null) {
@@ -4047,9 +4046,6 @@
flags = updateFlagsForComponent(flags, userId);
enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
false /* checkShell */, "get provider info");
- if (callingUid != Process.SYSTEM_UID) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
- }
ParsedProvider p = mComponentResolver.getProvider(component);
if (DEBUG_PACKAGE_INFO) Log.v(
TAG, "getProviderInfo " + component + ": " + p);
@@ -4679,9 +4675,6 @@
int callingUid) {
if (!mUserManager.exists(userId)) return null;
flags = updateFlagsForComponent(flags, userId);
- if (callingUid != Process.SYSTEM_UID) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
- }
final ProviderInfo providerInfo = mComponentResolver.queryProvider(this, name, flags,
userId);
boolean checkedGrants = false;
@@ -4794,13 +4787,6 @@
false /* checkShell */, "queryContentProviders");
if (!mUserManager.exists(userId)) return ParceledListSlice.emptyList();
flags = updateFlagsForComponent(flags, userId);
-
- // Only if the service query is coming from the system process,
- // it should be allowed to match quarantined components
- if (callingUid != Process.SYSTEM_UID) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
- }
-
ArrayList<ProviderInfo> finalList = null;
final List<ProviderInfo> matchList = mComponentResolver.queryProviders(this, processName,
metaDataKey, uid, flags, userId);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index edb45aa..7556f27 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -113,6 +113,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ArchivedPackageParcel;
import android.content.pm.DataLoaderType;
+import android.content.pm.Flags;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
import android.content.pm.PackageInstaller;
@@ -161,6 +162,7 @@
import com.android.server.EventLogTags;
import com.android.server.LocalManagerRegistry;
import com.android.server.SystemConfig;
+import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
import com.android.server.pm.Installer.LegacyDexoptDisabledException;
@@ -1269,10 +1271,9 @@
replace = true;
if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing package: " + pkgName);
}
- final AndroidPackage oldPackage = mPm.mPackages.get(pkgName);
- if (replace && oldPackage != null) {
+ if (replace) {
// Prevent apps opting out from runtime permissions
- final int oldTargetSdk = oldPackage.getTargetSdkVersion();
+ final int oldTargetSdk = ps.getTargetSdkVersion();
final int newTargetSdk = parsedPackage.getTargetSdkVersion();
if (oldTargetSdk > Build.VERSION_CODES.LOLLIPOP_MR1
&& newTargetSdk <= Build.VERSION_CODES.LOLLIPOP_MR1) {
@@ -1284,10 +1285,10 @@
+ " target SDK " + oldTargetSdk + " does.");
}
// Prevent persistent apps from being updated
- if (oldPackage.isPersistent()
+ if (ps.isPersistent()
&& ((installFlags & PackageManager.INSTALL_STAGED) == 0)) {
throw new PrepareFailure(PackageManager.INSTALL_FAILED_INVALID_APK,
- "Package " + oldPackage.getPackageName() + " is a persistent app. "
+ "Package " + pkgName + " is a persistent app. "
+ "Persistent apps are not updateable.");
}
}
@@ -1668,7 +1669,7 @@
}
// don't allow a system upgrade unless the upgrade hash matches
- if (oldPackage != null && oldPackage.getRestrictUpdateHash() != null
+ if (oldPackageState.getRestrictUpdateHash() != null
&& oldPackageState.isSystem()) {
final byte[] digestBytes;
try {
@@ -1684,12 +1685,13 @@
throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
"Could not compute hash: " + pkgName11);
}
- if (!Arrays.equals(oldPackage.getRestrictUpdateHash(), digestBytes)) {
+ if (!Arrays.equals(oldPackageState.getRestrictUpdateHash(), digestBytes)) {
throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
"New package fails restrict-update check: " + pkgName11);
}
// retain upgrade restriction
- parsedPackage.setRestrictUpdateHash(oldPackage.getRestrictUpdateHash());
+ parsedPackage.setRestrictUpdateHash(
+ oldPackageState.getRestrictUpdateHash());
}
if (oldPackage != null) {
@@ -2562,8 +2564,15 @@
LocalManagerRegistry.getManager(PackageManagerLocal.class);
try (PackageManagerLocal.FilteredSnapshot snapshot =
packageManagerLocal.withFilteredSnapshot()) {
- DexoptParams params =
- dexoptOptions.convertToDexoptParams(0 /* extraFlags */);
+ boolean ignoreDexoptProfile =
+ (installRequest.getInstallFlags()
+ & PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE)
+ != 0;
+ /*@DexoptFlags*/ int extraFlags =
+ ignoreDexoptProfile && Flags.useArtServiceV2()
+ ? ArtFlags.FLAG_IGNORE_PROFILE
+ : 0;
+ DexoptParams params = dexoptOptions.convertToDexoptParams(extraFlags);
DexoptResult dexOptResult = DexOptHelper.getArtManagerLocal().dexoptPackage(
snapshot, packageName, params);
installRequest.onDexoptFinished(dexOptResult);
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index 148e0df..9ad8318 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -89,6 +89,21 @@
if (packageState == null || packageState.getPkg() == null) {
throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package");
}
+ final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState,
+ mPm.mUserManager.getUserIds(), true);
+ final UserHandle userForMove;
+ if (installedUserIds.length > 0) {
+ userForMove = UserHandle.of(installedUserIds[0]);
+ } else {
+ throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST,
+ "Package is not installed for any user");
+ }
+ for (int userId : installedUserIds) {
+ if (snapshot.shouldFilterApplicationIncludingUninstalled(packageState, callingUid,
+ userId)) {
+ throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package");
+ }
+ }
final AndroidPackage pkg = packageState.getPkg();
if (packageState.isSystem()) {
throw new PackageManagerException(MOVE_FAILED_SYSTEM_PACKAGE,
@@ -134,8 +149,6 @@
final String label = String.valueOf(pm.getApplicationLabel(
AndroidPackageUtils.generateAppInfoWithoutState(pkg)));
final int targetSdkVersion = pkg.getTargetSdkVersion();
- final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState,
- mPm.mUserManager.getUserIds(), true);
final String fromCodePath;
if (codeFile.getParentFile().getName().startsWith(
PackageManagerService.RANDOM_DIR_PREFIX)) {
@@ -303,8 +316,8 @@
final PackageLite lite = ret.isSuccess() ? ret.getResult() : null;
final InstallingSession installingSession = new InstallingSession(origin, move,
installObserver, installFlags, /* developmentInstallFlags= */ 0, installSource,
- volumeUuid, user, packageAbiOverride, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED,
- lite, mPm);
+ volumeUuid, userForMove, packageAbiOverride,
+ PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, lite, mPm);
installingSession.movePackage();
}
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index f59188e..0fb1f7a 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -196,8 +196,12 @@
for (int i = 0, size = mainActivities.length; i < size; ++i) {
var mainActivity = mainActivities[i];
Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i);
- ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
- mainActivity.title, iconPath, null);
+ ArchiveActivityInfo activityInfo =
+ new ArchiveActivityInfo(
+ mainActivity.title,
+ mainActivity.originalComponentName,
+ iconPath,
+ null);
archiveActivityInfos.add(activityInfo);
}
@@ -215,8 +219,12 @@
for (int i = 0, size = mainActivities.size(); i < size; i++) {
LauncherActivityInfo mainActivity = mainActivities.get(i);
Path iconPath = storeIcon(packageName, mainActivity, userId, i);
- ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
- mainActivity.getLabel().toString(), iconPath, null);
+ ArchiveActivityInfo activityInfo =
+ new ArchiveActivityInfo(
+ mainActivity.getLabel().toString(),
+ mainActivity.getComponentName(),
+ iconPath,
+ null);
archiveActivityInfos.add(activityInfo);
}
@@ -593,6 +601,7 @@
}
var archivedActivity = new ArchivedActivityParcel();
archivedActivity.title = info.getTitle();
+ archivedActivity.originalComponentName = info.getOriginalComponentName();
archivedActivity.iconBitmap = bytesFromBitmapFile(info.getIconBitmap());
archivedActivity.monochromeIconBitmap = bytesFromBitmapFile(
info.getMonochromeIconBitmap());
@@ -624,6 +633,7 @@
}
var archivedActivity = new ArchivedActivityParcel();
archivedActivity.title = info.getLabel().toString();
+ archivedActivity.originalComponentName = info.getComponentName();
archivedActivity.iconBitmap =
info.getActivityInfo().getIconResource() == 0 ? null : bytesFromBitmap(
drawableToBitmap(info.getIcon(/* density= */ 0)));
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 651845e..e749968 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -747,7 +747,7 @@
@Override
public void notifyComponentUsed(@NonNull String packageName, @UserIdInt int userId,
- @NonNull String recentCallingPackage, @NonNull String debugInfo) {
+ @Nullable String recentCallingPackage, @NonNull String debugInfo) {
mService.notifyComponentUsed(snapshot(), packageName, userId,
recentCallingPackage, debugInfo);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 68aa93d..61b6b24 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -36,6 +36,7 @@
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
+import static android.util.FeatureFlagUtils.SETTINGS_TREAT_PAUSE_AS_QUARANTINE;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME;
@@ -164,6 +165,7 @@
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.ExceptionUtils;
+import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -1469,8 +1471,7 @@
archPkg.versionCodeMajor = (int) (longVersionCode >> 32);
archPkg.versionCode = (int) longVersionCode;
- // TODO(b/297916136): extract target sdk version.
- archPkg.targetSdkVersion = MIN_INSTALLABLE_TARGET_SDK;
+ archPkg.targetSdkVersion = ps.getTargetSdkVersion();
// These get translated in flags important for user data management.
archPkg.defaultToDeviceProtectedStorage = String.valueOf(
@@ -4565,6 +4566,7 @@
final Bundle extras = new Bundle();
extras.putInt(Intent.EXTRA_UID, pmi.getPackageUid(packageName, 0, userId));
extras.putInt(Intent.EXTRA_USER_HANDLE, userId);
+ extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime());
mHandler.post(() -> {
mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_UNSTOPPED,
packageName, extras,
@@ -4577,7 +4579,7 @@
}
void notifyComponentUsed(@NonNull Computer snapshot, @NonNull String packageName,
- @UserIdInt int userId, @NonNull String recentCallingPackage,
+ @UserIdInt int userId, @Nullable String recentCallingPackage,
@NonNull String debugInfo) {
synchronized (mLock) {
final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
@@ -6134,8 +6136,16 @@
final Computer snapshot = snapshotComputer();
enforceCanSetPackagesSuspendedAsUser(snapshot, callingPackage, callingUid, userId,
"setPackagesSuspendedAsUser");
- boolean quarantined = ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0)
- && Flags.quarantinedEnabled();
+ boolean quarantined = false;
+ if (Flags.quarantinedEnabled()) {
+ if ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0) {
+ quarantined = true;
+ } else if (FeatureFlagUtils.isEnabled(mContext,
+ SETTINGS_TREAT_PAUSE_AS_QUARANTINE)) {
+ final String wellbeingPkg = mContext.getString(R.string.config_systemWellbeing);
+ quarantined = callingPackage.equals(wellbeingPkg);
+ }
+ }
return mSuspendPackageHelper.setPackagesSuspended(snapshot, packageNames, suspended,
appExtras, launcherExtras, dialogInfo, callingPackage, userId, callingUid,
false /* forQuietMode */, quarantined);
@@ -6960,6 +6970,7 @@
final Bundle extras = new Bundle();
extras.putInt(Intent.EXTRA_UID, uid);
extras.putInt(Intent.EXTRA_USER_HANDLE, userId);
+ extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime());
mHandler.post(() -> {
mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_RESTARTED,
packageName, extras,
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 7264e2e..d4abad8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3703,6 +3703,9 @@
sessionParams.installFlags |=
PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK;
break;
+ case "--ignore-dexopt-profile":
+ sessionParams.installFlags |= PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE;
+ break;
default:
throw new IllegalArgumentException("Unknown option " + opt);
}
@@ -4799,7 +4802,7 @@
pw.println(" [--enable-rollback]");
pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
pw.println(" [--apex] [--non-staged] [--force-non-staged]");
- pw.println(" [--staged-ready-timeout TIMEOUT]");
+ pw.println(" [--staged-ready-timeout TIMEOUT] [--ignore-dexopt-profile]");
pw.println(" [PATH [SPLIT...]|-]");
pw.println(" Install an application. Must provide the apk data to install, either as");
pw.println(" file path(s) or '-' to read from stdin. Options are:");
@@ -4839,6 +4842,13 @@
pw.println(" milliseconds for pre-reboot verification to complete when");
pw.println(" performing staged install. This flag is used to alter the waiting");
pw.println(" time. You can skip the waiting time by specifying a TIMEOUT of '0'");
+ pw.println(" --ignore-dexopt-profile: If set, all profiles are ignored by dexopt");
+ pw.println(" during the installation, including the profile in the DM file and");
+ pw.println(" the profile embedded in the APK file. If an invalid profile is");
+ pw.println(" provided during installation, no warning will be reported by `adb");
+ pw.println(" install`.");
+ pw.println(" This option does not affect later dexopt operations (e.g.,");
+ pw.println(" background dexopt and manual `pm compile` invocations).");
pw.println("");
pw.println(" install-existing [--user USER_ID|all|current]");
pw.println(" [--instant] [--full] [--wait] [--restrict-permissions] PACKAGE");
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 2e60064..42f4cfb 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -99,6 +99,7 @@
private static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE = 1 << 1;
private static final int UPDATE_AVAILABLE = 1 << 2;
private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 3;
+ private static final int PERSISTENT = 1 << 4;
}
private int mBooleans;
@@ -217,6 +218,11 @@
@Nullable
private String mAppMetadataFilePath;
+ private int mTargetSdkVersion;
+
+ @Nullable
+ private byte[] mRestrictUpdateHash;
+
/**
* Snapshot support.
*/
@@ -232,34 +238,16 @@
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public PackageSetting(String name, String realName, @NonNull File path,
- String legacyNativeLibraryPath, String primaryCpuAbi,
- String secondaryCpuAbi, String cpuAbiOverride,
- long longVersionCode, int pkgFlags, int pkgPrivateFlags,
- int sharedUserAppId,
- String[] usesSdkLibraries, long[] usesSdkLibrariesVersionsMajor,
- String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
- Map<String, Set<String>> mimeGroups,
- @NonNull UUID domainSetId) {
+ public PackageSetting(@NonNull String name, @Nullable String realName, @NonNull File path,
+ int pkgFlags, int pkgPrivateFlags, @NonNull UUID domainSetId) {
super(pkgFlags, pkgPrivateFlags);
this.mName = name;
this.mRealName = realName;
- this.usesSdkLibraries = usesSdkLibraries;
- this.usesSdkLibrariesVersionsMajor = usesSdkLibrariesVersionsMajor;
- this.usesStaticLibraries = usesStaticLibraries;
- this.usesStaticLibrariesVersions = usesStaticLibrariesVersions;
this.mPath = path;
this.mPathString = path.toString();
- this.legacyNativeLibraryPath = legacyNativeLibraryPath;
- this.mPrimaryCpuAbi = primaryCpuAbi;
- this.mSecondaryCpuAbi = secondaryCpuAbi;
- this.mCpuAbiOverride = cpuAbiOverride;
- this.versionCode = longVersionCode;
this.signatures = new PackageSignatures();
this.installSource = InstallSource.EMPTY;
- this.mSharedUserAppId = sharedUserAppId;
- mDomainSetId = domainSetId;
- copyMimeGroups(mimeGroups);
+ this.mDomainSetId = domainSetId;
mSnapshot = makeCache();
}
@@ -530,9 +518,28 @@
return this;
}
- public void setSharedUserAppId(int sharedUserAppId) {
+ public PackageSetting setSharedUserAppId(int sharedUserAppId) {
mSharedUserAppId = sharedUserAppId;
onChanged();
+ return this;
+ }
+
+ public PackageSetting setIsPersistent(boolean isPersistent) {
+ setBoolean(Booleans.PERSISTENT, isPersistent);
+ onChanged();
+ return this;
+ }
+
+ public PackageSetting setTargetSdkVersion(int targetSdkVersion) {
+ mTargetSdkVersion = targetSdkVersion;
+ onChanged();
+ return this;
+ }
+
+ public PackageSetting setRestrictUpdateHash(byte[] restrictUpdateHash) {
+ mRestrictUpdateHash = restrictUpdateHash;
+ onChanged();
+ return this;
}
@Override
@@ -552,7 +559,7 @@
+ " " + mName + "/" + mAppId + "}";
}
- protected void copyMimeGroups(@Nullable Map<String, Set<String>> newMimeGroups) {
+ private void copyMimeGroups(@Nullable Map<String, Set<String>> newMimeGroups) {
if (newMimeGroups == null) {
mimeGroups = null;
return;
@@ -701,6 +708,9 @@
categoryOverride = other.categoryOverride;
mDomainSetId = other.mDomainSetId;
mAppMetadataFilePath = other.mAppMetadataFilePath;
+ mTargetSdkVersion = other.mTargetSdkVersion;
+ mRestrictUpdateHash = other.mRestrictUpdateHash == null
+ ? null : other.mRestrictUpdateHash.clone();
usesSdkLibraries = other.usesSdkLibraries != null
? Arrays.copyOf(other.usesSdkLibraries,
@@ -1201,6 +1211,9 @@
long activityInfoToken = proto.start(
PackageProto.UserInfoProto.ArchiveState.ACTIVITY_INFOS);
proto.write(ArchiveActivityInfo.TITLE, activityInfo.getTitle());
+ proto.write(
+ ArchiveActivityInfo.ORIGINAL_COMPONENT_NAME,
+ activityInfo.getOriginalComponentName().flattenToString());
if (activityInfo.getIconBitmap() != null) {
proto.write(ArchiveActivityInfo.ICON_BITMAP_PATH,
activityInfo.getIconBitmap().toAbsolutePath().toString());
@@ -1220,7 +1233,8 @@
/**
* @see #mPath
*/
- PackageSetting setPath(@NonNull File path) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public PackageSetting setPath(@NonNull File path) {
this.mPath = path;
this.mPathString = path.toString();
onChanged();
@@ -1421,9 +1435,11 @@
return this;
}
- public PackageSetting setMimeGroups(@NonNull Map<String, Set<String>> mimeGroups) {
- this.mimeGroups = mimeGroups;
- onChanged();
+ public PackageSetting setMimeGroups(@Nullable Map<String, Set<String>> mimeGroups) {
+ if (mimeGroups != null) {
+ copyMimeGroups(mimeGroups);
+ onChanged();
+ }
return this;
}
@@ -1572,6 +1588,11 @@
return getBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE);
}
+ @Override
+ public boolean isPersistent() {
+ return getBoolean(Booleans.PERSISTENT);
+ }
+
// Code below generated by codegen v1.0.23.
@@ -1714,11 +1735,21 @@
return mAppMetadataFilePath;
}
+ @DataClass.Generated.Member
+ public int getTargetSdkVersion() {
+ return mTargetSdkVersion;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable byte[] getRestrictUpdateHash() {
+ return mRestrictUpdateHash;
+ }
+
@DataClass.Generated(
- time = 1694196905013L,
+ time = 1696979728639L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
- inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+ inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic com.android.server.pm.PackageSetting setIsPersistent(boolean)\npublic com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic boolean isRequestLegacyExternalStorage()\npublic boolean isUserDataFragile()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n int[] queryUsersInstalledOrHasData(int[])\n long getCeDataInode(int)\n long getDeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int PERSISTENT\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index da14397..203e1de 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -517,12 +517,6 @@
if (!mUserManager.exists(userId)) return Collections.emptyList();
final int callingUid = Binder.getCallingUid();
- // Only if the service query is coming from the system process,
- // it should be allowed to match quarantined components
- if (callingUid != Process.SYSTEM_UID) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
- }
-
final String instantAppPkgName = computer.getInstantAppPackageName(callingUid);
flags = computer.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 0cac790..8d8acfd4 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -220,7 +220,8 @@
UserManagerService.getInstance(), usesSdkLibraries,
parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
- newDomainSetId);
+ newDomainSetId, parsedPackage.isPersistent(),
+ parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
} else {
// make a deep copy to avoid modifying any existing system state.
pkgSetting = new PackageSetting(pkgSetting);
@@ -240,7 +241,8 @@
UserManagerService.getInstance(),
usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
- parsedPackage.getMimeGroups(), newDomainSetId);
+ parsedPackage.getMimeGroups(), newDomainSetId, parsedPackage.isPersistent(),
+ parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
}
if (createNewPackage && originalPkgSetting != null) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 6e3b538..a39178e 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -368,6 +368,7 @@
private static final String ATTR_VALUE = "value";
private static final String ATTR_FIRST_INSTALL_TIME = "first-install-time";
private static final String ATTR_ARCHIVE_ACTIVITY_TITLE = "activity-title";
+ private static final String ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME = "original-component-name";
private static final String ATTR_ARCHIVE_INSTALLER_TITLE = "installer-title";
private static final String ATTR_ARCHIVE_ICON_PATH = "icon-path";
private static final String ATTR_ARCHIVE_MONOCHROME_ICON_PATH = "monochrome-icon-path";
@@ -930,16 +931,24 @@
sharedUserSetting.mDisabledPackages.remove(p);
}
p.getPkgState().setUpdatedSystemApp(false);
- PackageSetting ret = addPackageLPw(name, p.getRealName(), p.getPath(),
- p.getLegacyNativeLibraryPath(), p.getPrimaryCpuAbiLegacy(),
- p.getSecondaryCpuAbiLegacy(), p.getCpuAbiOverride(),
- p.getAppId(), p.getVersionCode(), p.getFlags(), p.getPrivateFlags(),
- p.getUsesSdkLibraries(), p.getUsesSdkLibrariesVersionsMajor(),
- p.getUsesStaticLibraries(), p.getUsesStaticLibrariesVersions(), p.getMimeGroups(),
- mDomainVerificationManager.generateNewId());
+ PackageSetting ret = addPackageLPw(name, p.getRealName(), p.getPath(), p.getAppId(),
+ p.getFlags(), p.getPrivateFlags(), mDomainVerificationManager.generateNewId());
if (ret != null) {
+ ret.setLegacyNativeLibraryPath(p.getLegacyNativeLibraryPath());
+ ret.setPrimaryCpuAbi(p.getPrimaryCpuAbiLegacy());
+ ret.setSecondaryCpuAbi(p.getSecondaryCpuAbiLegacy());
+ ret.setCpuAbiOverride(p.getCpuAbiOverride());
+ ret.setLongVersionCode(p.getVersionCode());
+ ret.setUsesSdkLibraries(p.getUsesSdkLibraries());
+ ret.setUsesSdkLibrariesVersionsMajor(p.getUsesSdkLibrariesVersionsMajor());
+ ret.setUsesStaticLibraries(p.getUsesStaticLibraries());
+ ret.setUsesStaticLibrariesVersions(p.getUsesStaticLibrariesVersions());
+ ret.setMimeGroups(p.getMimeGroups());
ret.setAppMetadataFilePath(p.getAppMetadataFilePath());
ret.getPkgState().setUpdatedSystemApp(false);
+ ret.setIsPersistent(p.isPersistent());
+ ret.setTargetSdkVersion(p.getTargetSdkVersion());
+ ret.setRestrictUpdateHash(p.getRestrictUpdateHash());
}
mDisabledSysPackages.remove(name);
return ret;
@@ -960,13 +969,8 @@
}
}
- PackageSetting addPackageLPw(String name, String realName, File codePath,
- String legacyNativeLibraryPathString, String primaryCpuAbiString,
- String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, long vc,
- int pkgFlags, int pkgPrivateFlags, String[] usesSdkLibraries,
- long[] usesSdkLibrariesVersions, String[] usesStaticLibraries,
- long[] usesStaticLibrariesVersions, Map<String, Set<String>> mimeGroups,
- @NonNull UUID domainSetId) {
+ PackageSetting addPackageLPw(String name, String realName, File codePath, int uid, int pkgFlags,
+ int pkgPrivateFlags, @NonNull UUID domainSetId) {
PackageSetting p = mPackages.get(name);
if (p != null) {
if (p.getAppId() == uid) {
@@ -976,11 +980,8 @@
"Adding duplicate package, keeping first: " + name);
return null;
}
- p = new PackageSetting(name, realName, codePath, legacyNativeLibraryPathString,
- primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, vc, pkgFlags,
- pkgPrivateFlags, 0 /*userId*/, usesSdkLibraries, usesSdkLibrariesVersions,
- usesStaticLibraries, usesStaticLibrariesVersions, mimeGroups, domainSetId);
- p.setAppId(uid);
+ p = new PackageSetting(name, realName, codePath, pkgFlags, pkgPrivateFlags, domainSetId)
+ .setAppId(uid);
if (mAppIds.registerExistingAppId(uid, p, name)) {
mPackages.put(name, p);
return p;
@@ -1060,7 +1061,8 @@
boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager,
String[] usesSdkLibraries, long[] usesSdkLibrariesVersions,
String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
- Set<String> mimeGroupNames, @NonNull UUID domainSetId) {
+ Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent,
+ int targetSdkVersion, byte[] restrictUpdatedHash) {
final PackageSetting pkgSetting;
if (originalPkg != null) {
if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
@@ -1080,20 +1082,31 @@
.setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)
// Update new package state.
.setLastModifiedTime(codePath.lastModified())
- .setDomainSetId(domainSetId);
+ .setDomainSetId(domainSetId)
+ .setIsPersistent(isPersistent)
+ .setTargetSdkVersion(targetSdkVersion)
+ .setRestrictUpdateHash(restrictUpdatedHash);
pkgSetting.setFlags(pkgFlags)
.setPrivateFlags(pkgPrivateFlags);
} else {
int installUserId = installUser != null ? installUser.getIdentifier()
: UserHandle.USER_SYSTEM;
- pkgSetting = new PackageSetting(pkgName, realPkgName, codePath,
- legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
- null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
- 0 /*sharedUserAppId*/, usesSdkLibraries, usesSdkLibrariesVersions,
- usesStaticLibraries, usesStaticLibrariesVersions,
- createMimeGroups(mimeGroupNames), domainSetId);
- pkgSetting.setLastModifiedTime(codePath.lastModified());
+ pkgSetting = new PackageSetting(pkgName, realPkgName, codePath, pkgFlags,
+ pkgPrivateFlags, domainSetId)
+ .setUsesSdkLibraries(usesSdkLibraries)
+ .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions)
+ .setUsesStaticLibraries(usesStaticLibraries)
+ .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)
+ .setLegacyNativeLibraryPath(legacyNativeLibraryPath)
+ .setPrimaryCpuAbi(primaryCpuAbi)
+ .setSecondaryCpuAbi(secondaryCpuAbi)
+ .setLongVersionCode(versionCode)
+ .setMimeGroups(createMimeGroups(mimeGroupNames))
+ .setIsPersistent(isPersistent)
+ .setTargetSdkVersion(targetSdkVersion)
+ .setRestrictUpdateHash(restrictUpdatedHash)
+ .setLastModifiedTime(codePath.lastModified());
if (sharedUser != null) {
pkgSetting.setSharedUserAppId(sharedUser.mAppId);
}
@@ -1206,7 +1219,8 @@
int pkgPrivateFlags, @NonNull UserManagerService userManager,
@Nullable String[] usesSdkLibraries, @Nullable long[] usesSdkLibrariesVersions,
@Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
- @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId)
+ @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent,
+ int targetSdkVersion, byte[] restrictUpdatedHash)
throws PackageManagerException {
final String pkgName = pkgSetting.getPackageName();
if (sharedUser != null) {
@@ -1258,7 +1272,10 @@
pkgSetting.setPrimaryCpuAbi(primaryCpuAbi)
.setSecondaryCpuAbi(secondaryCpuAbi)
.updateMimeGroups(mimeGroupNames)
- .setDomainSetId(domainSetId);
+ .setDomainSetId(domainSetId)
+ .setIsPersistent(isPersistent)
+ .setTargetSdkVersion(targetSdkVersion)
+ .setRestrictUpdateHash(restrictUpdatedHash);
// Update SDK library dependencies if needed.
if (usesSdkLibraries != null && usesSdkLibrariesVersions != null
&& usesSdkLibraries.length == usesSdkLibrariesVersions.length) {
@@ -2068,6 +2085,8 @@
if (tagName.equals(TAG_ARCHIVE_ACTIVITY_INFO)) {
String title = parser.getAttributeValue(null,
ATTR_ARCHIVE_ACTIVITY_TITLE);
+ String originalComponentName =
+ parser.getAttributeValue(null, ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME);
String iconAttribute = parser.getAttributeValue(null,
ATTR_ARCHIVE_ICON_PATH);
Path iconPath = iconAttribute == null ? null : Path.of(iconAttribute);
@@ -2076,17 +2095,27 @@
Path monochromeIconPath = monochromeAttribute == null ? null : Path.of(
monochromeAttribute);
- if (title == null || iconPath == null) {
- Slog.wtf(TAG,
- TextUtils.formatSimple("Missing attributes in tag %s. %s: %s, %s: %s",
- TAG_ARCHIVE_ACTIVITY_INFO, ATTR_ARCHIVE_ACTIVITY_TITLE, title,
+ if (title == null || originalComponentName == null || iconPath == null) {
+ Slog.wtf(
+ TAG,
+ TextUtils.formatSimple(
+ "Missing attributes in tag %s. %s: %s, %s: %s, %s: %s",
+ TAG_ARCHIVE_ACTIVITY_INFO,
+ ATTR_ARCHIVE_ACTIVITY_TITLE,
+ title,
+ ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME,
+ originalComponentName,
ATTR_ARCHIVE_ICON_PATH,
iconPath));
continue;
}
activityInfos.add(
- new ArchiveState.ArchiveActivityInfo(title, iconPath, monochromeIconPath));
+ new ArchiveState.ArchiveActivityInfo(
+ title,
+ ComponentName.unflattenFromString(originalComponentName),
+ iconPath,
+ monochromeIconPath));
}
}
return activityInfos;
@@ -2458,6 +2487,10 @@
for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) {
serializer.startTag(null, TAG_ARCHIVE_ACTIVITY_INFO);
serializer.attribute(null, ATTR_ARCHIVE_ACTIVITY_TITLE, activityInfo.getTitle());
+ serializer.attribute(
+ null,
+ ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME,
+ activityInfo.getOriginalComponentName().flattenToString());
if (activityInfo.getIconBitmap() != null) {
serializer.attribute(null, ATTR_ARCHIVE_ICON_PATH,
activityInfo.getIconBitmap().toAbsolutePath().toString());
@@ -3038,6 +3071,12 @@
serializer.attributeLongHex(null, "ft", pkg.getLastModifiedTime());
serializer.attributeLongHex(null, "ut", pkg.getLastUpdateTime());
serializer.attributeLong(null, "version", pkg.getVersionCode());
+ serializer.attributeBoolean(null, "isPersistent", pkg.isPersistent());
+ serializer.attributeInt(null, "targetSdkVersion", pkg.getTargetSdkVersion());
+ if (pkg.getRestrictUpdateHash() != null) {
+ serializer.attributeBytesBase64(null, "restrictUpdateHash",
+ pkg.getRestrictUpdateHash());
+ }
if (pkg.getLegacyNativeLibraryPath() != null) {
serializer.attribute(null, "nativeLibraryPath", pkg.getLegacyNativeLibraryPath());
}
@@ -3101,6 +3140,12 @@
serializer.attributeLongHex(null, "ft", pkg.getLastModifiedTime());
serializer.attributeLongHex(null, "ut", pkg.getLastUpdateTime());
serializer.attributeLong(null, "version", pkg.getVersionCode());
+ serializer.attributeBoolean(null, "isPersistent", pkg.isPersistent());
+ serializer.attributeInt(null, "targetSdkVersion", pkg.getTargetSdkVersion());
+ if (pkg.getRestrictUpdateHash() != null) {
+ serializer.attributeBytesBase64(null, "restrictUpdateHash",
+ pkg.getRestrictUpdateHash());
+ }
if (!pkg.hasSharedUser()) {
serializer.attributeInt(null, "userId", pkg.getAppId());
} else {
@@ -3833,6 +3878,10 @@
}
long versionCode = parser.getAttributeLong(null, "version", 0);
+ boolean isPersistent = parser.getAttributeBoolean(null, "isPersistent", false);
+ int targetSdkVersion = parser.getAttributeInt(null, "targetSdkVersion", 0);
+ byte[] restrictUpdateHash = parser.getAttributeBytesBase64(null, "restrictUpdateHash",
+ null);
int pkgFlags = 0;
int pkgPrivateFlags = 0;
@@ -3845,10 +3894,16 @@
// debug invalid entries. The actual logic for migrating to a new ID is done in other
// methods that use DomainVerificationManagerInternal#generateNewId
UUID domainSetId = DomainVerificationManagerInternal.DISABLED_ID;
- PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
- legacyNativeLibraryPathStr, primaryCpuAbiStr, secondaryCpuAbiStr, cpuAbiOverrideStr,
- versionCode, pkgFlags, pkgPrivateFlags, 0 /*sharedUserAppId*/, null, null, null,
- null, null, domainSetId);
+ PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr), pkgFlags,
+ pkgPrivateFlags, domainSetId)
+ .setLegacyNativeLibraryPath(legacyNativeLibraryPathStr)
+ .setPrimaryCpuAbi(primaryCpuAbiStr)
+ .setSecondaryCpuAbi(secondaryCpuAbiStr)
+ .setCpuAbiOverride(cpuAbiOverrideStr)
+ .setLongVersionCode(versionCode)
+ .setIsPersistent(isPersistent)
+ .setTargetSdkVersion(targetSdkVersion)
+ .setRestrictUpdateHash(restrictUpdateHash);
long timeStamp = parser.getAttributeLongHex(null, "ft", 0);
if (timeStamp == 0) {
timeStamp = parser.getAttributeLong(null, "ts", 0);
@@ -3942,6 +3997,9 @@
long loadingCompletedTime = 0;
UUID domainSetId;
String appMetadataFilePath = null;
+ boolean isPersistent = false;
+ int targetSdkVersion = 0;
+ byte[] restrictUpdateHash = null;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
realName = parser.getAttributeValue(null, "realName");
@@ -3965,6 +4023,9 @@
}
versionCode = parser.getAttributeLong(null, "version", 0);
+ isPersistent = parser.getAttributeBoolean(null, "isPersistent", false);
+ targetSdkVersion = parser.getAttributeInt(null, "targetSdkVersion", 0);
+ restrictUpdateHash = parser.getAttributeBytesBase64(null, "restrictUpdateHash", null);
installerPackageName = parser.getAttributeValue(null, "installer");
installerPackageUid = parser.getAttributeInt(null, "installerUid", INVALID_UID);
updateOwnerPackageName = parser.getAttributeValue(null, "updateOwner");
@@ -4060,11 +4121,7 @@
+ parser.getPositionDescription());
} else if (appId > 0) {
packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
- legacyNativeLibraryPathStr, primaryCpuAbiString, secondaryCpuAbiString,
- cpuAbiOverrideString, appId, versionCode, pkgFlags, pkgPrivateFlags,
- null /* usesSdkLibraries */, null /* usesSdkLibraryVersions */,
- null /* usesStaticLibraries */, null /* usesStaticLibraryVersions */,
- null /* mimeGroups */, domainSetId);
+ appId, pkgFlags, pkgPrivateFlags, domainSetId);
if (PackageManagerService.DEBUG_SETTINGS)
Log.i(PackageManagerService.TAG, "Reading package " + name + ": appId="
+ appId + " pkg=" + packageSetting);
@@ -4073,22 +4130,26 @@
+ appId + " while parsing settings at "
+ parser.getPositionDescription());
} else {
+ packageSetting.setLegacyNativeLibraryPath(legacyNativeLibraryPathStr);
+ packageSetting.setPrimaryCpuAbi(primaryCpuAbiString);
+ packageSetting.setSecondaryCpuAbi(secondaryCpuAbiString);
+ packageSetting.setCpuAbiOverride(cpuAbiOverrideString);
+ packageSetting.setLongVersionCode(versionCode);
packageSetting.setLastModifiedTime(timeStamp);
packageSetting.setLastUpdateTime(lastUpdateTime);
}
} else if (sharedUserAppId != 0) {
if (sharedUserAppId > 0) {
packageSetting = new PackageSetting(name.intern(), realName,
- new File(codePathStr), legacyNativeLibraryPathStr,
- primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
- versionCode, pkgFlags, pkgPrivateFlags, sharedUserAppId,
- null /* usesSdkLibraries */,
- null /* usesSdkLibrariesVersions */,
- null /* usesStaticLibraries */,
- null /* usesStaticLibraryVersions */,
- null /* mimeGroups */, domainSetId);
- packageSetting.setLastModifiedTime(timeStamp);
- packageSetting.setLastUpdateTime(lastUpdateTime);
+ new File(codePathStr), pkgFlags, pkgPrivateFlags, domainSetId)
+ .setLegacyNativeLibraryPath(legacyNativeLibraryPathStr)
+ .setPrimaryCpuAbi(primaryCpuAbiString)
+ .setSecondaryCpuAbi(secondaryCpuAbiString)
+ .setCpuAbiOverride(cpuAbiOverrideString)
+ .setLongVersionCode(versionCode)
+ .setSharedUserAppId(sharedUserAppId)
+ .setLastModifiedTime(timeStamp)
+ .setLastUpdateTime(lastUpdateTime);
mPendingPackages.add(packageSetting);
if (PackageManagerService.DEBUG_SETTINGS)
Log.i(PackageManagerService.TAG, "Reading package " + name
@@ -4127,7 +4188,10 @@
.setForceQueryableOverride(installedForceQueryable)
.setLoadingProgress(loadingProgress)
.setLoadingCompletedTime(loadingCompletedTime)
- .setAppMetadataFilePath(appMetadataFilePath);
+ .setAppMetadataFilePath(appMetadataFilePath)
+ .setIsPersistent(isPersistent)
+ .setTargetSdkVersion(targetSdkVersion)
+ .setRestrictUpdateHash(restrictUpdateHash);
// Handle legacy string here for single-user mode
final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
if (enabledStr != null) {
@@ -4888,9 +4952,11 @@
}
pw.print(prefix); pw.print(" versionCode="); pw.print(ps.getVersionCode());
if (pkg != null) {
- pw.print(" minSdk="); pw.print(pkg.getMinSdkVersion());
- pw.print(" targetSdk="); pw.println(pkg.getTargetSdkVersion());
-
+ pw.print(" minSdk=");
+ pw.print(pkg.getMinSdkVersion());
+ }
+ pw.print(" targetSdk="); pw.println(ps.getTargetSdkVersion());
+ if (pkg != null) {
SparseIntArray minExtensionVersions = pkg.getMinExtensionVersions();
pw.print(prefix); pw.print(" minExtensionVersions=[");
@@ -6400,17 +6466,26 @@
}
boolean clearPersistentPreferredActivity(IntentFilter filter, int userId) {
+ ArrayList<PersistentPreferredActivity> removed = null;
PersistentPreferredIntentResolver ppir = mPersistentPreferredActivities.get(userId);
Iterator<PersistentPreferredActivity> it = ppir.filterIterator();
boolean changed = false;
while (it.hasNext()) {
PersistentPreferredActivity ppa = it.next();
if (IntentFilter.filterEquals(ppa.getIntentFilter(), filter)) {
- ppir.removeFilter(ppa);
- changed = true;
- break;
+ if (removed == null) {
+ removed = new ArrayList<>();
+ }
+ removed.add(ppa);
}
}
+ if (removed != null) {
+ for (int i = 0; i < removed.size(); i++) {
+ PersistentPreferredActivity ppa = removed.get(i);
+ ppir.removeFilter(ppa);
+ }
+ changed = true;
+ }
if (changed) {
onChanged();
}
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 9987867..585e2e4 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.content.pm.Flags.sdkLibIndependence;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST;
@@ -951,10 +952,12 @@
}
}
if (!pkg.getUsesSdkLibraries().isEmpty()) {
+ // Allow installation even if sdk-library dependency doesn't exist
+ boolean required = !sdkLibIndependence();
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(),
pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(),
- pkg.getPackageName(), "sdk", true, pkg.getTargetSdkVersion(), usesLibraryInfos,
- availablePackages, newLibraries);
+ pkg.getPackageName(), "sdk", required, pkg.getTargetSdkVersion(),
+ usesLibraryInfos, availablePackages, newLibraries);
}
return usesLibraryInfos;
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 0e98158..7331bc1 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1335,18 +1335,6 @@
&& user.profileGroupId == profile.profileGroupId);
}
- private Intent buildProfileAvailabilityIntent(UserInfo profile, boolean enableQuietMode,
- boolean useManagedActions) {
- Intent intent = new Intent();
- intent.setAction(getAvailabilityIntentAction(enableQuietMode, useManagedActions));
- intent.putExtra(Intent.EXTRA_QUIET_MODE, enableQuietMode);
- intent.putExtra(Intent.EXTRA_USER, profile.getUserHandle());
- intent.putExtra(Intent.EXTRA_USER_HANDLE, profile.getUserHandle().getIdentifier());
- intent.addFlags(
- Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
- return intent;
- }
-
private String getAvailabilityIntentAction(boolean enableQuietMode, boolean useManagedActions) {
return useManagedActions ?
enableQuietMode ?
@@ -1359,12 +1347,20 @@
private void broadcastProfileAvailabilityChanges(UserInfo profileInfo,
UserHandle parentHandle, boolean enableQuietMode, boolean useManagedActions) {
- Intent availabilityIntent = buildProfileAvailabilityIntent(profileInfo, enableQuietMode,
- useManagedActions);
+ Intent availabilityIntent = new Intent();
+ availabilityIntent.setAction(
+ getAvailabilityIntentAction(enableQuietMode, useManagedActions));
+ availabilityIntent.putExtra(Intent.EXTRA_QUIET_MODE, enableQuietMode);
+ availabilityIntent.putExtra(Intent.EXTRA_USER, profileInfo.getUserHandle());
+ availabilityIntent.putExtra(Intent.EXTRA_USER_HANDLE,
+ profileInfo.getUserHandle().getIdentifier());
if (profileInfo.isManagedProfile()) {
getDevicePolicyManagerInternal().broadcastIntentToManifestReceivers(
availabilityIntent, parentHandle, /* requiresPermission= */ true);
}
+ availabilityIntent.addFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+
// TODO(b/302708423): Restrict the apps that can receive these intents in case of a private
// profile.
final Bundle options = new BroadcastOptions()
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index d16a812..61e96ca 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -379,7 +379,7 @@
ai.privateFlags |= flag(state.isInstantApp(), ApplicationInfo.PRIVATE_FLAG_INSTANT)
| flag(state.isVirtualPreload(), ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD)
| flag(state.isHidden(), ApplicationInfo.PRIVATE_FLAG_HIDDEN);
- if ((flags & PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS) != 0
+ if ((flags & PackageManager.MATCH_QUARANTINED_COMPONENTS) == 0
&& state.isQuarantined()) {
ai.enabled = false;
} else if (state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
@@ -444,7 +444,7 @@
updateApplicationInfo(info, flags, state);
- initForUser(info, pkg, userId);
+ initForUser(info, pkg, userId, state);
// TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
PackageStateUnserialized pkgState = pkgSetting.getTransientState();
@@ -690,7 +690,7 @@
info.splitDependencies = pkg.getSplitDependencies().size() == 0
? null : pkg.getSplitDependencies();
- initForUser(info, pkg, userId);
+ initForUser(info, pkg, userId, state);
info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
@@ -1006,7 +1006,7 @@
}
private static void initForUser(ApplicationInfo output, AndroidPackage input,
- @UserIdInt int userId) {
+ @UserIdInt int userId, PackageUserStateInternal state) {
PackageImpl pkg = ((PackageImpl) input);
String packageName = input.getPackageName();
output.uid = UserHandle.getUid(userId, UserHandle.getAppId(input.getUid()));
@@ -1016,6 +1016,13 @@
return;
}
+ if (android.content.pm.Flags.nullableDataDir()
+ && !state.isInstalled() && !state.dataExists()) {
+ // The data dir has been deleted
+ output.dataDir = null;
+ return;
+ }
+
// For performance reasons, all these paths are built as strings
if (userId == UserHandle.USER_SYSTEM) {
output.credentialProtectedDataDir =
@@ -1050,7 +1057,7 @@
// This duplicates the ApplicationInfo variant because it uses field assignment and the classes
// don't inherit from each other, unfortunately. Consolidating logic would introduce overhead.
private static void initForUser(InstrumentationInfo output, AndroidPackage input,
- @UserIdInt int userId) {
+ @UserIdInt int userId, PackageUserStateInternal state) {
PackageImpl pkg = ((PackageImpl) input);
String packageName = input.getPackageName();
if ("android".equals(packageName)) {
@@ -1058,6 +1065,13 @@
return;
}
+ if (android.content.pm.Flags.nullableDataDir()
+ && !state.isInstalled() && !state.dataExists()) {
+ // The data dir has been deleted
+ output.dataDir = null;
+ return;
+ }
+
// For performance reasons, all these paths are built as strings
if (userId == UserHandle.USER_SYSTEM) {
output.credentialProtectedDataDir =
@@ -1089,12 +1103,23 @@
}
}
- @NonNull
+ /**
+ * Returns the data dir of the app for the target user. Return null if the app isn't installed
+ * on the target user and doesn't have a data dir on the target user.
+ */
+ @Nullable
public static File getDataDir(PackageStateInternal ps, int userId) {
if ("android".equals(ps.getPackageName())) {
return Environment.getDataSystemDirectory();
}
+ if (android.content.pm.Flags.nullableDataDir()
+ && !ps.getUserStateOrDefault(userId).isInstalled()
+ && !ps.getUserStateOrDefault(userId).dataExists()) {
+ // The app has been uninstalled for the user and the data dir has been deleted
+ return null;
+ }
+
if (ps.isDefaultToDeviceProtectedStorage()
&& PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
return Environment.getDataUserDePackageDirectory(ps.getVolumeUuid(), userId,
diff --git a/services/core/java/com/android/server/pm/pkg/ArchiveState.java b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
index 4916a4a..1e40d44 100644
--- a/services/core/java/com/android/server/pm/pkg/ArchiveState.java
+++ b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
@@ -16,9 +16,11 @@
package com.android.server.pm.pkg;
+import android.content.ComponentName;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import com.android.internal.util.AnnotationValidations;
import com.android.internal.util.DataClass;
import java.nio.file.Path;
@@ -56,6 +58,10 @@
@NonNull
private final String mTitle;
+ /** The component name of the original activity (pre-archival). */
+ @NonNull
+ private final ComponentName mOriginalComponentName;
+
/**
* The path to the stored icon of the activity in the app's locale. Null if the app does
* not define any icon (default icon would be shown on the launcher).
@@ -96,11 +102,13 @@
@DataClass.Generated.Member
public ArchiveActivityInfo(
@NonNull String title,
+ @NonNull ComponentName originalComponentName,
@Nullable Path iconBitmap,
@Nullable Path monochromeIconBitmap) {
this.mTitle = title;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mTitle);
+ this.mOriginalComponentName = originalComponentName;
+ AnnotationValidations.validate(NonNull.class, null, mTitle);
+ AnnotationValidations.validate(NonNull.class, null, mOriginalComponentName);
this.mIconBitmap = iconBitmap;
this.mMonochromeIconBitmap = monochromeIconBitmap;
@@ -116,6 +124,14 @@
}
/**
+ * The component name of the original activity (pre-archival).
+ */
+ @DataClass.Generated.Member
+ public @NonNull ComponentName getOriginalComponentName() {
+ return mOriginalComponentName;
+ }
+
+ /**
* The path to the stored icon of the activity in the app's locale. Null if the app does
* not define any icon (default icon would be shown on the launcher).
*/
@@ -140,6 +156,7 @@
return "ArchiveActivityInfo { " +
"title = " + mTitle + ", " +
+ "originalComponentName = " + mOriginalComponentName + ", " +
"iconBitmap = " + mIconBitmap + ", " +
"monochromeIconBitmap = " + mMonochromeIconBitmap +
" }";
@@ -159,6 +176,7 @@
//noinspection PointlessBooleanExpression
return true
&& java.util.Objects.equals(mTitle, that.mTitle)
+ && java.util.Objects.equals(mOriginalComponentName, that.mOriginalComponentName)
&& java.util.Objects.equals(mIconBitmap, that.mIconBitmap)
&& java.util.Objects.equals(mMonochromeIconBitmap, that.mMonochromeIconBitmap);
}
@@ -171,6 +189,7 @@
int _hash = 1;
_hash = 31 * _hash + java.util.Objects.hashCode(mTitle);
+ _hash = 31* _hash + java.util.Objects.hashCode(mOriginalComponentName);
_hash = 31 * _hash + java.util.Objects.hashCode(mIconBitmap);
_hash = 31 * _hash + java.util.Objects.hashCode(mMonochromeIconBitmap);
return _hash;
@@ -180,7 +199,8 @@
time = 1693590309015L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
+ inputSignatures =
+ "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.NonNull android.content.ComponentName mOriginalComponentName\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
@Deprecated
private void __metadata() {}
@@ -224,11 +244,9 @@
@NonNull List<ArchiveActivityInfo> activityInfos,
@NonNull String installerTitle) {
this.mActivityInfos = activityInfos;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mActivityInfos);
+ AnnotationValidations.validate(NonNull.class, null, mActivityInfos);
this.mInstallerTitle = installerTitle;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mInstallerTitle);
+ AnnotationValidations.validate(NonNull.class, null, mInstallerTitle);
// onConstructed(); // You can define this method to get a callback
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 3f347e4..e7137bb 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -434,4 +434,26 @@
*/
@Nullable
String getApexModuleName();
+
+ /**
+ * @see ApplicationInfo#FLAG_PERSISTENT
+ * @see R.styleable#AndroidManifestApplication_persistent
+ * @hide
+ */
+ boolean isPersistent();
+
+ /**
+ * @see ApplicationInfo#targetSdkVersion
+ * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion
+ * @hide
+ */
+ int getTargetSdkVersion();
+
+ /**
+ * @see R.styleable#AndroidManifestRestrictUpdate
+ * @hide
+ */
+ @Immutable.Ignore
+ @Nullable
+ byte[] getRestrictUpdateHash();
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
index 7b07e5b..cd3583b 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
@@ -16,9 +16,9 @@
package com.android.server.pm.pkg;
-import static android.content.pm.PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+import static android.content.pm.PackageManager.MATCH_QUARANTINED_COMPONENTS;
import android.annotation.NonNull;
import android.content.pm.ComponentInfo;
@@ -147,7 +147,7 @@
return true;
}
- if ((flags & FILTER_OUT_QUARANTINED_COMPONENTS) != 0 && state.isQuarantined()) {
+ if ((flags & MATCH_QUARANTINED_COMPONENTS) == 0 && state.isQuarantined()) {
return false;
}
diff --git a/services/core/java/com/android/server/pm/pkg/SuspendParams.java b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
index 86391c9..153238fa 100644
--- a/services/core/java/com/android/server/pm/pkg/SuspendParams.java
+++ b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
@@ -17,7 +17,6 @@
package com.android.server.pm.pkg;
import android.annotation.Nullable;
-import android.content.pm.Flags;
import android.content.pm.SuspendDialogInfo;
import android.os.BaseBundle;
import android.os.PersistableBundle;
@@ -142,8 +141,7 @@
PersistableBundle readAppExtras = null;
PersistableBundle readLauncherExtras = null;
- final boolean quarantined = in.getAttributeBoolean(null, ATTR_QUARANTINED, false)
- && Flags.quarantinedEnabled();
+ final boolean quarantined = in.getAttributeBoolean(null, ATTR_QUARANTINED, false);
final int currentDepth = in.getDepth();
int type;
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index d6e35e8..a33e353 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -347,8 +347,15 @@
UserHandle user = UserHandle.getUserHandleForUid(uid);
PermissionControllerManager manager = mPermControllerManagers.get(user);
if (manager == null) {
- manager = new PermissionControllerManager(
- getUserContext(getContext(), user), PermissionThread.getHandler());
+ try {
+ manager = new PermissionControllerManager(
+ getUserContext(getContext(), user), PermissionThread.getHandler());
+ } catch (IllegalArgumentException exception) {
+ // There's a possible race condition when a user is being removed
+ Log.e(LOG_TAG, "Could not create PermissionControllerManager for user"
+ + user, exception);
+ return;
+ }
mPermControllerManagers.put(user, manager);
}
manager.updateUserSensitiveForApp(uid);
diff --git a/services/core/java/com/android/server/power/Android.bp b/services/core/java/com/android/server/power/Android.bp
index 1da9dd7..607d435 100644
--- a/services/core/java/com/android/server/power/Android.bp
+++ b/services/core/java/com/android/server/power/Android.bp
@@ -1,5 +1,5 @@
aconfig_declarations {
- name: "power_optimization_flags",
+ name: "backstage_power_flags",
package: "com.android.server.power.optimization",
srcs: [
"stats/*.aconfig",
@@ -7,6 +7,6 @@
}
java_aconfig_library {
- name: "power_optimization_flags_lib",
- aconfig_declarations: "power_optimization_flags",
+ name: "backstage_power_flags_lib",
+ aconfig_declarations: "backstage_power_flags",
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index dfbcbae6..4a4214f 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -3347,8 +3347,6 @@
} else {
startDreaming = false;
}
- Slog.i(TAG, "handleSandman powerGroup=" + groupId + " startDreaming=" + startDreaming
- + " wakefulness=" + wakefulnessToString(wakefulness));
}
// Start dreaming if needed.
@@ -3383,23 +3381,19 @@
if (startDreaming && isDreaming) {
mDreamsBatteryLevelDrain = 0;
if (wakefulness == WAKEFULNESS_DOZING) {
- Slog.i(TAG, "Dozing powerGroup " + groupId);
+ Slog.i(TAG, "Dozing...");
} else {
- Slog.i(TAG, "Dreaming powerGroup " + groupId);
+ Slog.i(TAG, "Dreaming...");
}
}
// If preconditions changed, wait for the next iteration to determine
// whether the dream should continue (or be restarted).
final PowerGroup powerGroup = mPowerGroups.get(groupId);
- final int newWakefulness = powerGroup.getWakefulnessLocked();
if (powerGroup.isSandmanSummonedLocked()
- || newWakefulness != wakefulness) {
+ || powerGroup.getWakefulnessLocked() != wakefulness) {
return; // wait for next cycle
}
- Slog.i(TAG, "handleSandman powerGroup=" + groupId + " isDreaming=" + isDreaming
- + " wakefulness=" + newWakefulness);
-
// Determine whether the dream should continue.
long now = mClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
index 6cc9d0a..bc90f5c 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
@@ -18,15 +18,26 @@
import android.annotation.CurrentTimeMillisLong;
import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.os.BatteryConsumer;
import android.os.UserHandle;
import android.text.format.DateFormat;
import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.TimeUtils;
import com.android.internal.os.PowerStats;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
-import java.io.PrintWriter;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
/**
@@ -34,25 +45,75 @@
* etc) covering a specific period of power usage history.
*/
class AggregatedPowerStats {
+ private static final String TAG = "AggregatedPowerStats";
+ private static final int MAX_CLOCK_UPDATES = 100;
+ private static final String XML_TAG_AGGREGATED_POWER_STATS = "agg-power-stats";
+
private final PowerComponentAggregatedPowerStats[] mPowerComponentStats;
- @CurrentTimeMillisLong
- private long mStartTime;
+ static class ClockUpdate {
+ public long monotonicTime;
+ @CurrentTimeMillisLong public long currentTime;
+ }
+
+ private final List<ClockUpdate> mClockUpdates = new ArrayList<>();
@DurationMillisLong
private long mDurationMs;
- AggregatedPowerStats(PowerComponentAggregatedPowerStats... powerComponentAggregatedPowerStats) {
- this.mPowerComponentStats = powerComponentAggregatedPowerStats;
+ AggregatedPowerStats(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+ List<AggregatedPowerStatsConfig.PowerComponent> configs =
+ aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs();
+ mPowerComponentStats = new PowerComponentAggregatedPowerStats[configs.size()];
+ for (int i = 0; i < configs.size(); i++) {
+ mPowerComponentStats[i] = createPowerComponentAggregatedPowerStats(configs.get(i));
+ }
}
- void setStartTime(@CurrentTimeMillisLong long startTime) {
- mStartTime = startTime;
+ private PowerComponentAggregatedPowerStats createPowerComponentAggregatedPowerStats(
+ AggregatedPowerStatsConfig.PowerComponent config) {
+ switch (config.getPowerComponentId()) {
+ case BatteryConsumer.POWER_COMPONENT_CPU:
+ return new CpuAggregatedPowerStats(config);
+ default:
+ return new PowerComponentAggregatedPowerStats(config);
+ }
}
- @CurrentTimeMillisLong
- public long getStartTime() {
- return mStartTime;
+ /**
+ * Records a mapping of monotonic time to wall-clock time. Since wall-clock time can change,
+ * there may be multiple clock updates in one set of aggregated stats.
+ *
+ * @param monotonicTime monotonic time in milliseconds, see
+ * {@link com.android.internal.os.MonotonicClock}
+ * @param currentTime current time in milliseconds, see {@link System#currentTimeMillis()}
+ */
+ void addClockUpdate(long monotonicTime, @CurrentTimeMillisLong long currentTime) {
+ ClockUpdate clockUpdate = new ClockUpdate();
+ clockUpdate.monotonicTime = monotonicTime;
+ clockUpdate.currentTime = currentTime;
+ if (mClockUpdates.size() < MAX_CLOCK_UPDATES) {
+ mClockUpdates.add(clockUpdate);
+ } else {
+ Slog.i(TAG, "Too many clock updates. Replacing the previous update with "
+ + DateFormat.format("yyyy-MM-dd-HH-mm-ss", currentTime));
+ mClockUpdates.set(mClockUpdates.size() - 1, clockUpdate);
+ }
+ }
+
+ /**
+ * Start time according to {@link com.android.internal.os.MonotonicClock}
+ */
+ long getStartTime() {
+ if (mClockUpdates.isEmpty()) {
+ return 0;
+ } else {
+ return mClockUpdates.get(0).monotonicTime;
+ }
+ }
+
+ List<ClockUpdate> getClockUpdates() {
+ return mClockUpdates;
}
void setDuration(long durationMs) {
@@ -73,13 +134,14 @@
return null;
}
- void setDeviceState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) {
+ void setDeviceState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state,
+ long time) {
for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
stats.setState(stateId, state, time);
}
}
- void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state,
+ void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state,
long time) {
for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
stats.setUidState(uid, stateId, state, time);
@@ -106,20 +168,90 @@
}
void reset() {
- mStartTime = 0;
+ mClockUpdates.clear();
mDurationMs = 0;
for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
stats.reset();
}
}
- void dump(PrintWriter pw) {
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
- ipw.print("Start time: ");
- ipw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartTime));
- ipw.print(" duration: ");
- ipw.print(mDurationMs);
- ipw.println();
+ public void writeXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(null, XML_TAG_AGGREGATED_POWER_STATS);
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.writeXml(serializer);
+ }
+ serializer.endTag(null, XML_TAG_AGGREGATED_POWER_STATS);
+ serializer.flush();
+ }
+
+ @NonNull
+ public static AggregatedPowerStats createFromXml(
+ TypedXmlPullParser parser, AggregatedPowerStatsConfig aggregatedPowerStatsConfig)
+ throws XmlPullParserException, IOException {
+ AggregatedPowerStats stats = new AggregatedPowerStats(aggregatedPowerStatsConfig);
+ boolean inElement = false;
+ boolean skipToEnd = false;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG
+ && parser.getName().equals(XML_TAG_AGGREGATED_POWER_STATS))) {
+ if (!skipToEnd && eventType == XmlPullParser.START_TAG) {
+ switch (parser.getName()) {
+ case XML_TAG_AGGREGATED_POWER_STATS:
+ inElement = true;
+ break;
+ case PowerComponentAggregatedPowerStats.XML_TAG_POWER_COMPONENT:
+ if (!inElement) {
+ break;
+ }
+
+ int powerComponentId = parser.getAttributeInt(null,
+ PowerComponentAggregatedPowerStats.XML_ATTR_ID);
+ for (PowerComponentAggregatedPowerStats powerComponent :
+ stats.mPowerComponentStats) {
+ if (powerComponent.powerComponentId == powerComponentId) {
+ if (!powerComponent.readFromXml(parser)) {
+ skipToEnd = true;
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ eventType = parser.next();
+ }
+ return stats;
+ }
+
+ void dump(IndentingPrintWriter ipw) {
+ StringBuilder sb = new StringBuilder();
+ long baseTime = 0;
+ for (int i = 0; i < mClockUpdates.size(); i++) {
+ ClockUpdate clockUpdate = mClockUpdates.get(i);
+ sb.setLength(0);
+ if (i == 0) {
+ baseTime = clockUpdate.monotonicTime;
+ sb.append("Start time: ")
+ .append(DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime))
+ .append(" (")
+ .append(baseTime)
+ .append(") duration: ")
+ .append(mDurationMs);
+ ipw.println(sb);
+ } else {
+ sb.setLength(0);
+ sb.append("Clock update: ");
+ TimeUtils.formatDuration(
+ clockUpdate.monotonicTime - baseTime, sb,
+ TimeUtils.HUNDRED_DAY_FIELD_LEN + 3);
+ sb.append(" ").append(
+ DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime));
+ ipw.increaseIndent();
+ ipw.println(sb);
+ ipw.decreaseIndent();
+ }
+ }
ipw.println("Device");
ipw.increaseIndent();
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
new file mode 100644
index 0000000..477c228
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats;
+
+import android.annotation.IntDef;
+import android.os.BatteryConsumer;
+
+import com.android.internal.os.MultiStateStats;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Configuration that controls how power stats are aggregated. It determines which state changes
+ * are to be considered as essential dimensions ("tracked states") for each power component (CPU,
+ * WiFi, etc). Also, it determines which states are tracked globally and which ones on a per-UID
+ * basis.
+ */
+public class AggregatedPowerStatsConfig {
+ public static final int STATE_POWER = 0;
+ public static final int STATE_SCREEN = 1;
+ public static final int STATE_PROCESS_STATE = 2;
+
+ @IntDef({
+ STATE_POWER,
+ STATE_SCREEN,
+ STATE_PROCESS_STATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TrackedState {
+ }
+
+ static final String STATE_NAME_POWER = "pwr";
+ static final int POWER_STATE_BATTERY = 0;
+ static final int POWER_STATE_OTHER = 1; // Plugged in, or on wireless charger, etc.
+ static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"};
+
+ static final String STATE_NAME_SCREEN = "scr";
+ static final int SCREEN_STATE_ON = 0;
+ static final int SCREEN_STATE_OTHER = 1; // Off, doze etc
+ static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"};
+
+ static final String STATE_NAME_PROCESS_STATE = "ps";
+ static final String[] STATE_LABELS_PROCESS_STATE;
+
+ static {
+ String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT];
+ for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) {
+ procStateLabels[i] = BatteryConsumer.processStateToString(i);
+ }
+ STATE_LABELS_PROCESS_STATE = procStateLabels;
+ }
+
+ /**
+ * Configuration for a give power component (CPU, WiFi, etc)
+ */
+ public static class PowerComponent {
+ private final int mPowerComponentId;
+ private @TrackedState int[] mTrackedDeviceStates;
+ private @TrackedState int[] mTrackedUidStates;
+
+ PowerComponent(int powerComponentId) {
+ this.mPowerComponentId = powerComponentId;
+ }
+
+ /**
+ * Configures which states should be tracked as separate dimensions for the entire device.
+ */
+ public PowerComponent trackDeviceStates(@TrackedState int... states) {
+ mTrackedDeviceStates = states;
+ return this;
+ }
+
+ /**
+ * Configures which states should be tracked as separate dimensions on a per-UID basis.
+ */
+ public PowerComponent trackUidStates(@TrackedState int... states) {
+ mTrackedUidStates = states;
+ return this;
+ }
+
+ public int getPowerComponentId() {
+ return mPowerComponentId;
+ }
+
+ public MultiStateStats.States[] getDeviceStateConfig() {
+ return new MultiStateStats.States[]{
+ new MultiStateStats.States(STATE_NAME_POWER,
+ isTracked(mTrackedDeviceStates, STATE_POWER),
+ STATE_LABELS_POWER),
+ new MultiStateStats.States(STATE_NAME_SCREEN,
+ isTracked(mTrackedDeviceStates, STATE_SCREEN),
+ STATE_LABELS_SCREEN),
+ };
+ }
+
+ public MultiStateStats.States[] getUidStateConfig() {
+ return new MultiStateStats.States[]{
+ new MultiStateStats.States(STATE_NAME_POWER,
+ isTracked(mTrackedUidStates, STATE_POWER),
+ AggregatedPowerStatsConfig.STATE_LABELS_POWER),
+ new MultiStateStats.States(STATE_NAME_SCREEN,
+ isTracked(mTrackedUidStates, STATE_SCREEN),
+ AggregatedPowerStatsConfig.STATE_LABELS_SCREEN),
+ new MultiStateStats.States(STATE_NAME_PROCESS_STATE,
+ isTracked(mTrackedUidStates, STATE_PROCESS_STATE),
+ AggregatedPowerStatsConfig.STATE_LABELS_PROCESS_STATE),
+ };
+ }
+
+ private boolean isTracked(int[] trackedStates, int state) {
+ if (trackedStates == null) {
+ return false;
+ }
+
+ for (int trackedState : trackedStates) {
+ if (trackedState == state) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private final List<PowerComponent> mPowerComponents = new ArrayList<>();
+
+ /**
+ * Creates a configuration for the specified power component, which may be one of the
+ * standard power component IDs, e.g. {@link BatteryConsumer#POWER_COMPONENT_CPU}, or
+ * a custom power component.
+ */
+ public PowerComponent trackPowerComponent(int powerComponentId) {
+ PowerComponent builder = new PowerComponent(powerComponentId);
+ mPowerComponents.add(builder);
+ return builder;
+ }
+
+ public List<PowerComponent> getPowerComponentsAggregatedStatsConfigs() {
+ return mPowerComponents;
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java
new file mode 100644
index 0000000..7ba4330
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java
@@ -0,0 +1,48 @@
+/*
+ * 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.power.stats;
+
+import android.util.IndentingPrintWriter;
+
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+
+class AggregatedPowerStatsSection extends PowerStatsSpan.Section {
+ public static final String TYPE = "aggregated-power-stats";
+
+ private final AggregatedPowerStats mAggregatedPowerStats;
+
+ AggregatedPowerStatsSection(AggregatedPowerStats aggregatedPowerStats) {
+ super(TYPE);
+ mAggregatedPowerStats = aggregatedPowerStats;
+ }
+
+ public AggregatedPowerStats getAggregatedPowerStats() {
+ return mAggregatedPowerStats;
+ }
+
+ @Override
+ void write(TypedXmlSerializer serializer) throws IOException {
+ mAggregatedPowerStats.writeXml(serializer);
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter ipw) {
+ mAggregatedPowerStats.dump(ipw);
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 613f189..a9c2bc2 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -124,6 +124,7 @@
import com.android.internal.os.KernelSingleUidTimeReader;
import com.android.internal.os.LongArrayMultiStateCounter;
import com.android.internal.os.LongMultiStateCounter;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
import com.android.internal.os.PowerStats;
import com.android.internal.os.RailStats;
@@ -283,7 +284,6 @@
private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
private int[] mCpuPowerBracketMap;
private final CpuPowerStatsCollector mCpuPowerStatsCollector;
- private final PowerStatsAggregator mPowerStatsAggregator;
public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
return mKernelMemoryStats;
@@ -356,6 +356,11 @@
protected Queue<UidToRemove> mPendingRemovedUids = new LinkedList<>();
@NonNull
+ public BatteryStatsHistory getHistory() {
+ return mHistory;
+ }
+
+ @NonNull
BatteryStatsHistory copyHistory() {
return mHistory.copy();
}
@@ -1718,14 +1723,7 @@
return mMaxLearnedBatteryCapacityUah;
}
- public BatteryStatsImpl() {
- this(Clock.SYSTEM_CLOCK);
- }
-
- public BatteryStatsImpl(Clock clock) {
- this(clock, null);
- }
-
+ @VisibleForTesting
public BatteryStatsImpl(Clock clock, File historyDirectory) {
init(clock);
mBatteryStatsConfig = new BatteryStatsConfig.Builder().build();
@@ -1737,18 +1735,19 @@
mCheckinFile = null;
mStatsFile = null;
mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
- mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock,
+ new MonotonicClock(0, mClock));
} else {
mCheckinFile = new AtomicFile(new File(historyDirectory, "batterystats-checkin.bin"));
mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));
mHistory = new BatteryStatsHistory(historyDirectory, mConstants.MAX_HISTORY_FILES,
- mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock,
+ new MonotonicClock(0, mClock));
}
mPlatformIdleStateCallback = null;
mEnergyConsumerRetriever = null;
mUserInfoProvider = null;
mCpuPowerStatsCollector = null;
- mPowerStatsAggregator = null;
}
private void init(Clock clock) {
@@ -10906,20 +10905,12 @@
return mTmpCpuTimeInFreq;
}
- public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @Nullable File systemDir,
+ public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock,
+ @NonNull MonotonicClock monotonicClock, @Nullable File systemDir,
@NonNull Handler handler, @Nullable PlatformIdleStateCallback cb,
@Nullable EnergyStatsRetriever energyStatsCb,
@NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile,
@NonNull CpuScalingPolicies cpuScalingPolicies) {
- this(config, Clock.SYSTEM_CLOCK, systemDir, handler, cb, energyStatsCb, userInfoProvider,
- powerProfile, cpuScalingPolicies);
- }
-
- private BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock,
- @Nullable File systemDir, @NonNull Handler handler,
- @Nullable PlatformIdleStateCallback cb, @Nullable EnergyStatsRetriever energyStatsCb,
- @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile,
- @NonNull CpuScalingPolicies cpuScalingPolicies) {
init(clock);
mBatteryStatsConfig = config;
@@ -10936,31 +10927,19 @@
mCheckinFile = null;
mDailyFile = null;
mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
- mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock);
} else {
mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin"));
mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES,
- mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock);
}
mCpuPowerStatsCollector = new CpuPowerStatsCollector(mCpuScalingPolicies, mPowerProfile,
mHandler, mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu());
mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
- PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory);
- builder.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
- .trackDeviceStates(
- PowerStatsAggregator.STATE_POWER,
- PowerStatsAggregator.STATE_SCREEN)
- .trackUidStates(
- PowerStatsAggregator.STATE_POWER,
- PowerStatsAggregator.STATE_SCREEN,
- PowerStatsAggregator.STATE_PROCESS_STATE);
-
- mPowerStatsAggregator = builder.build();
-
mStartCount++;
initTimersAndCounters();
mOnBattery = mOnBatteryInternal = false;
@@ -15702,20 +15681,23 @@
}
/**
+ * Schedules an immediate (but asynchronous) collection of PowerStats samples.
+ * Callers will need to wait for the collection to complete on the handler thread.
+ */
+ public void schedulePowerStatsSampleCollection() {
+ if (mCpuPowerStatsCollector == null) {
+ return;
+ }
+ mCpuPowerStatsCollector.forceSchedule();
+ }
+
+ /**
* Grabs one sample of PowerStats and prints it.
*/
public void dumpStatsSample(PrintWriter pw) {
mCpuPowerStatsCollector.collectAndDump(pw);
}
- /**
- * Aggregates power stats between the specified times and prints them.
- */
- public void dumpAggregatedStats(PrintWriter pw, long startTimeMs, long endTimeMs) {
- mPowerStatsAggregator.aggregateBatteryStats(startTimeMs, endTimeMs,
- stats-> stats.dump(pw));
- }
-
private final Runnable mWriteAsyncRunnable = () -> {
synchronized (BatteryStatsImpl.this) {
writeSyncLocked();
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index f6fa9f2..851a3f7 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -46,7 +46,7 @@
private static final String TAG = "BatteryUsageStatsProv";
private final Context mContext;
private final BatteryStats mStats;
- private final BatteryUsageStatsStore mBatteryUsageStatsStore;
+ private final PowerStatsStore mPowerStatsStore;
private final PowerProfile mPowerProfile;
private final CpuScalingPolicies mCpuScalingPolicies;
private final Object mLock = new Object();
@@ -58,10 +58,10 @@
@VisibleForTesting
public BatteryUsageStatsProvider(Context context, BatteryStats stats,
- BatteryUsageStatsStore batteryUsageStatsStore) {
+ PowerStatsStore powerStatsStore) {
mContext = context;
mStats = stats;
- mBatteryUsageStatsStore = batteryUsageStatsStore;
+ mPowerStatsStore = powerStatsStore;
mPowerProfile = stats instanceof BatteryStatsImpl
? ((BatteryStatsImpl) stats).getPowerProfile()
: new PowerProfile(context);
@@ -314,20 +314,52 @@
final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
customEnergyConsumerNames, includePowerModels, includeProcessStateData,
minConsumedPowerThreshold);
- if (mBatteryUsageStatsStore == null) {
- Log.e(TAG, "BatteryUsageStatsStore is unavailable");
+ if (mPowerStatsStore == null) {
+ Log.e(TAG, "PowerStatsStore is unavailable");
return builder.build();
}
- final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
- for (long timestamp : timestamps) {
- if (timestamp > query.getFromTimestamp() && timestamp <= query.getToTimestamp()) {
- final BatteryUsageStats snapshot =
- mBatteryUsageStatsStore.loadBatteryUsageStats(timestamp);
- if (snapshot == null) {
- continue;
- }
+ List<PowerStatsSpan.Metadata> toc = mPowerStatsStore.getTableOfContents();
+ for (PowerStatsSpan.Metadata spanMetadata : toc) {
+ if (!spanMetadata.getSections().contains(BatteryUsageStatsSection.TYPE)) {
+ continue;
+ }
+ // BatteryUsageStatsQuery is expressed in terms of wall-clock time range for the
+ // session end time.
+ //
+ // The following algorithm is correct when there is only one time frame in the span.
+ // When the wall-clock time is adjusted in the middle of an stats span,
+ // constraining it by wall-clock time becomes ambiguous. In this case, the algorithm
+ // only covers some situations, but not others. When using the resulting data for
+ // analysis, we should always pay attention to the full set of included timeframes.
+ // TODO(b/298459065): switch to monotonic clock
+ long minTime = Long.MAX_VALUE;
+ long maxTime = 0;
+ for (PowerStatsSpan.TimeFrame timeFrame : spanMetadata.getTimeFrames()) {
+ long spanEndTime = timeFrame.startTime + timeFrame.duration;
+ minTime = Math.min(minTime, spanEndTime);
+ maxTime = Math.max(maxTime, spanEndTime);
+ }
+
+ // Per BatteryUsageStatsQuery API, the "from" timestamp is *exclusive*,
+ // while the "to" timestamp is *inclusive*.
+ boolean isInRange =
+ (query.getFromTimestamp() == 0 || minTime > query.getFromTimestamp())
+ && (query.getToTimestamp() == 0 || maxTime <= query.getToTimestamp());
+ if (!isInRange) {
+ continue;
+ }
+
+ PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan(
+ spanMetadata.getId(), BatteryUsageStatsSection.TYPE);
+ if (powerStatsSpan == null) {
+ continue;
+ }
+
+ for (PowerStatsSpan.Section section : powerStatsSpan.getSections()) {
+ BatteryUsageStats snapshot =
+ ((BatteryUsageStatsSection) section).getBatteryUsageStats();
if (!Arrays.equals(snapshot.getCustomPowerComponentNames(),
customEnergyConsumerNames)) {
Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different "
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
new file mode 100644
index 0000000..b95faac
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.os.BatteryUsageStats;
+import android.util.IndentingPrintWriter;
+
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+
+class BatteryUsageStatsSection extends PowerStatsSpan.Section {
+ public static final String TYPE = "battery-usage-stats";
+
+ private final BatteryUsageStats mBatteryUsageStats;
+
+ BatteryUsageStatsSection(BatteryUsageStats batteryUsageStats) {
+ super(TYPE);
+ mBatteryUsageStats = batteryUsageStats;
+ }
+
+ public BatteryUsageStats getBatteryUsageStats() {
+ return mBatteryUsageStats;
+ }
+
+ @Override
+ void write(TypedXmlSerializer serializer) throws IOException {
+ mBatteryUsageStats.writeXml(serializer);
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter ipw) {
+ mBatteryUsageStats.dump(ipw, "");
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java
deleted file mode 100644
index 0d7a140..0000000
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright (C) 2020 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.power.stats;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.BatteryUsageStats;
-import android.os.BatteryUsageStatsQuery;
-import android.os.Handler;
-import android.util.AtomicFile;
-import android.util.Log;
-import android.util.LongArray;
-import android.util.Slog;
-import android.util.Xml;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.StandardOpenOption;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-import java.util.TreeMap;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * A storage mechanism for BatteryUsageStats snapshots.
- */
-public class BatteryUsageStatsStore {
- private static final String TAG = "BatteryUsageStatsStore";
-
- private static final List<BatteryUsageStatsQuery> BATTERY_USAGE_STATS_QUERY = List.of(
- new BatteryUsageStatsQuery.Builder()
- .setMaxStatsAgeMs(0)
- .includePowerModels()
- .includeProcessStateData()
- .build());
- private static final String BATTERY_USAGE_STATS_DIR = "battery-usage-stats";
- private static final String SNAPSHOT_FILE_EXTENSION = ".bus";
- private static final String DIR_LOCK_FILENAME = ".lock";
- private static final String CONFIG_FILENAME = "config";
- private static final String BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY =
- "BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP";
- private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 100 * 1024;
-
- private final Context mContext;
- private final BatteryStatsImpl mBatteryStats;
- private boolean mSystemReady;
- private final File mStoreDir;
- private final File mLockFile;
- private final ReentrantLock mFileLock = new ReentrantLock();
- private FileLock mJvmLock;
- private final AtomicFile mConfigFile;
- private final long mMaxStorageBytes;
- private final Handler mHandler;
- private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
-
- public BatteryUsageStatsStore(Context context, BatteryStatsImpl stats, File systemDir,
- Handler handler) {
- this(context, stats, systemDir, handler, MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
- }
-
- @VisibleForTesting
- public BatteryUsageStatsStore(Context context, BatteryStatsImpl batteryStats, File systemDir,
- Handler handler, long maxStorageBytes) {
- mContext = context;
- mBatteryStats = batteryStats;
- mStoreDir = new File(systemDir, BATTERY_USAGE_STATS_DIR);
- mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME);
- mConfigFile = new AtomicFile(new File(mStoreDir, CONFIG_FILENAME));
- mHandler = handler;
- mMaxStorageBytes = maxStorageBytes;
- mBatteryStats.setBatteryResetListener(this::prepareForBatteryStatsReset);
- mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(mContext, mBatteryStats);
- }
-
- /**
- * Notifies BatteryUsageStatsStore that the system server is ready.
- */
- public void onSystemReady() {
- mSystemReady = true;
- }
-
- private void prepareForBatteryStatsReset(int resetReason) {
- if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE || !mSystemReady) {
- return;
- }
-
- final List<BatteryUsageStats> stats =
- mBatteryUsageStatsProvider.getBatteryUsageStats(BATTERY_USAGE_STATS_QUERY);
- if (stats.isEmpty()) {
- Slog.wtf(TAG, "No battery usage stats generated");
- return;
- }
-
- mHandler.post(() -> storeBatteryUsageStats(stats.get(0)));
- }
-
- private void storeBatteryUsageStats(BatteryUsageStats stats) {
- lockSnapshotDirectory();
- try {
- if (!mStoreDir.exists()) {
- if (!mStoreDir.mkdirs()) {
- Slog.e(TAG, "Could not create a directory for battery usage stats snapshots");
- return;
- }
- }
- File file = makeSnapshotFilename(stats.getStatsEndTimestamp());
- try {
- writeXmlFileLocked(stats, file);
- } catch (Exception e) {
- Slog.e(TAG, "Cannot save battery usage stats", e);
- }
-
- removeOldSnapshotsLocked();
- } finally {
- unlockSnapshotDirectory();
- }
- }
-
- /**
- * Returns the timestamps of the stored BatteryUsageStats snapshots. The timestamp corresponds
- * to the time the snapshot was taken {@link BatteryUsageStats#getStatsEndTimestamp()}.
- */
- public long[] listBatteryUsageStatsTimestamps() {
- LongArray timestamps = new LongArray(100);
- lockSnapshotDirectory();
- try {
- for (File file : mStoreDir.listFiles()) {
- String fileName = file.getName();
- if (fileName.endsWith(SNAPSHOT_FILE_EXTENSION)) {
- try {
- String fileNameWithoutExtension = fileName.substring(0,
- fileName.length() - SNAPSHOT_FILE_EXTENSION.length());
- timestamps.add(Long.parseLong(fileNameWithoutExtension));
- } catch (NumberFormatException e) {
- Slog.wtf(TAG, "Invalid format of BatteryUsageStats snapshot file name: "
- + fileName);
- }
- }
- }
- } finally {
- unlockSnapshotDirectory();
- }
- return timestamps.toArray();
- }
-
- /**
- * Reads the specified snapshot of BatteryUsageStats. Returns null if the snapshot
- * does not exist.
- */
- @Nullable
- public BatteryUsageStats loadBatteryUsageStats(long timestamp) {
- lockSnapshotDirectory();
- try {
- File file = makeSnapshotFilename(timestamp);
- try {
- return readXmlFileLocked(file);
- } catch (Exception e) {
- Slog.e(TAG, "Cannot read battery usage stats", e);
- }
- } finally {
- unlockSnapshotDirectory();
- }
- return null;
- }
-
- /**
- * Saves the supplied timestamp of the BATTERY_USAGE_STATS_BEFORE_RESET statsd atom pull
- * in persistent file.
- */
- public void setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(long timestamp) {
- Properties props = new Properties();
- lockSnapshotDirectory();
- try {
- try (InputStream in = mConfigFile.openRead()) {
- props.load(in);
- } catch (IOException e) {
- Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
- }
- props.put(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY,
- String.valueOf(timestamp));
- FileOutputStream out = null;
- try {
- out = mConfigFile.startWrite();
- props.store(out, "Statsd atom pull timestamps");
- mConfigFile.finishWrite(out);
- } catch (IOException e) {
- mConfigFile.failWrite(out);
- Slog.e(TAG, "Cannot save config file " + mConfigFile, e);
- }
- } finally {
- unlockSnapshotDirectory();
- }
- }
-
- /**
- * Retrieves the previously saved timestamp of the last BATTERY_USAGE_STATS_BEFORE_RESET
- * statsd atom pull.
- */
- public long getLastBatteryUsageStatsBeforeResetAtomPullTimestamp() {
- Properties props = new Properties();
- lockSnapshotDirectory();
- try {
- try (InputStream in = mConfigFile.openRead()) {
- props.load(in);
- } catch (IOException e) {
- Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
- }
- } finally {
- unlockSnapshotDirectory();
- }
- return Long.parseLong(
- props.getProperty(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, "0"));
- }
-
- private void lockSnapshotDirectory() {
- mFileLock.lock();
-
- // Lock the directory from access by other JVMs
- try {
- mLockFile.getParentFile().mkdirs();
- mLockFile.createNewFile();
- mJvmLock = FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock();
- } catch (IOException e) {
- Log.e(TAG, "Cannot lock snapshot directory", e);
- }
- }
-
- private void unlockSnapshotDirectory() {
- try {
- mJvmLock.close();
- } catch (IOException e) {
- Log.e(TAG, "Cannot unlock snapshot directory", e);
- } finally {
- mFileLock.unlock();
- }
- }
-
- /**
- * Creates a file name by formatting the timestamp as 19-digit zero-padded number.
- * This ensures that sorted directory list follows the chronological order.
- */
- private File makeSnapshotFilename(long statsEndTimestamp) {
- return new File(mStoreDir, String.format(Locale.ENGLISH, "%019d", statsEndTimestamp)
- + SNAPSHOT_FILE_EXTENSION);
- }
-
- private void writeXmlFileLocked(BatteryUsageStats stats, File file) throws IOException {
- try (OutputStream out = new FileOutputStream(file)) {
- TypedXmlSerializer serializer = Xml.newBinarySerializer();
- serializer.setOutput(out, StandardCharsets.UTF_8.name());
- serializer.startDocument(null, true);
- stats.writeXml(serializer);
- serializer.endDocument();
- }
- }
-
- private BatteryUsageStats readXmlFileLocked(File file)
- throws IOException, XmlPullParserException {
- try (InputStream in = new FileInputStream(file)) {
- TypedXmlPullParser parser = Xml.newBinaryPullParser();
- parser.setInput(in, StandardCharsets.UTF_8.name());
- return BatteryUsageStats.createFromXml(parser);
- }
- }
-
- private void removeOldSnapshotsLocked() {
- // Read the directory list into a _sorted_ map. The alphanumeric ordering
- // corresponds to the historical order of snapshots because the file names
- // are timestamps zero-padded to the same length.
- long totalSize = 0;
- TreeMap<File, Long> mFileSizes = new TreeMap<>();
- for (File file : mStoreDir.listFiles()) {
- final long fileSize = file.length();
- totalSize += fileSize;
- if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) {
- mFileSizes.put(file, fileSize);
- }
- }
-
- while (totalSize > mMaxStorageBytes) {
- final Map.Entry<File, Long> entry = mFileSizes.firstEntry();
- if (entry == null) {
- break;
- }
-
- File file = entry.getKey();
- if (!file.delete()) {
- Slog.e(TAG, "Cannot delete battery usage stats " + file);
- }
- totalSize -= entry.getValue();
- mFileSizes.remove(file);
- }
- }
-
- public void removeAllSnapshots() {
- lockSnapshotDirectory();
- try {
- for (File file : mStoreDir.listFiles()) {
- if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) {
- if (!file.delete()) {
- Slog.e(TAG, "Cannot delete battery usage stats " + file);
- }
- }
- }
- } finally {
- unlockSnapshotDirectory();
- }
- }
-}
diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
index 5b3fe06..fbf6928 100644
--- a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
@@ -16,14 +16,8 @@
package com.android.server.power.stats;
-import android.os.BatteryConsumer;
-
-import com.android.internal.os.MultiStateStats;
-
class CpuAggregatedPowerStats extends PowerComponentAggregatedPowerStats {
-
- CpuAggregatedPowerStats(MultiStateStats.States[] deviceStates,
- MultiStateStats.States[] uidStates) {
- super(BatteryConsumer.POWER_COMPONENT_CPU, deviceStates, uidStates);
+ CpuAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) {
+ super(config);
}
}
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 686268f..05c0a13 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,13 @@
import com.android.internal.os.MultiStateStats;
import com.android.internal.os.PowerStats;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
import java.util.Collection;
/**
@@ -31,6 +37,12 @@
* as part of the {@link PowerStats.Descriptor}.
*/
class PowerComponentAggregatedPowerStats {
+ static final String XML_TAG_POWER_COMPONENT = "power_component";
+ static final String XML_ATTR_ID = "id";
+ private static final String XML_TAG_DEVICE_STATS = "device-stats";
+ private static final String XML_TAG_UID_STATS = "uid-stats";
+ private static final String XML_ATTR_UID = "uid";
+
public final int powerComponentId;
private final MultiStateStats.States[] mDeviceStateConfig;
private final MultiStateStats.States[] mUidStateConfig;
@@ -49,22 +61,24 @@
public MultiStateStats stats;
}
- PowerComponentAggregatedPowerStats(int powerComponentId,
- MultiStateStats.States[] deviceStates,
- MultiStateStats.States[] uidStates) {
- this.powerComponentId = powerComponentId;
- mDeviceStateConfig = deviceStates;
- mUidStateConfig = uidStates;
+ PowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) {
+ this.powerComponentId = config.getPowerComponentId();
+ mDeviceStateConfig = config.getDeviceStateConfig();
+ mUidStateConfig = config.getUidStateConfig();
mDeviceStates = new int[mDeviceStateConfig.length];
mDeviceStateTimestamps = new long[mDeviceStateConfig.length];
}
- void setState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) {
+ public PowerStats.Descriptor getPowerStatsDescriptor() {
+ return mPowerStatsDescriptor;
+ }
+
+ void setState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time) {
mDeviceStates[stateId] = state;
mDeviceStateTimestamps[stateId] = time;
if (mDeviceStateConfig[stateId].isTracked()) {
- if (mDeviceStats != null || createDeviceStats()) {
+ if (mDeviceStats != null) {
mDeviceStats.setState(stateId, state, time);
}
}
@@ -72,14 +86,14 @@
if (mUidStateConfig[stateId].isTracked()) {
for (int i = mUidStats.size() - 1; i >= 0; i--) {
PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i);
- if (uidStats.stats != null || createUidStats(uidStats)) {
+ if (uidStats.stats != null) {
uidStats.stats.setState(stateId, state, time);
}
}
}
}
- void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state,
+ void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state,
long time) {
if (!mUidStateConfig[stateId].isTracked()) {
return;
@@ -89,7 +103,7 @@
uidStats.states[stateId] = state;
uidStats.stateTimestampMs[stateId] = time;
- if (uidStats.stats != null || createUidStats(uidStats)) {
+ if (uidStats.stats != null) {
uidStats.stats.setState(stateId, state, time);
}
}
@@ -102,13 +116,6 @@
mPowerStatsDescriptor = powerStats.descriptor;
if (mDeviceStats == null) {
- if (mStatsFactory == null) {
- mStatsFactory = new MultiStateStats.Factory(
- mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig);
- mUidStatsFactory = new MultiStateStats.Factory(
- mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig);
- }
-
createDeviceStats();
}
@@ -183,7 +190,11 @@
private boolean createDeviceStats() {
if (mStatsFactory == null) {
- return false;
+ if (mPowerStatsDescriptor == null) {
+ return false;
+ }
+ mStatsFactory = new MultiStateStats.Factory(
+ mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig);
}
mDeviceStats = mStatsFactory.create();
@@ -196,7 +207,11 @@
private boolean createUidStats(UidStats uidStats) {
if (mUidStatsFactory == null) {
- return false;
+ if (mPowerStatsDescriptor == null) {
+ return false;
+ }
+ mUidStatsFactory = new MultiStateStats.Factory(
+ mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig);
}
uidStats.stats = mUidStatsFactory.create();
@@ -211,6 +226,74 @@
return true;
}
+ public void writeXml(TypedXmlSerializer serializer) throws IOException {
+ // No stats aggregated - can skip writing XML altogether
+ if (mPowerStatsDescriptor == null) {
+ return;
+ }
+
+ serializer.startTag(null, XML_TAG_POWER_COMPONENT);
+ serializer.attributeInt(null, XML_ATTR_ID, powerComponentId);
+ mPowerStatsDescriptor.writeXml(serializer);
+
+ if (mDeviceStats != null) {
+ serializer.startTag(null, XML_TAG_DEVICE_STATS);
+ mDeviceStats.writeXml(serializer);
+ serializer.endTag(null, XML_TAG_DEVICE_STATS);
+ }
+
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ int uid = mUidStats.keyAt(i);
+ UidStats uidStats = mUidStats.valueAt(i);
+ if (uidStats.stats != null) {
+ serializer.startTag(null, XML_TAG_UID_STATS);
+ serializer.attributeInt(null, XML_ATTR_UID, uid);
+ uidStats.stats.writeXml(serializer);
+ serializer.endTag(null, XML_TAG_UID_STATS);
+ }
+ }
+
+ serializer.endTag(null, XML_TAG_POWER_COMPONENT);
+ serializer.flush();
+ }
+
+ public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException,
+ IOException {
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ switch (parser.getName()) {
+ case PowerStats.Descriptor.XML_TAG_DESCRIPTOR:
+ mPowerStatsDescriptor = PowerStats.Descriptor.createFromXml(parser);
+ if (mPowerStatsDescriptor == null) {
+ return false;
+ }
+ break;
+ case XML_TAG_DEVICE_STATS:
+ if (mDeviceStats == null) {
+ createDeviceStats();
+ }
+ if (!mDeviceStats.readFromXml(parser)) {
+ return false;
+ }
+ break;
+ case XML_TAG_UID_STATS:
+ int uid = parser.getAttributeInt(null, XML_ATTR_UID);
+ UidStats uidStats = getUidStats(uid);
+ if (uidStats.stats == null) {
+ createUidStats(uidStats);
+ }
+ if (!uidStats.stats.readFromXml(parser)) {
+ return false;
+ }
+ break;
+ }
+ }
+ eventType = parser.next();
+ }
+ return true;
+ }
+
void dumpDevice(IndentingPrintWriter ipw) {
if (mDeviceStats != null) {
ipw.println(mPowerStatsDescriptor.name);
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
index 6a1c1da..f374fb7 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
@@ -15,59 +15,26 @@
*/
package com.android.server.power.stats;
-import android.annotation.IntDef;
-import android.os.BatteryConsumer;
import android.os.BatteryStats;
import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.BatteryStatsHistoryIterator;
-import com.android.internal.os.MultiStateStats;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
import java.util.function.Consumer;
-class PowerStatsAggregator {
- public static final int STATE_POWER = 0;
- public static final int STATE_SCREEN = 1;
- public static final int STATE_PROCESS_STATE = 2;
-
- @IntDef({
- STATE_POWER,
- STATE_SCREEN,
- STATE_PROCESS_STATE,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface TrackedState {
- }
-
- static final int POWER_STATE_BATTERY = 0;
- static final int POWER_STATE_OTHER = 1; // Plugged in, or on wireless charger, etc.
- static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"};
-
- static final int SCREEN_STATE_ON = 0;
- static final int SCREEN_STATE_OTHER = 1; // Off, doze etc
- static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"};
-
- static final String[] STATE_LABELS_PROCESS_STATE;
-
- static {
- String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT];
- for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) {
- procStateLabels[i] = BatteryConsumer.processStateToString(i);
- }
- STATE_LABELS_PROCESS_STATE = procStateLabels;
- }
-
- private final BatteryStatsHistory mHistory;
+/**
+ * Power stats aggregator. It reads through portions of battery stats history, finds
+ * relevant items (state changes, power stats etc) and produces one or more
+ * {@link AggregatedPowerStats} that adds up power stats from the samples found in battery history.
+ */
+public class PowerStatsAggregator {
private final AggregatedPowerStats mStats;
+ private final BatteryStatsHistory mHistory;
- private PowerStatsAggregator(BatteryStatsHistory history,
- AggregatedPowerStats aggregatedPowerStats) {
+ public PowerStatsAggregator(AggregatedPowerStatsConfig aggregatedPowerStatsConfig,
+ BatteryStatsHistory history) {
+ mStats = new AggregatedPowerStats(aggregatedPowerStatsConfig);
mHistory = history;
- mStats = aggregatedPowerStats;
}
/**
@@ -82,12 +49,10 @@
* Note: the AggregatedPowerStats object is reused, so the consumer should fully consume
* the stats in the <code>accept</code> method and never cache it.
*/
- void aggregateBatteryStats(long startTimeMs, long endTimeMs,
+ public void aggregatePowerStats(long startTimeMs, long endTimeMs,
Consumer<AggregatedPowerStats> consumer) {
- mStats.reset();
-
- int currentBatteryState = POWER_STATE_BATTERY;
- int currentScreenState = SCREEN_STATE_OTHER;
+ int currentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+ int currentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
long baseTime = -1;
long lastTime = 0;
try (BatteryStatsHistoryIterator iterator =
@@ -96,131 +61,60 @@
BatteryStats.HistoryItem item = iterator.next();
if (baseTime < 0) {
- mStats.setStartTime(item.currentTime);
+ mStats.addClockUpdate(item.time, item.currentTime);
baseTime = item.time;
+ } else if (item.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME
+ || item.cmd == BatteryStats.HistoryItem.CMD_RESET) {
+ mStats.addClockUpdate(item.time, item.currentTime);
}
lastTime = item.time;
int batteryState =
(item.states & BatteryStats.HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0
- ? POWER_STATE_OTHER : POWER_STATE_BATTERY;
+ ? AggregatedPowerStatsConfig.POWER_STATE_OTHER
+ : AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
if (batteryState != currentBatteryState) {
- mStats.setDeviceState(STATE_POWER, batteryState, item.time);
+ mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_POWER, batteryState,
+ item.time);
currentBatteryState = batteryState;
}
int screenState =
(item.states & BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG) != 0
- ? SCREEN_STATE_ON : SCREEN_STATE_OTHER;
+ ? AggregatedPowerStatsConfig.SCREEN_STATE_ON
+ : AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
if (screenState != currentScreenState) {
- mStats.setDeviceState(STATE_SCREEN, screenState, item.time);
+ mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN, screenState,
+ item.time);
currentScreenState = screenState;
}
if (item.processStateChange != null) {
- mStats.setUidState(item.processStateChange.uid, STATE_PROCESS_STATE,
+ mStats.setUidState(item.processStateChange.uid,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
item.processStateChange.processState, item.time);
}
if (item.powerStats != null) {
if (!mStats.isCompatible(item.powerStats)) {
- mStats.setDuration(lastTime - baseTime);
- consumer.accept(mStats);
+ if (lastTime > baseTime) {
+ mStats.setDuration(lastTime - baseTime);
+ consumer.accept(mStats);
+ }
mStats.reset();
- mStats.setStartTime(item.currentTime);
+ mStats.addClockUpdate(item.time, item.currentTime);
baseTime = lastTime = item.time;
}
mStats.addPowerStats(item.powerStats, item.time);
}
}
}
- mStats.setDuration(lastTime - baseTime);
- consumer.accept(mStats);
- }
-
- static class Builder {
- static class PowerComponentAggregateStatsBuilder {
- private final int mPowerComponentId;
- private @TrackedState int[] mTrackedDeviceStates;
- private @TrackedState int[] mTrackedUidStates;
-
- PowerComponentAggregateStatsBuilder(int powerComponentId) {
- this.mPowerComponentId = powerComponentId;
- }
-
- public PowerComponentAggregateStatsBuilder trackDeviceStates(
- @TrackedState int... states) {
- mTrackedDeviceStates = states;
- return this;
- }
-
- public PowerComponentAggregateStatsBuilder trackUidStates(@TrackedState int... states) {
- mTrackedUidStates = states;
- return this;
- }
-
- private PowerComponentAggregatedPowerStats build() {
- MultiStateStats.States[] deviceStates = new MultiStateStats.States[]{
- new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_POWER),
- PowerStatsAggregator.STATE_LABELS_POWER),
- new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_SCREEN),
- PowerStatsAggregator.STATE_LABELS_SCREEN),
- };
-
- MultiStateStats.States[] uidStates = new MultiStateStats.States[]{
- new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_POWER),
- PowerStatsAggregator.STATE_LABELS_POWER),
- new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_SCREEN),
- PowerStatsAggregator.STATE_LABELS_SCREEN),
- new MultiStateStats.States(
- isTracked(mTrackedUidStates, STATE_PROCESS_STATE),
- PowerStatsAggregator.STATE_LABELS_PROCESS_STATE),
- };
-
- switch (mPowerComponentId) {
- case BatteryConsumer.POWER_COMPONENT_CPU:
- return new CpuAggregatedPowerStats(deviceStates, uidStates);
- default:
- return new PowerComponentAggregatedPowerStats(mPowerComponentId,
- deviceStates, uidStates);
- }
- }
-
- private boolean isTracked(int[] trackedStates, int state) {
- if (trackedStates == null) {
- return false;
- }
-
- for (int trackedState : trackedStates) {
- if (trackedState == state) {
- return true;
- }
- }
- return false;
- }
+ if (lastTime > baseTime) {
+ mStats.setDuration(lastTime - baseTime);
+ consumer.accept(mStats);
}
- private final BatteryStatsHistory mHistory;
- private final List<PowerComponentAggregateStatsBuilder> mPowerComponents =
- new ArrayList<>();
-
- Builder(BatteryStatsHistory history) {
- mHistory = history;
- }
-
- PowerComponentAggregateStatsBuilder trackPowerComponent(int powerComponentId) {
- PowerComponentAggregateStatsBuilder builder = new PowerComponentAggregateStatsBuilder(
- powerComponentId);
- mPowerComponents.add(builder);
- return builder;
- }
-
- PowerStatsAggregator build() {
- return new PowerStatsAggregator(mHistory, new AggregatedPowerStats(
- mPowerComponents.stream()
- .map(PowerComponentAggregateStatsBuilder::build)
- .toArray(PowerComponentAggregatedPowerStats[]::new)));
- }
+ mStats.reset(); // to free up memory
}
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
new file mode 100644
index 0000000..58619c7
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2020 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.power.stats;
+
+import android.annotation.DurationMillisLong;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+import com.android.internal.os.MonotonicClock;
+
+import java.io.PrintWriter;
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Controls the frequency at which {@link PowerStatsSpan}'s are generated and stored in
+ * {@link PowerStatsStore}.
+ */
+public class PowerStatsScheduler {
+ private static final long MINUTE_IN_MILLIS = TimeUnit.MINUTES.toMillis(1);
+ private static final long HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1);
+
+ private final Context mContext;
+ private boolean mEnablePeriodicPowerStatsCollection;
+ @DurationMillisLong
+ private final long mAggregatedPowerStatsSpanDuration;
+ @DurationMillisLong
+ private final long mPowerStatsAggregationPeriod;
+ private final PowerStatsStore mPowerStatsStore;
+ private final Clock mClock;
+ private final MonotonicClock mMonotonicClock;
+ private final Handler mHandler;
+ private final BatteryStatsImpl mBatteryStats;
+ private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+ private final PowerStatsAggregator mPowerStatsAggregator;
+ private long mLastSavedSpanEndMonotonicTime;
+
+ public PowerStatsScheduler(Context context, PowerStatsAggregator powerStatsAggregator,
+ @DurationMillisLong long aggregatedPowerStatsSpanDuration,
+ @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore,
+ Clock clock, MonotonicClock monotonicClock, Handler handler,
+ BatteryStatsImpl batteryStats, BatteryUsageStatsProvider batteryUsageStatsProvider) {
+ mContext = context;
+ mPowerStatsAggregator = powerStatsAggregator;
+ mAggregatedPowerStatsSpanDuration = aggregatedPowerStatsSpanDuration;
+ mPowerStatsAggregationPeriod = powerStatsAggregationPeriod;
+ mPowerStatsStore = powerStatsStore;
+ mClock = clock;
+ mMonotonicClock = monotonicClock;
+ mHandler = handler;
+ mBatteryStats = batteryStats;
+ mBatteryUsageStatsProvider = batteryUsageStatsProvider;
+ }
+
+ /**
+ * Kicks off the scheduling of power stats aggregation spans.
+ */
+ public void start(boolean enablePeriodicPowerStatsCollection) {
+ mBatteryStats.setBatteryResetListener(this::storeBatteryUsageStatsOnReset);
+ mEnablePeriodicPowerStatsCollection = enablePeriodicPowerStatsCollection;
+ if (mEnablePeriodicPowerStatsCollection) {
+ scheduleNextPowerStatsAggregation();
+ }
+ }
+
+ private void scheduleNextPowerStatsAggregation() {
+ AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
+ alarmManager.set(AlarmManager.ELAPSED_REALTIME,
+ mClock.elapsedRealtime() + mPowerStatsAggregationPeriod, "PowerStats",
+ () -> {
+ schedulePowerStatsAggregation();
+ mHandler.post(this::scheduleNextPowerStatsAggregation);
+ }, mHandler);
+ }
+
+ /**
+ * Initiate an asynchronous process of aggregation of power stats.
+ */
+ @VisibleForTesting
+ public void schedulePowerStatsAggregation() {
+ // Catch up the power stats collectors
+ mBatteryStats.schedulePowerStatsSampleCollection();
+ mHandler.post(this::aggregateAndStorePowerStats);
+ }
+
+ private void aggregateAndStorePowerStats() {
+ long currentTimeMillis = mClock.currentTimeMillis();
+ long currentMonotonicTime = mMonotonicClock.monotonicTime();
+ long startTime = getLastSavedSpanEndMonotonicTime();
+ long endTimeMs = alignToWallClock(startTime + mAggregatedPowerStatsSpanDuration,
+ mAggregatedPowerStatsSpanDuration, currentMonotonicTime, currentTimeMillis);
+ while (endTimeMs <= currentMonotonicTime) {
+ mPowerStatsAggregator.aggregatePowerStats(startTime, endTimeMs,
+ stats -> {
+ storeAggregatedPowerStats(stats);
+ mLastSavedSpanEndMonotonicTime = stats.getStartTime() + stats.getDuration();
+ });
+
+ startTime = endTimeMs;
+ endTimeMs += mAggregatedPowerStatsSpanDuration;
+ }
+ }
+
+ /**
+ * Performs a power stats aggregation pass and then dumps all stored aggregated power stats
+ * spans followed by the remainder that has not been stored yet.
+ */
+ public void aggregateAndDumpPowerStats(PrintWriter pw) {
+ if (mHandler.getLooper().isCurrentThread()) {
+ throw new IllegalStateException("Should not be executed on the bg handler thread.");
+ }
+
+ schedulePowerStatsAggregation();
+
+ // Wait for the aggregation process to finish storing aggregated stats spans in the store.
+ awaitCompletion();
+
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ mHandler.post(() -> {
+ mPowerStatsStore.dump(ipw);
+ // Aggregate the remainder of power stats and dump the results without storing them yet.
+ long powerStoreEndMonotonicTime = getLastSavedSpanEndMonotonicTime();
+ mPowerStatsAggregator.aggregatePowerStats(powerStoreEndMonotonicTime, 0,
+ stats -> {
+ // Create a PowerStatsSpan for consistency of the textual output
+ PowerStatsSpan span = PowerStatsStore.createPowerStatsSpan(stats);
+ if (span != null) {
+ span.dump(ipw);
+ }
+ });
+ });
+
+ awaitCompletion();
+ }
+
+ /**
+ * Align the supplied time to the wall clock, for aesthetic purposes. For example, if
+ * the schedule is configured with a 15-min interval, the captured aggregated stats will
+ * be for spans XX:00-XX:15, XX:15-XX:30, XX:30-XX:45 and XX:45-XX:60. Only the current
+ * time is used for the alignment, so if the wall clock changed during an aggregation span,
+ * or if the device was off (which stops the monotonic clock), the alignment may be
+ * temporarily broken.
+ */
+ @VisibleForTesting
+ public static long alignToWallClock(long targetMonotonicTime, long interval,
+ long currentMonotonicTime, long currentTimeMillis) {
+
+ // Estimate the wall clock time for the requested targetMonotonicTime
+ long targetWallClockTime = currentTimeMillis + (targetMonotonicTime - currentMonotonicTime);
+
+ if (interval >= MINUTE_IN_MILLIS && TimeUnit.HOURS.toMillis(1) % interval == 0) {
+ // If the interval is a divisor of an hour, e.g. 10 minutes, 15 minutes, etc
+
+ // First, round up to the next whole minute
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(targetWallClockTime + MINUTE_IN_MILLIS - 1);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+
+ // Now set the minute to a multiple of the requested interval
+ int intervalInMinutes = (int) (interval / MINUTE_IN_MILLIS);
+ cal.set(Calendar.MINUTE,
+ ((cal.get(Calendar.MINUTE) + intervalInMinutes - 1) / intervalInMinutes)
+ * intervalInMinutes);
+
+ long adjustment = cal.getTimeInMillis() - targetWallClockTime;
+ return targetMonotonicTime + adjustment;
+ } else if (interval >= HOUR_IN_MILLIS && TimeUnit.DAYS.toMillis(1) % interval == 0) {
+ // If the interval is a divisor of a day, e.g. 2h, 3h, etc
+
+ // First, round up to the next whole hour
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(targetWallClockTime + HOUR_IN_MILLIS - 1);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+
+ // Now set the hour of day to a multiple of the requested interval
+ int intervalInHours = (int) (interval / HOUR_IN_MILLIS);
+ cal.set(Calendar.HOUR_OF_DAY,
+ ((cal.get(Calendar.HOUR_OF_DAY) + intervalInHours - 1) / intervalInHours)
+ * intervalInHours);
+
+ long adjustment = cal.getTimeInMillis() - targetWallClockTime;
+ return targetMonotonicTime + adjustment;
+ }
+
+ return targetMonotonicTime;
+ }
+
+ private long getLastSavedSpanEndMonotonicTime() {
+ if (mLastSavedSpanEndMonotonicTime != 0) {
+ return mLastSavedSpanEndMonotonicTime;
+ }
+
+ for (PowerStatsSpan.Metadata metadata : mPowerStatsStore.getTableOfContents()) {
+ if (metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) {
+ for (PowerStatsSpan.TimeFrame timeFrame : metadata.getTimeFrames()) {
+ long endMonotonicTime = timeFrame.startMonotonicTime + timeFrame.duration;
+ if (endMonotonicTime > mLastSavedSpanEndMonotonicTime) {
+ mLastSavedSpanEndMonotonicTime = endMonotonicTime;
+ }
+ }
+ }
+ }
+ return mLastSavedSpanEndMonotonicTime;
+ }
+
+ private void storeAggregatedPowerStats(AggregatedPowerStats stats) {
+ mPowerStatsStore.storeAggregatedPowerStats(stats);
+ }
+
+ private void storeBatteryUsageStatsOnReset(int resetReason) {
+ if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE) {
+ return;
+ }
+
+ final BatteryUsageStats batteryUsageStats =
+ mBatteryUsageStatsProvider.getBatteryUsageStats(
+ new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
+ .includePowerModels()
+ .includeProcessStateData()
+ .build());
+
+ // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end
+ // Once that change is made, we will be able to use the BatteryUsageStats' monotonic
+ // start time
+ long monotonicStartTime =
+ mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration();
+ mHandler.post(() ->
+ mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats));
+ }
+
+ private void awaitCompletion() {
+ ConditionVariable done = new ConditionVariable();
+ mHandler.post(done::open);
+ done.block();
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
new file mode 100644
index 0000000..3b260ca
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
@@ -0,0 +1,433 @@
+/*
+ * 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.power.stats;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import com.google.android.collect.Sets;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Contains power stats of various kinds, aggregated over a time span.
+ */
+public class PowerStatsSpan {
+ private static final String TAG = "PowerStatsStore";
+
+ /**
+ * Increment VERSION when the XML format of the store changes. Also, update
+ * {@link #isCompatibleXmlFormat} to return true for all legacy versions
+ * that are compatible with the new one.
+ */
+ private static final int VERSION = 1;
+
+ private static final String XML_TAG_METADATA = "metadata";
+ private static final String XML_ATTR_ID = "id";
+ private static final String XML_ATTR_VERSION = "version";
+ private static final String XML_TAG_TIMEFRAME = "timeframe";
+ private static final String XML_ATTR_MONOTONIC = "monotonic";
+ private static final String XML_ATTR_START_TIME = "start";
+ private static final String XML_ATTR_DURATION = "duration";
+ private static final String XML_TAG_SECTION = "section";
+ private static final String XML_ATTR_SECTION_TYPE = "type";
+
+ private static final DateTimeFormatter DATE_FORMAT =
+ DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+
+ static class TimeFrame {
+ public final long startMonotonicTime;
+ @CurrentTimeMillisLong
+ public final long startTime;
+ @DurationMillisLong
+ public final long duration;
+
+ TimeFrame(long startMonotonicTime, @CurrentTimeMillisLong long startTime,
+ @DurationMillisLong long duration) {
+ this.startMonotonicTime = startMonotonicTime;
+ this.startTime = startTime;
+ this.duration = duration;
+ }
+
+ void write(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(null, XML_TAG_TIMEFRAME);
+ serializer.attributeLong(null, XML_ATTR_START_TIME, startTime);
+ serializer.attributeLong(null, XML_ATTR_MONOTONIC, startMonotonicTime);
+ serializer.attributeLong(null, XML_ATTR_DURATION, duration);
+ serializer.endTag(null, XML_TAG_TIMEFRAME);
+ }
+
+ static TimeFrame read(TypedXmlPullParser parser) throws XmlPullParserException {
+ return new TimeFrame(
+ parser.getAttributeLong(null, XML_ATTR_MONOTONIC),
+ parser.getAttributeLong(null, XML_ATTR_START_TIME),
+ parser.getAttributeLong(null, XML_ATTR_DURATION));
+ }
+
+ /**
+ * Prints the contents of this TimeFrame.
+ */
+ public void dump(IndentingPrintWriter pw) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(DATE_FORMAT.format(Instant.ofEpochMilli(startTime)))
+ .append(" (monotonic=").append(startMonotonicTime).append(") ")
+ .append(" duration=");
+ String durationString = TimeUtils.formatDuration(duration);
+ if (durationString.startsWith("+")) {
+ sb.append(durationString.substring(1));
+ } else {
+ sb.append(durationString);
+ }
+ pw.print(sb);
+ }
+ }
+
+ static class Metadata {
+ static final Comparator<Metadata> COMPARATOR = Comparator.comparing(Metadata::getId);
+
+ private final long mId;
+ private final List<TimeFrame> mTimeFrames = new ArrayList<>();
+ private final List<String> mSections = new ArrayList<>();
+
+ Metadata(long id) {
+ mId = id;
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ public List<TimeFrame> getTimeFrames() {
+ return mTimeFrames;
+ }
+
+ public List<String> getSections() {
+ return mSections;
+ }
+
+ void addTimeFrame(TimeFrame timeFrame) {
+ mTimeFrames.add(timeFrame);
+ }
+
+ void addSection(String sectionType) {
+ // The number of sections per span is small, so there is no need to use a Set
+ if (!mSections.contains(sectionType)) {
+ mSections.add(sectionType);
+ }
+ }
+
+ void write(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(null, XML_TAG_METADATA);
+ serializer.attributeLong(null, XML_ATTR_ID, mId);
+ serializer.attributeInt(null, XML_ATTR_VERSION, VERSION);
+ for (TimeFrame timeFrame : mTimeFrames) {
+ timeFrame.write(serializer);
+ }
+ for (String section : mSections) {
+ serializer.startTag(null, XML_TAG_SECTION);
+ serializer.attribute(null, XML_ATTR_SECTION_TYPE, section);
+ serializer.endTag(null, XML_TAG_SECTION);
+ }
+ serializer.endTag(null, XML_TAG_METADATA);
+ }
+
+ /**
+ * Reads just the header of the XML file containing metadata.
+ * Returns null if the file does not contain a compatible <metadata> element.
+ */
+ @Nullable
+ public static Metadata read(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ Metadata metadata = null;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG
+ && parser.getName().equals(XML_TAG_METADATA))) {
+ if (eventType == XmlPullParser.START_TAG) {
+ String tagName = parser.getName();
+ if (tagName.equals(XML_TAG_METADATA)) {
+ int version = parser.getAttributeInt(null, XML_ATTR_VERSION);
+ if (!isCompatibleXmlFormat(version)) {
+ Slog.e(TAG,
+ "Incompatible version " + version + "; expected " + VERSION);
+ return null;
+ }
+
+ long id = parser.getAttributeLong(null, XML_ATTR_ID);
+ metadata = new Metadata(id);
+ } else if (metadata != null && tagName.equals(XML_TAG_TIMEFRAME)) {
+ metadata.addTimeFrame(TimeFrame.read(parser));
+ } else if (metadata != null && tagName.equals(XML_TAG_SECTION)) {
+ metadata.addSection(parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE));
+ }
+ }
+ eventType = parser.next();
+ }
+ return metadata;
+ }
+
+ /**
+ * Prints the metadata.
+ */
+ public void dump(IndentingPrintWriter pw) {
+ dump(pw, true);
+ }
+
+ void dump(IndentingPrintWriter pw, boolean includeSections) {
+ pw.print("Span ");
+ if (mTimeFrames.size() > 0) {
+ mTimeFrames.get(0).dump(pw);
+ pw.println();
+ }
+
+ // Sometimes, when the wall clock is adjusted in the middle of a stats session,
+ // we will have more than one time frame.
+ for (int i = 1; i < mTimeFrames.size(); i++) {
+ TimeFrame timeFrame = mTimeFrames.get(i);
+ pw.print(" "); // Aligned below "Span "
+ timeFrame.dump(pw);
+ pw.println();
+ }
+
+ if (includeSections) {
+ pw.increaseIndent();
+ for (String section : mSections) {
+ pw.print("section", section);
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringWriter sw = new StringWriter();
+ IndentingPrintWriter ipw = new IndentingPrintWriter(sw);
+ ipw.print("id", mId);
+ for (int i = 0; i < mTimeFrames.size(); i++) {
+ TimeFrame timeFrame = mTimeFrames.get(i);
+ ipw.print("timeframe=[");
+ timeFrame.dump(ipw);
+ ipw.print("] ");
+ }
+ for (String section : mSections) {
+ ipw.print("section", section);
+ }
+ ipw.flush();
+ return sw.toString().trim();
+ }
+ }
+
+ /**
+ * Contains a specific type of aggregate power stats. The contents type is determined by
+ * the section type.
+ */
+ public abstract static class Section {
+ private final String mType;
+
+ Section(String type) {
+ mType = type;
+ }
+
+ /**
+ * Returns the section type, which determines the type of data stored in the corresponding
+ * section of {@link PowerStatsSpan}
+ */
+ public String getType() {
+ return mType;
+ }
+
+ abstract void write(TypedXmlSerializer serializer) throws IOException;
+
+ /**
+ * Prints the section type.
+ */
+ public void dump(IndentingPrintWriter ipw) {
+ ipw.println(mType);
+ }
+ }
+
+ /**
+ * A universal XML parser for {@link PowerStatsSpan.Section}'s. It is aware of all
+ * supported section types as well as their corresponding XML formats.
+ */
+ public interface SectionReader {
+ /**
+ * Reads the contents of the section using the parser. The type of the object
+ * read and the corresponding XML format are determined by the section type.
+ */
+ Section read(String sectionType, TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException;
+ }
+
+ private final Metadata mMetadata;
+ private final List<Section> mSections = new ArrayList<>();
+
+ public PowerStatsSpan(long id) {
+ this(new Metadata(id));
+ }
+
+ private PowerStatsSpan(Metadata metadata) {
+ mMetadata = metadata;
+ }
+
+ public Metadata getMetadata() {
+ return mMetadata;
+ }
+
+ public long getId() {
+ return mMetadata.mId;
+ }
+
+ void addTimeFrame(long monotonicTime, @CurrentTimeMillisLong long wallClockTime,
+ @DurationMillisLong long duration) {
+ mMetadata.mTimeFrames.add(new TimeFrame(monotonicTime, wallClockTime, duration));
+ }
+
+ void addSection(Section section) {
+ mMetadata.addSection(section.getType());
+ mSections.add(section);
+ }
+
+ @NonNull
+ public List<Section> getSections() {
+ return mSections;
+ }
+
+ private static boolean isCompatibleXmlFormat(int version) {
+ return version == VERSION;
+ }
+
+ /**
+ * Creates an XML file containing the persistent state of the power stats span.
+ */
+ @VisibleForTesting
+ public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
+ serializer.setOutput(out, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ mMetadata.write(serializer);
+ for (Section section : mSections) {
+ serializer.startTag(null, XML_TAG_SECTION);
+ serializer.attribute(null, XML_ATTR_SECTION_TYPE, section.mType);
+ section.write(serializer);
+ serializer.endTag(null, XML_TAG_SECTION);
+ }
+ serializer.endDocument();
+ }
+
+ @Nullable
+ static PowerStatsSpan read(InputStream in, TypedXmlPullParser parser,
+ SectionReader sectionReader, String... sectionTypes)
+ throws IOException, XmlPullParserException {
+ Set<String> neededSections = Sets.newArraySet(sectionTypes);
+ boolean selectSections = !neededSections.isEmpty();
+ parser.setInput(in, StandardCharsets.UTF_8.name());
+
+ Metadata metadata = Metadata.read(parser);
+ if (metadata == null) {
+ return null;
+ }
+
+ PowerStatsSpan span = new PowerStatsSpan(metadata);
+ boolean skipSection = false;
+ int nestingLevel = 0;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (skipSection) {
+ if (eventType == XmlPullParser.END_TAG
+ && parser.getName().equals(XML_TAG_SECTION)) {
+ nestingLevel--;
+ if (nestingLevel == 0) {
+ skipSection = false;
+ }
+ } else if (eventType == XmlPullParser.START_TAG
+ && parser.getName().equals(XML_TAG_SECTION)) {
+ nestingLevel++;
+ }
+ } else if (eventType == XmlPullParser.START_TAG) {
+ String tag = parser.getName();
+ if (tag.equals(XML_TAG_SECTION)) {
+ String sectionType = parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE);
+ if (!selectSections || neededSections.contains(sectionType)) {
+ Section section = sectionReader.read(sectionType, parser);
+ if (section == null) {
+ if (selectSections) {
+ throw new XmlPullParserException(
+ "Unsupported PowerStatsStore section type: " + sectionType);
+ } else {
+ section = new Section(sectionType) {
+ @Override
+ public void dump(IndentingPrintWriter ipw) {
+ ipw.println("Unsupported PowerStatsStore section type: "
+ + sectionType);
+ }
+
+ @Override
+ void write(TypedXmlSerializer serializer) {
+ }
+ };
+ }
+ }
+ span.addSection(section);
+ } else {
+ skipSection = true;
+ }
+ } else if (tag.equals(XML_TAG_METADATA)) {
+ Metadata.read(parser);
+ }
+ }
+ eventType = parser.next();
+ }
+ return span;
+ }
+
+ /**
+ * Prints the contents of this power stats span.
+ */
+ public void dump(IndentingPrintWriter ipw) {
+ mMetadata.dump(ipw, /* includeSections */ false);
+ for (Section section : mSections) {
+ ipw.increaseIndent();
+ ipw.println(section.mType);
+ section.dump(ipw);
+ ipw.decreaseIndent();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsStore.java b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
new file mode 100644
index 0000000..7123bcb
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
@@ -0,0 +1,371 @@
+/*
+ * 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.power.stats;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.BatteryUsageStats;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.util.AtomicFile;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A storage mechanism for aggregated power/battery stats.
+ */
+public class PowerStatsStore {
+ private static final String TAG = "PowerStatsStore";
+
+ private static final String POWER_STATS_DIR = "power-stats";
+ private static final String POWER_STATS_SPAN_FILE_EXTENSION = ".pss";
+ private static final String DIR_LOCK_FILENAME = ".lock";
+ private static final long MAX_POWER_STATS_SPAN_STORAGE_BYTES = 100 * 1024;
+
+ private final File mSystemDir;
+ private final File mStoreDir;
+ private final File mLockFile;
+ private final ReentrantLock mFileLock = new ReentrantLock();
+ private FileLock mJvmLock;
+ private final long mMaxStorageBytes;
+ private final Handler mHandler;
+ private final PowerStatsSpan.SectionReader mSectionReader;
+ private volatile List<PowerStatsSpan.Metadata> mTableOfContents;
+
+ public PowerStatsStore(@NonNull File systemDir, Handler handler,
+ AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+ this(systemDir, MAX_POWER_STATS_SPAN_STORAGE_BYTES, handler,
+ new DefaultSectionReader(aggregatedPowerStatsConfig));
+ }
+
+ @VisibleForTesting
+ public PowerStatsStore(@NonNull File systemDir, long maxStorageBytes, Handler handler,
+ @NonNull PowerStatsSpan.SectionReader sectionReader) {
+ mSystemDir = systemDir;
+ mStoreDir = new File(systemDir, POWER_STATS_DIR);
+ mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME);
+ mHandler = handler;
+ mMaxStorageBytes = maxStorageBytes;
+ mSectionReader = sectionReader;
+ mHandler.post(this::maybeClearLegacyStore);
+ }
+
+ /**
+ * Returns the metadata for all {@link PowerStatsSpan}'s contained in the store.
+ */
+ @NonNull
+ public List<PowerStatsSpan.Metadata> getTableOfContents() {
+ List<PowerStatsSpan.Metadata> toc = mTableOfContents;
+ if (toc != null) {
+ return toc;
+ }
+
+ TypedXmlPullParser parser = Xml.newBinaryPullParser();
+ lockStoreDirectory();
+ try {
+ toc = new ArrayList<>();
+ for (File file : mStoreDir.listFiles()) {
+ String fileName = file.getName();
+ if (!fileName.endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) {
+ continue;
+ }
+ try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
+ parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+ PowerStatsSpan.Metadata metadata = PowerStatsSpan.Metadata.read(parser);
+ if (metadata != null) {
+ toc.add(metadata);
+ } else {
+ Slog.e(TAG, "Removing incompatible PowerStatsSpan file: " + fileName);
+ file.delete();
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + fileName);
+ }
+ }
+ toc.sort(PowerStatsSpan.Metadata.COMPARATOR);
+ mTableOfContents = Collections.unmodifiableList(toc);
+ } finally {
+ unlockStoreDirectory();
+ }
+
+ return toc;
+ }
+
+ /**
+ * Saves the specified span in the store.
+ */
+ public void storePowerStatsSpan(PowerStatsSpan span) {
+ maybeClearLegacyStore();
+ lockStoreDirectory();
+ try {
+ if (!mStoreDir.exists()) {
+ if (!mStoreDir.mkdirs()) {
+ Slog.e(TAG, "Could not create a directory for power stats store");
+ return;
+ }
+ }
+
+ AtomicFile file = new AtomicFile(makePowerStatsSpanFilename(span.getId()));
+ file.write(out-> {
+ try {
+ span.writeXml(out, Xml.newBinarySerializer());
+ } catch (Exception e) {
+ // AtomicFile will log the exception and delete the file.
+ throw new RuntimeException(e);
+ }
+ });
+ mTableOfContents = null;
+ removeOldSpansLocked();
+ } finally {
+ unlockStoreDirectory();
+ }
+ }
+
+ /**
+ * Loads the PowerStatsSpan identified by its ID. Only loads the sections with
+ * the specified types. Loads all sections if no sectionTypes is empty.
+ */
+ @Nullable
+ public PowerStatsSpan loadPowerStatsSpan(long id, String... sectionTypes) {
+ TypedXmlPullParser parser = Xml.newBinaryPullParser();
+ lockStoreDirectory();
+ try {
+ File file = makePowerStatsSpanFilename(id);
+ try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
+ return PowerStatsSpan.read(inputStream, parser, mSectionReader, sectionTypes);
+ } catch (IOException | XmlPullParserException e) {
+ Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + file);
+ }
+ } finally {
+ unlockStoreDirectory();
+ }
+ return null;
+ }
+
+ void storeAggregatedPowerStats(AggregatedPowerStats stats) {
+ PowerStatsSpan span = createPowerStatsSpan(stats);
+ if (span == null) {
+ return;
+ }
+ storePowerStatsSpan(span);
+ }
+
+ static PowerStatsSpan createPowerStatsSpan(AggregatedPowerStats stats) {
+ List<AggregatedPowerStats.ClockUpdate> clockUpdates = stats.getClockUpdates();
+ if (clockUpdates.isEmpty()) {
+ Slog.w(TAG, "No clock updates in aggregated power stats " + stats);
+ return null;
+ }
+
+ long monotonicTime = clockUpdates.get(0).monotonicTime;
+ long durationSum = 0;
+ PowerStatsSpan span = new PowerStatsSpan(monotonicTime);
+ for (int i = 0; i < clockUpdates.size(); i++) {
+ AggregatedPowerStats.ClockUpdate clockUpdate = clockUpdates.get(i);
+ long duration;
+ if (i == clockUpdates.size() - 1) {
+ duration = stats.getDuration() - durationSum;
+ } else {
+ duration = clockUpdate.monotonicTime - monotonicTime;
+ }
+ span.addTimeFrame(clockUpdate.monotonicTime, clockUpdate.currentTime, duration);
+ monotonicTime = clockUpdate.monotonicTime;
+ durationSum += duration;
+ }
+
+ span.addSection(new AggregatedPowerStatsSection(stats));
+ return span;
+ }
+
+ /**
+ * Stores a {@link PowerStatsSpan} containing a single section for the supplied
+ * battery usage stats.
+ */
+ public void storeBatteryUsageStats(long monotonicStartTime,
+ BatteryUsageStats batteryUsageStats) {
+ PowerStatsSpan span = new PowerStatsSpan(monotonicStartTime);
+ span.addTimeFrame(monotonicStartTime, batteryUsageStats.getStatsStartTimestamp(),
+ batteryUsageStats.getStatsDuration());
+ span.addSection(new BatteryUsageStatsSection(batteryUsageStats));
+ storePowerStatsSpan(span);
+ }
+
+ /**
+ * Creates a file name by formatting the span ID as a 19-digit zero-padded number.
+ * This ensures that the lexicographically sorted directory follows the chronological order.
+ */
+ private File makePowerStatsSpanFilename(long id) {
+ return new File(mStoreDir, String.format(Locale.ENGLISH, "%019d", id)
+ + POWER_STATS_SPAN_FILE_EXTENSION);
+ }
+
+ private void maybeClearLegacyStore() {
+ File legacyStoreDir = new File(mSystemDir, "battery-usage-stats");
+ if (legacyStoreDir.exists()) {
+ FileUtils.deleteContentsAndDir(legacyStoreDir);
+ }
+ }
+
+ private void lockStoreDirectory() {
+ mFileLock.lock();
+
+ // Lock the directory from access by other JVMs
+ try {
+ mLockFile.getParentFile().mkdirs();
+ mLockFile.createNewFile();
+ mJvmLock = FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock();
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot lock snapshot directory", e);
+ }
+ }
+
+ private void unlockStoreDirectory() {
+ try {
+ mJvmLock.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot unlock snapshot directory", e);
+ } finally {
+ mFileLock.unlock();
+ }
+ }
+
+ private void removeOldSpansLocked() {
+ // Read the directory list into a _sorted_ map. The alphanumeric ordering
+ // corresponds to the historical order of snapshots because the file names
+ // are timestamps zero-padded to the same length.
+ long totalSize = 0;
+ TreeMap<File, Long> mFileSizes = new TreeMap<>();
+ for (File file : mStoreDir.listFiles()) {
+ final long fileSize = file.length();
+ totalSize += fileSize;
+ if (file.getName().endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) {
+ mFileSizes.put(file, fileSize);
+ }
+ }
+
+ while (totalSize > mMaxStorageBytes) {
+ final Map.Entry<File, Long> entry = mFileSizes.firstEntry();
+ if (entry == null) {
+ break;
+ }
+
+ File file = entry.getKey();
+ if (!file.delete()) {
+ Slog.e(TAG, "Cannot delete power stats span " + file);
+ }
+ totalSize -= entry.getValue();
+ mFileSizes.remove(file);
+ mTableOfContents = null;
+ }
+ }
+
+ /**
+ * Deletes all contents from the store.
+ */
+ public void reset() {
+ lockStoreDirectory();
+ try {
+ for (File file : mStoreDir.listFiles()) {
+ if (file.getName().endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) {
+ if (!file.delete()) {
+ Slog.e(TAG, "Cannot delete power stats span " + file);
+ }
+ }
+ }
+ mTableOfContents = List.of();
+ } finally {
+ unlockStoreDirectory();
+ }
+ }
+
+ /**
+ * Prints the summary of contents of the store: only metadata, but not the actual stored
+ * objects.
+ */
+ public void dumpTableOfContents(IndentingPrintWriter ipw) {
+ ipw.println("Power stats store TOC");
+ ipw.increaseIndent();
+ List<PowerStatsSpan.Metadata> contents = getTableOfContents();
+ for (PowerStatsSpan.Metadata metadata : contents) {
+ metadata.dump(ipw);
+ }
+ ipw.decreaseIndent();
+ }
+
+ /**
+ * Prints the contents of the store.
+ */
+ public void dump(IndentingPrintWriter ipw) {
+ ipw.println("Power stats store");
+ ipw.increaseIndent();
+ List<PowerStatsSpan.Metadata> contents = getTableOfContents();
+ for (PowerStatsSpan.Metadata metadata : contents) {
+ PowerStatsSpan span = loadPowerStatsSpan(metadata.getId());
+ if (span != null) {
+ span.dump(ipw);
+ }
+ }
+ ipw.decreaseIndent();
+ }
+
+ private static class DefaultSectionReader implements PowerStatsSpan.SectionReader {
+ private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+
+ DefaultSectionReader(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+ mAggregatedPowerStatsConfig = aggregatedPowerStatsConfig;
+ }
+
+ @Override
+ public PowerStatsSpan.Section read(String sectionType, TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ switch (sectionType) {
+ case AggregatedPowerStatsSection.TYPE:
+ return new AggregatedPowerStatsSection(
+ AggregatedPowerStats.createFromXml(parser,
+ mAggregatedPowerStatsConfig));
+ case BatteryUsageStatsSection.TYPE:
+ return new BatteryUsageStatsSection(
+ BatteryUsageStats.createFromXml(parser));
+ default:
+ return null;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index d61bebc..0f13571 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -1,8 +1,15 @@
package: "com.android.server.power.optimization"
flag {
+ name: "power_monitor_api"
+ namespace: "backstage_power"
+ description: "Feature flag for ODPM API"
+ bug: "295027807"
+}
+
+flag {
name: "streamlined_battery_stats"
- namespace: "power_optimization"
+ namespace: "backstage_power"
description: "Feature flag for streamlined battery stats"
bug: "285646152"
}
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index 5609f69..77290fd 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -694,7 +694,7 @@
Log.d(TAG, String.format(Locale.ENGLISH,
"Monitor=%s timestamp=%d energy=%d"
+ " uid=%d noise=%.1f%% returned=%d",
- state.powerMonitor.name,
+ state.powerMonitor.getName(),
state.timestampMs,
state.energyUws,
callingUid,
@@ -728,7 +728,7 @@
}
for (PowerMonitorState powerMonitorState : powerMonitorStates) {
- if (powerMonitorState.powerMonitor.type
+ if (powerMonitorState.powerMonitor.getType()
== PowerMonitor.POWER_MONITOR_TYPE_CONSUMER) {
for (EnergyConsumerResult energyConsumerResult : energyConsumerResults) {
if (energyConsumerResult.id == powerMonitorState.id) {
@@ -754,7 +754,7 @@
}
for (PowerMonitorState powerMonitorState : powerMonitorStates) {
- if (powerMonitorState.powerMonitor.type
+ if (powerMonitorState.powerMonitor.getType()
== PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
for (EnergyMeasurement energyMeasurement : energyMeasurements) {
if (energyMeasurement.id == powerMonitorState.id) {
@@ -773,7 +773,7 @@
@PowerMonitor.PowerMonitorType int type) {
int count = 0;
for (PowerMonitorState monitorState : powerMonitorStates) {
- if (monitorState.powerMonitor.type == type) {
+ if (monitorState.powerMonitor.getType() == type) {
count++;
}
}
@@ -785,7 +785,7 @@
int[] ids = new int[count];
int index = 0;
for (PowerMonitorState monitorState : powerMonitorStates) {
- if (monitorState.powerMonitor.type == type) {
+ if (monitorState.powerMonitor.getType() == type) {
ids[index++] = monitorState.id;
}
}
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index c9db343..0656a6a 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -174,8 +174,6 @@
private CallStateHelper mCallStateHelper;
private KeyguardManager mKeyguardManager;
- private SafetyCenterManager mSafetyCenterManager;
-
private int mCurrentUser = USER_NULL;
public SensorPrivacyService(Context context) {
@@ -191,7 +189,6 @@
mTelephonyManager = context.getSystemService(TelephonyManager.class);
mPackageManagerInternal = getLocalService(PackageManagerInternal.class);
mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
- mSafetyCenterManager = mContext.getSystemService(SafetyCenterManager.class);
}
@Override
@@ -656,7 +653,9 @@
String contentTitle = getUiContext().getString(messageRes);
Spanned contentText = Html.fromHtml(getUiContext().getString(
R.string.sensor_privacy_start_use_notification_content_text, packageLabel), 0);
- String action = mSafetyCenterManager.isSafetyCenterEnabled()
+ SafetyCenterManager safetyCenterManager =
+ mContext.getSystemService(SafetyCenterManager.class);
+ String action = safetyCenterManager.isSafetyCenterEnabled()
? Settings.ACTION_PRIVACY_CONTROLS : Settings.ACTION_PRIVACY_SETTINGS;
PendingIntent contentIntent = PendingIntent.getActivity(mContext, sensor,
diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS
index 174ad3a..c33f3d9 100644
--- a/services/core/java/com/android/server/stats/OWNERS
+++ b/services/core/java/com/android/server/stats/OWNERS
@@ -1,11 +1,10 @@
jeffreyhuang@google.com
joeo@google.com
-jtnguyen@google.com
+monicamwang@google.com
muhammadq@google.com
+rayhdez@google.com
rslawik@google.com
-ruchirr@google.com
sharaienko@google.com
singhtejinder@google.com
tsaichristine@google.com
yaochen@google.com
-yro@google.com
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
index bfe34049e..9a9b836 100644
--- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -41,8 +41,8 @@
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.SystemService;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import com.android.server.pm.UserManagerInternal;
import java.io.ByteArrayInputStream;
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index a5c0fb3..cddc79d 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -1047,6 +1047,16 @@
// in use frontends when no available frontend has been found.
int priority = getFrontendHighestClientPriority(fr.getOwnerClientId());
if (currentLowestPriority > priority) {
+ // we need to check the max used num if the target frontend type is not
+ // currently in primary use (and simply blocked due to exclusive group)
+ ClientProfile targetOwnerProfile = getClientProfile(fr.getOwnerClientId());
+ int primaryFeId = targetOwnerProfile.getPrimaryFrontend();
+ FrontendResource primaryFe = getFrontendResource(primaryFeId);
+ if (fr.getType() != primaryFe.getType()
+ && isFrontendMaxNumUseReached(fr.getType())) {
+ continue;
+ }
+ // update the target frontend
inUseLowestPriorityFrHandle = fr.getHandle();
currentLowestPriority = priority;
isRequestFromSameProcess = (requestClient.getProcessId()
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
index e4f9607..a346216 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.content.res.Resources;
import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
import android.os.VibratorInfo;
import android.os.vibrator.persistence.ParsedVibration;
import android.os.vibrator.persistence.VibrationXmlParser;
@@ -127,6 +128,10 @@
VibrationXmlParser.VibrationXmlParserException,
XmlParserException,
XmlPullParserException {
+ if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) {
+ Slog.d(TAG, "Haptic feedback customization feature is not enabled.");
+ return null;
+ }
String customizationFile =
res.getString(
com.android.internal.R.string.config_hapticFeedbackCustomizationFile);
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index becbbf2..519acec 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -22,11 +22,10 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
import android.util.Slog;
import android.util.SparseArray;
import android.view.HapticFeedbackConstants;
-import android.view.flags.FeatureFlags;
-import android.view.flags.FeatureFlagsImpl;
import com.android.internal.annotations.VisibleForTesting;
@@ -56,7 +55,8 @@
// If present and valid, a vibration here will be used for an effect.
// Otherwise, the system's default vibration will be used.
@Nullable private final SparseArray<VibrationEffect> mHapticCustomizations;
- private final FeatureFlags mViewFeatureFlags;
+
+ private float mKeyboardVibrationFixedAmplitude;
/** @hide */
public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) {
@@ -65,16 +65,14 @@
/** @hide */
public HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo) {
- this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo),
- new FeatureFlagsImpl());
+ this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo));
}
/** @hide */
@VisibleForTesting HapticFeedbackVibrationProvider(
Resources res,
VibratorInfo vibratorInfo,
- @Nullable SparseArray<VibrationEffect> hapticCustomizations,
- FeatureFlags viewFeatureFlags) {
+ @Nullable SparseArray<VibrationEffect> hapticCustomizations) {
mVibratorInfo = vibratorInfo;
mHapticTextHandleEnabled = res.getBoolean(
com.android.internal.R.bool.config_enableHapticTextHandle);
@@ -83,14 +81,17 @@
hapticCustomizations = null;
}
mHapticCustomizations = hapticCustomizations;
- mViewFeatureFlags = viewFeatureFlags;
-
mSafeModeEnabledVibrationEffect =
effectHasCustomization(HapticFeedbackConstants.SAFE_MODE_ENABLED)
? mHapticCustomizations.get(HapticFeedbackConstants.SAFE_MODE_ENABLED)
: VibrationSettings.createEffectFromResource(
res,
com.android.internal.R.array.config_safeModeEnabledVibePattern);
+ mKeyboardVibrationFixedAmplitude = res.getFloat(
+ com.android.internal.R.dimen.config_keyboardHapticFeedbackFixedAmplitude);
+ if (mKeyboardVibrationFixedAmplitude < 0 || mKeyboardVibrationFixedAmplitude > 1) {
+ mKeyboardVibrationFixedAmplitude = -1;
+ }
}
/**
@@ -120,6 +121,9 @@
return getVibration(effectId, VibrationEffect.EFFECT_TEXTURE_TICK);
case HapticFeedbackConstants.KEYBOARD_RELEASE:
+ case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
+ return getKeyboardVibration(effectId);
+
case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
case HapticFeedbackConstants.ENTRY_BUMP:
case HapticFeedbackConstants.DRAG_CROSSING:
@@ -128,7 +132,6 @@
VibrationEffect.EFFECT_TICK,
/* fallbackForPredefinedEffect= */ false);
- case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
case HapticFeedbackConstants.VIRTUAL_KEY:
case HapticFeedbackConstants.EDGE_RELEASE:
case HapticFeedbackConstants.CALENDAR_DATE:
@@ -204,6 +207,10 @@
case HapticFeedbackConstants.SCROLL_LIMIT:
attrs = HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
break;
+ case HapticFeedbackConstants.KEYBOARD_TAP:
+ case HapticFeedbackConstants.KEYBOARD_RELEASE:
+ attrs = createKeyboardVibrationAttributes();
+ break;
default:
attrs = TOUCH_VIBRATION_ATTRIBUTES;
}
@@ -212,9 +219,12 @@
if (bypassVibrationIntensitySetting) {
flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
}
- if (shouldBypassInterruptionPolicy(effectId, mViewFeatureFlags)) {
+ if (shouldBypassInterruptionPolicy(effectId)) {
flags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
}
+ if (shouldBypassIntensityScale(effectId)) {
+ flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
+ }
return flags == 0 ? attrs : new VibrationAttributes.Builder(attrs).setFlags(flags).build();
}
@@ -295,6 +305,64 @@
return mHapticCustomizations != null && mHapticCustomizations.contains(effectId);
}
+ private VibrationEffect getKeyboardVibration(int effectId) {
+ if (effectHasCustomization(effectId)) {
+ return mHapticCustomizations.get(effectId);
+ }
+
+ int primitiveId;
+ int predefinedEffectId;
+ boolean predefinedEffectFallback;
+
+ switch (effectId) {
+ case HapticFeedbackConstants.KEYBOARD_RELEASE:
+ primitiveId = VibrationEffect.Composition.PRIMITIVE_TICK;
+ predefinedEffectId = VibrationEffect.EFFECT_TICK;
+ predefinedEffectFallback = false;
+ break;
+ case HapticFeedbackConstants.KEYBOARD_TAP:
+ default:
+ primitiveId = VibrationEffect.Composition.PRIMITIVE_CLICK;
+ predefinedEffectId = VibrationEffect.EFFECT_CLICK;
+ predefinedEffectFallback = true;
+ }
+ if (Flags.keyboardCategoryEnabled() && mKeyboardVibrationFixedAmplitude > 0) {
+ if (mVibratorInfo.isPrimitiveSupported(primitiveId)) {
+ return VibrationEffect.startComposition()
+ .addPrimitive(primitiveId, mKeyboardVibrationFixedAmplitude)
+ .compose();
+ }
+ }
+ return getVibration(effectId, predefinedEffectId,
+ /* fallbackForPredefinedEffect= */ predefinedEffectFallback);
+ }
+
+ private boolean shouldBypassIntensityScale(int effectId) {
+ if (!Flags.keyboardCategoryEnabled() || mKeyboardVibrationFixedAmplitude < 0) {
+ // shouldn't bypass if not support keyboard category or no fixed amplitude
+ return false;
+ }
+ switch (effectId) {
+ case HapticFeedbackConstants.KEYBOARD_TAP:
+ return mVibratorInfo.isPrimitiveSupported(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
+ case HapticFeedbackConstants.KEYBOARD_RELEASE:
+ return mVibratorInfo.isPrimitiveSupported(
+ VibrationEffect.Composition.PRIMITIVE_TICK);
+ }
+ return false;
+ }
+
+ private static VibrationAttributes createKeyboardVibrationAttributes() {
+ if (!Flags.keyboardCategoryEnabled()) {
+ return TOUCH_VIBRATION_ATTRIBUTES;
+ }
+
+ return new VibrationAttributes.Builder(TOUCH_VIBRATION_ATTRIBUTES)
+ .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+ .build();
+ }
+
@Nullable
private static SparseArray<VibrationEffect> loadHapticCustomizations(
Resources res, VibratorInfo vibratorInfo) {
@@ -306,8 +374,7 @@
}
}
- private static boolean shouldBypassInterruptionPolicy(
- int effectId, FeatureFlags viewFeatureFlags) {
+ private static boolean shouldBypassInterruptionPolicy(int effectId) {
switch (effectId) {
case HapticFeedbackConstants.SCROLL_TICK:
case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
@@ -315,7 +382,7 @@
// The SCROLL_* constants should bypass interruption filter, so that scroll haptics
// can play regardless of focus modes like DND. Guard this behavior by the feature
// flag controlling the general scroll feedback APIs.
- return viewFeatureFlags.scrollFeedbackApi();
+ return android.view.flags.Flags.scrollFeedbackApi();
default:
return false;
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index db8a9ae..7f55836 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import static android.os.VibrationAttributes.CATEGORY_KEYBOARD;
import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY;
import static android.os.VibrationAttributes.USAGE_ALARM;
import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
@@ -52,6 +53,7 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.Vibrator.VibrationIntensity;
+import android.os.vibrator.Flags;
import android.os.vibrator.VibrationConfig;
import android.provider.Settings;
import android.util.IndentingPrintWriter;
@@ -188,6 +190,8 @@
@GuardedBy("mLock")
private boolean mVibrateOn;
@GuardedBy("mLock")
+ private boolean mKeyboardVibrationOn;
+ @GuardedBy("mLock")
private int mRingerMode;
@GuardedBy("mLock")
private boolean mOnWirelessCharger;
@@ -295,6 +299,8 @@
Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY));
registerSettingsObserver(
Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY));
+ registerSettingsObserver(
+ Settings.System.getUriFor(Settings.System.KEYBOARD_VIBRATION_ENABLED));
if (mVibrationConfig.ignoreVibrationsOnWirelessCharger()) {
Intent batteryStatus = mContext.registerReceiver(
@@ -418,14 +424,9 @@
}
if (!callerInfo.attrs.isFlagSet(
- VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)) {
- if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) {
- return Vibration.Status.IGNORED_FOR_SETTINGS;
- }
-
- if (getCurrentIntensity(usage) == Vibrator.VIBRATION_INTENSITY_OFF) {
- return Vibration.Status.IGNORED_FOR_SETTINGS;
- }
+ VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
+ && !shouldVibrateForUserSetting(callerInfo)) {
+ return Vibration.Status.IGNORED_FOR_SETTINGS;
}
if (!callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
@@ -497,6 +498,30 @@
return mRingerMode != AudioManager.RINGER_MODE_SILENT;
}
+ /**
+ * Return {@code true} if the device should vibrate for user setting, and
+ * {@code false} to ignore the vibration.
+ */
+ @GuardedBy("mLock")
+ private boolean shouldVibrateForUserSetting(Vibration.CallerInfo callerInfo) {
+ final int usage = callerInfo.attrs.getUsage();
+ if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) {
+ // Main setting disabled.
+ return false;
+ }
+
+ if (Flags.keyboardCategoryEnabled()) {
+ int category = callerInfo.attrs.getCategory();
+ if (usage == USAGE_TOUCH && category == CATEGORY_KEYBOARD) {
+ // Keyboard touch has a different user setting.
+ return mKeyboardVibrationOn;
+ }
+ }
+
+ // Apply individual user setting based on usage.
+ return getCurrentIntensity(usage) != Vibrator.VIBRATION_INTENSITY_OFF;
+ }
+
/** Update all cached settings and triggers registered listeners. */
void update() {
updateSettings();
@@ -508,6 +533,8 @@
synchronized (mLock) {
mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0;
mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1) > 0;
+ mKeyboardVibrationOn = loadSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED,
+ mVibrationConfig.isDefaultKeyboardVibrationEnabled() ? 1 : 0) > 0;
int alarmIntensity = toIntensity(
loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1),
@@ -806,18 +833,24 @@
private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
public boolean isUidForeground(int uid) {
- return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
- <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ synchronized (this) {
+ return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
+ <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ }
}
@Override
public void onUidGone(int uid, boolean disabled) {
- mProcStatesCache.delete(uid);
+ synchronized (this) {
+ mProcStatesCache.delete(uid);
+ }
}
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
- mProcStatesCache.put(uid, procState);
+ synchronized (this) {
+ mProcStatesCache.put(uid, procState);
+ }
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 45bd152..ace7777 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -97,7 +97,8 @@
new VibrationAttributes.Builder().build();
private static final int ATTRIBUTES_ALL_BYPASS_FLAGS =
VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
- | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
+ | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF
+ | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
/** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */
private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;
@@ -771,8 +772,11 @@
private Vibration.EndInfo startVibrationLocked(HalVibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
- // Scale effect before dispatching it to the input devices or the vibration thread.
- vib.scaleEffects(mVibrationScaler::scale);
+ if (!vib.callerInfo.attrs.isFlagSet(
+ VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
+ // Scale effect before dispatching it to the input devices or the vibration thread.
+ vib.scaleEffects(mVibrationScaler::scale);
+ }
boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
vib.callerInfo, vib.getEffectToPlay());
if (inputDevicesAvailable) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 01ea33f..c7a3c43 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -51,6 +51,7 @@
import android.app.IWallpaperManager;
import android.app.IWallpaperManagerCallback;
import android.app.PendingIntent;
+import android.app.UidObserver;
import android.app.UserSwitchObserver;
import android.app.WallpaperColors;
import android.app.WallpaperInfo;
@@ -103,6 +104,7 @@
import android.system.ErrnoException;
import android.system.Os;
import android.util.EventLog;
+import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -120,6 +122,7 @@
import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.TimingsTraceAndSlog;
+import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import org.xmlpull.v1.XmlPullParserException;
@@ -1645,6 +1648,40 @@
mWallpaperDisplayHelper = new WallpaperDisplayHelper(dm, mWindowManagerInternal);
mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
mActivityManager = mContext.getSystemService(ActivityManager.class);
+
+ if (mContext.getResources().getBoolean(
+ R.bool.config_pauseWallpaperRenderWhenStateChangeEnabled)) {
+ // Pause wallpaper rendering engine as soon as a performance impacted app is launched.
+ final String[] pauseRenderList = mContext.getResources().getStringArray(
+ R.array.pause_wallpaper_render_when_state_change);
+ final IntArray pauseRenderUids = new IntArray();
+ for (String pauseRenderApp : pauseRenderList) {
+ try {
+ int uid = mContext.getPackageManager().getApplicationInfo(
+ pauseRenderApp, 0).uid;
+ pauseRenderUids.add(uid);
+ } catch (Exception e) {
+ Slog.e(TAG, e.toString());
+ }
+ }
+ if (pauseRenderUids.size() > 0) {
+ try {
+ ActivityManager.getService().registerUidObserverForUids(new UidObserver() {
+ @Override
+ public void onUidStateChanged(int uid, int procState, long procStateSeq,
+ int capability) {
+ pauseOrResumeRenderingImmediately(
+ procState == ActivityManager.PROCESS_STATE_TOP);
+ }
+ }, ActivityManager.UID_OBSERVER_PROCSTATE,
+ ActivityManager.PROCESS_STATE_TOP, "android",
+ pauseRenderUids.toArray());
+ } catch (RemoteException e) {
+ Slog.e(TAG, e.toString());
+ }
+ }
+ }
+
mMonitor = new MyPackageMonitor();
mColorsChangedListeners = new SparseArray<>();
mWallpaperDataParser = new WallpaperDataParser(mContext, mWallpaperDisplayHelper,
@@ -2625,6 +2662,35 @@
}
}
+ private void pauseOrResumeRenderingImmediately(boolean pause) {
+ synchronized (mLock) {
+ final WallpaperData[] wallpapers = mIsLockscreenLiveWallpaperEnabled
+ ? getActiveWallpapers() : new WallpaperData[] {
+ mWallpaperMap.get(mCurrentUserId) };
+ for (WallpaperData data : wallpapers) {
+ if (data.connection == null || data.connection.mInfo == null) {
+ continue;
+ }
+ if (pause || LocalServices.getService(ActivityTaskManagerInternal.class)
+ .isUidForeground(data.connection.mInfo.getServiceInfo()
+ .applicationInfo.uid)) {
+ if (data.connection.containsDisplay(
+ mWindowManagerInternal.getTopFocusedDisplayId())) {
+ data.connection.forEachDisplayConnector(displayConnector -> {
+ if (displayConnector.mEngine != null) {
+ try {
+ displayConnector.mEngine.setVisibility(!pause);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to set visibility", e);
+ }
+ }
+ });
+ }
+ }
+ }
+ }
+ }
+
/**
* Propagate a wake event to the wallpaper engine(s).
*/
@@ -2843,7 +2909,7 @@
WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId);
WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);
boolean systemValid = systemWallpaper != null;
- boolean lockValid = lockWallpaper != null && !isLockscreenLiveWallpaperEnabled();
+ boolean lockValid = lockWallpaper != null && isLockscreenLiveWallpaperEnabled();
return systemValid && lockValid ? new WallpaperData[]{systemWallpaper, lockWallpaper}
: systemValid ? new WallpaperData[]{systemWallpaper}
: lockValid ? new WallpaperData[]{lockWallpaper}
@@ -3982,7 +4048,9 @@
if (wallpaper == null) {
// common case, this is the first lookup post-boot of the system or
// unified lock, so we bring up the saved state lazily now and recheck.
- int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM;
+ // if we're loading the system wallpaper for the first time, also load the lock
+ // wallpaper to determine if the system wallpaper is system+lock or system only.
+ int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM | FLAG_LOCK;
loadSettingsLocked(userId, false, whichLoad);
wallpaper = whichSet.get(userId);
if (wallpaper == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c866dd0..9fa5ed2 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1316,6 +1316,9 @@
if (mLaunchIntoPipHostActivity != null) {
pw.println(prefix + "launchIntoPipHostActivity=" + mLaunchIntoPipHostActivity);
}
+ if (mWaitForEnteringPinnedMode) {
+ pw.print(prefix); pw.println("mWaitForEnteringPinnedMode=true");
+ }
mLetterboxUiController.dump(pw, prefix);
@@ -3132,9 +3135,7 @@
}
boolean canReceiveKeys() {
- // TODO(156521483): Propagate the state down the hierarchy instead of checking the parent
- return getWindowConfiguration().canReceiveKeys()
- && (task == null || task.getWindowConfiguration().canReceiveKeys());
+ return getWindowConfiguration().canReceiveKeys() && !mWaitForEnteringPinnedMode;
}
boolean isResizeable() {
@@ -4029,6 +4030,9 @@
if (mAppStopped) {
abortAndClearOptionsAnimation();
}
+ if (mDisplayContent != null) {
+ mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
+ }
}
boolean isFinishing() {
@@ -5387,12 +5391,6 @@
mLastDeferHidingClient = deferHidingClient;
if (!visible) {
- // If this activity is about to finish/stopped and now becomes invisible, remove it
- // from the unknownApp list in case the activity does not want to draw anything, which
- // keep the user waiting for the next transition to start.
- if (finishing || isState(STOPPED)) {
- displayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
- }
// Because starting window was transferred, this activity may be a trampoline which has
// been occluded by next activity. If it has added windows, set client visibility
// immediately to avoid the client getting RELAYOUT_RES_FIRST_TIME from relayout and
@@ -5836,6 +5834,9 @@
break;
case STOPPED:
mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_STOPPED);
+ if (mDisplayContent != null) {
+ mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
+ }
break;
case DESTROYED:
if (app != null && (mVisible || mVisibleRequested)) {
@@ -6516,7 +6517,6 @@
}
// Reset the last saved PiP snap fraction on app stop.
mDisplayContent.mPinnedTaskController.onActivityHidden(mActivityComponent);
- mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
if (isClientVisible()) {
// Though this is usually unlikely to happen, still make sure the client is invisible.
setClientVisible(false);
@@ -8264,7 +8264,11 @@
private void clearSizeCompatModeAttributes() {
mInSizeCompatModeForBounds = false;
+ final float lastSizeCompatScale = mSizeCompatScale;
mSizeCompatScale = 1f;
+ if (mSizeCompatScale != lastSizeCompatScale) {
+ forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
+ }
mSizeCompatBounds = null;
mCompatDisplayInsets = null;
mLetterboxUiController.clearInheritedCompatDisplayInsets();
@@ -8272,11 +8276,7 @@
@VisibleForTesting
void clearSizeCompatMode() {
- final float lastSizeCompatScale = mSizeCompatScale;
clearSizeCompatModeAttributes();
- if (mSizeCompatScale != lastSizeCompatScale) {
- forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
- }
// Clear config override in #updateCompatDisplayInsets().
final int activityType = getActivityType();
final Configuration overrideConfig = getRequestedOverrideConfiguration();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0b67321..c021785 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -68,6 +68,7 @@
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DREAM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
@@ -3001,7 +3002,7 @@
|| mLastResumedActivity == null) {
return;
}
- var userInfo = mUserManager.getUserInfo(mLastResumedActivity.mUserId);
+ var userInfo = getUserManager().getUserInfo(mLastResumedActivity.mUserId);
if (userInfo == null || !userInfo.isManagedProfile()) {
return;
}
@@ -3686,6 +3687,11 @@
getTransitionController(), mWindowManager.mSyncEngine)
: null;
+ if (r.getTaskFragment() != null && r.getTaskFragment().isEmbeddedWithBoundsOverride()
+ && transition != null) {
+ transition.addFlag(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
+ }
+
final Runnable enterPipRunnable = () -> {
synchronized (mGlobalLock) {
if (r.getParent() == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 12e1e2c..777b5cd 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2832,17 +2832,22 @@
static class OpaqueActivityHelper implements Predicate<ActivityRecord> {
private ActivityRecord mStarting;
private boolean mIncludeInvisibleAndFinishing;
+ private boolean mIgnoringKeyguard;
- ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) {
+ ActivityRecord getOpaqueActivity(
+ @NonNull WindowContainer<?> container, boolean ignoringKeyguard) {
mIncludeInvisibleAndFinishing = true;
+ mIgnoringKeyguard = ignoringKeyguard;
return container.getActivity(this,
true /* traverseTopToBottom */, null /* boundary */);
}
- ActivityRecord getVisibleOpaqueActivity(@NonNull WindowContainer<?> container,
- @Nullable ActivityRecord starting) {
+ ActivityRecord getVisibleOpaqueActivity(
+ @NonNull WindowContainer<?> container, @Nullable ActivityRecord starting,
+ boolean ignoringKeyguard) {
mStarting = starting;
mIncludeInvisibleAndFinishing = false;
+ mIgnoringKeyguard = ignoringKeyguard;
final ActivityRecord opaque = container.getActivity(this,
true /* traverseTopToBottom */, null /* boundary */);
mStarting = null;
@@ -2851,7 +2856,9 @@
@Override
public boolean test(ActivityRecord r) {
- if (!mIncludeInvisibleAndFinishing && !r.visibleIgnoringKeyguard && r != mStarting) {
+ if (!mIncludeInvisibleAndFinishing && r != mStarting
+ && ((mIgnoringKeyguard && !r.visibleIgnoringKeyguard)
+ || (!mIgnoringKeyguard && !r.isVisible()))) {
// Ignore invisible activities that are not the currently starting activity
// (about to be visible).
return false;
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index b039646..3dc377d 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -22,6 +22,7 @@
import android.view.SurfaceControl.Transaction;
import android.view.animation.Animation;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
@@ -31,7 +32,8 @@
* Interface that describes an animation and bridges the animation start to the component
* responsible for running the animation.
*/
-interface AnimationAdapter {
+@VisibleForTesting
+public interface AnimationAdapter {
long STATUS_BAR_TRANSITION_DURATION = 120L;
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 4444709..c79a8b6 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -378,8 +378,23 @@
if (!wc.isSyncFinished(this)) {
allFinished = false;
Slog.i(TAG, "Unfinished container: " + wc);
+ wc.forAllActivities(a -> {
+ if (a.isVisibleRequested()) {
+ if (a.isRelaunching()) {
+ Slog.i(TAG, " " + a + " is relaunching");
+ }
+ a.forAllWindows(w -> {
+ Slog.i(TAG, " " + w + " " + w.mWinAnimator.drawStateToString());
+ }, true /* traverseTopToBottom */);
+ } else if (a.mDisplayContent != null && !a.mDisplayContent
+ .mUnknownAppVisibilityController.allResolved()) {
+ Slog.i(TAG, " UnknownAppVisibility: " + a.mDisplayContent
+ .mUnknownAppVisibilityController.getDebugMessage());
+ }
+ });
}
}
+
for (int i = mDependencies.size() - 1; i >= 0; --i) {
allFinished = false;
Slog.i(TAG, "Unfinished dependency: " + mDependencies.get(i).mSyncId);
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 4237668..6d59b29 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -276,6 +276,10 @@
// activity, we won't close the activity.
backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
removedWindowContainer = window;
+ } else if (!currentActivity.occludesParent() || currentActivity.showWallpaper()) {
+ // skip if current activity is translucent
+ backType = BackNavigationInfo.TYPE_CALLBACK;
+ removedWindowContainer = window;
} else if (prevActivity != null) {
if (!isOccluded || prevActivity.canShowWhenLocked()) {
// We have another Activity in the same currentTask to go to
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index ae29afa..823fbc9 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -11,172 +11,39 @@
* 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
+ * limitations under the License.
*/
package com.android.server.wm;
-import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
-import static com.android.server.wm.AlphaAnimationSpecProto.TO;
-import static com.android.server.wm.AnimationSpecProto.ALPHA;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
-
import android.annotation.NonNull;
import android.graphics.Rect;
-import android.util.Log;
-import android.util.proto.ProtoOutputStream;
-import android.view.Surface;
import android.view.SurfaceControl;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
-
-import java.io.PrintWriter;
+import com.android.window.flags.Flags;
/**
* Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is
* black layers of varying opacity at various Z-levels which create the effect of a Dim.
*/
-class Dimmer {
- private static final String TAG = "WindowManager";
- // This is in milliseconds.
- private static final int DEFAULT_DIM_ANIM_DURATION = 200;
+public abstract class Dimmer {
- private class DimAnimatable implements SurfaceAnimator.Animatable {
- private SurfaceControl mDimLayer;
-
- private DimAnimatable(SurfaceControl dimLayer) {
- mDimLayer = dimLayer;
- }
-
- @Override
- public SurfaceControl.Transaction getSyncTransaction() {
- return mHost.getSyncTransaction();
- }
-
- @Override
- public SurfaceControl.Transaction getPendingTransaction() {
- return mHost.getPendingTransaction();
- }
-
- @Override
- public void commitPendingTransaction() {
- mHost.commitPendingTransaction();
- }
-
- @Override
- public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
- }
-
- @Override
- public void onAnimationLeashLost(SurfaceControl.Transaction t) {
- }
-
- @Override
- public SurfaceControl.Builder makeAnimationLeash() {
- return mHost.makeAnimationLeash();
- }
-
- @Override
- public SurfaceControl getAnimationLeashParent() {
- return mHost.getSurfaceControl();
- }
-
- @Override
- public SurfaceControl getSurfaceControl() {
- return mDimLayer;
- }
-
- @Override
- public SurfaceControl getParentSurfaceControl() {
- return mHost.getSurfaceControl();
- }
-
- @Override
- public int getSurfaceWidth() {
- // This will determine the size of the leash created. This should be the size of the
- // host and not the dim layer since the dim layer may get bigger during animation. If
- // that occurs, the leash size cannot change so we need to ensure the leash is big
- // enough that the dim layer can grow.
- // This works because the mHost will be a Task which has the display bounds.
- return mHost.getSurfaceWidth();
- }
-
- @Override
- public int getSurfaceHeight() {
- // See getSurfaceWidth() above for explanation.
- return mHost.getSurfaceHeight();
- }
-
- void removeSurface() {
- if (mDimLayer != null && mDimLayer.isValid()) {
- getSyncTransaction().remove(mDimLayer);
- }
- mDimLayer = null;
- }
- }
-
- @VisibleForTesting
- class DimState {
- /**
- * The layer where property changes should be invoked on.
- */
- SurfaceControl mDimLayer;
- boolean mDimming;
- boolean isVisible;
- SurfaceAnimator mSurfaceAnimator;
-
- // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
- final Rect mDimBounds = new Rect();
-
- /**
- * Determines whether the dim layer should animate before destroying.
- */
- boolean mAnimateExit = true;
-
- /**
- * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for
- * details on Dim lifecycle.
- */
- boolean mDontReset;
-
- DimState(SurfaceControl dimLayer) {
- mDimLayer = dimLayer;
- mDimming = true;
- final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer);
- mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> {
- if (!mDimming) {
- dimAnimatable.removeSurface();
- }
- }, mHost.mWmService);
- }
- }
+ static final boolean DIMMER_REFACTOR = Flags.dimmerRefactor();
/**
- * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the
+ * The {@link WindowContainer} that our Dims are bounded to. We may be dimming on behalf of the
* host, some controller of it, or one of the hosts children.
*/
- private WindowContainer mHost;
- private WindowContainer mLastRequestedDimContainer;
- @VisibleForTesting
- DimState mDimState;
+ protected final WindowContainer mHost;
- private final SurfaceAnimatorStarter mSurfaceAnimatorStarter;
-
- @VisibleForTesting
- interface SurfaceAnimatorStarter {
- void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
- AnimationAdapter anim, boolean hidden, @AnimationType int type);
- }
-
- Dimmer(WindowContainer host) {
- this(host, SurfaceAnimator::startAnimation);
- }
-
- Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) {
+ protected Dimmer(WindowContainer host) {
mHost = host;
- mSurfaceAnimatorStarter = surfaceAnimatorStarter;
+ }
+
+ // Constructs the correct type of dimmer
+ static Dimmer create(WindowContainer host) {
+ return DIMMER_REFACTOR ? new SmoothDimmer(host) : new LegacyDimmer(host);
}
@NonNull
@@ -184,73 +51,34 @@
return mHost;
}
- private SurfaceControl makeDimLayer() {
- return mHost.makeChildSurface(null)
- .setParent(mHost.getSurfaceControl())
- .setColorLayer()
- .setName("Dim Layer for - " + mHost.getName())
- .setCallsite("Dimmer.makeDimLayer")
- .build();
- }
-
/**
- * Retrieve the DimState, creating one if it doesn't exist.
- */
- private DimState getDimState(WindowContainer container) {
- if (mDimState == null) {
- try {
- final SurfaceControl ctl = makeDimLayer();
- mDimState = new DimState(ctl);
- } catch (Surface.OutOfResourcesException e) {
- Log.w(TAG, "OutOfResourcesException creating dim surface");
- }
- }
-
- mLastRequestedDimContainer = container;
- return mDimState;
- }
-
- private void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) {
- final DimState d = getDimState(container);
-
- if (d == null) {
- return;
- }
-
- // The dim method is called from WindowState.prepareSurfaces(), which is always called
- // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
- // relative to the highest Z layer with a dim.
- SurfaceControl.Transaction t = mHost.getPendingTransaction();
- t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
- t.setAlpha(d.mDimLayer, alpha);
- t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
-
- d.mDimming = true;
- }
-
- /**
- * Place a dim above the given container, which should be a child of the host container.
- * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
- * and the child should call dimAbove again to request the Dim to continue.
+ * Position the dim relatively to the dimming container.
+ * Normally called together with #setAppearance, it can be called alone to keep the dim parented
+ * to a visible container until the next dimming container is ready.
+ * If multiple containers call this method, only the changes relative to the topmost will be
+ * applied.
*
- * @param container The container which to dim above. Should be a child of our host.
- * @param alpha The alpha at which to Dim.
+ * For each call to {@link WindowContainer#prepareSurfaces()} the DimState will be reset, and
+ * the child of the host should call adjustRelativeLayer and {@link Dimmer#adjustAppearance} to
+ * continue dimming. Indeed, this method won't be able to keep dimming or get a new DimState
+ * without also adjusting the appearance.
+ * @param container The container which to dim above. Should be a child of the host.
+ * @param relativeLayer The position of the dim wrt the container
*/
- void dimAbove(@NonNull WindowContainer container, float alpha) {
- dim(container, 1, alpha, 0);
- }
+ protected abstract void adjustRelativeLayer(WindowContainer container, int relativeLayer);
/**
- * Like {@link #dimAbove} but places the dim below the given container.
- *
- * @param container The container which to dim below. Should be a child of our host.
- * @param alpha The alpha at which to Dim.
- * @param blurRadius The amount of blur added to the Dim.
+ * Set the aspect of the dim layer, and request to keep dimming.
+ * For each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset, and the
+ * child should call setAppearance again to request the Dim to continue.
+ * If multiple containers call this method, only the changes relative to the topmost will be
+ * applied.
+ * @param container Container requesting the dim
+ * @param alpha Dim amount
+ * @param blurRadius Blur amount
*/
-
- void dimBelow(@NonNull WindowContainer container, float alpha, int blurRadius) {
- dim(container, -1, alpha, blurRadius);
- }
+ protected abstract void adjustAppearance(
+ WindowContainer container, float alpha, int blurRadius);
/**
* Mark all dims as pending completion on the next call to {@link #updateDims}
@@ -260,25 +88,15 @@
* chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
* a chance to request dims to continue.
*/
- void resetDimStates() {
- if (mDimState == null) {
- return;
- }
- if (!mDimState.mDontReset) {
- mDimState.mDimming = false;
- }
- }
+ abstract void resetDimStates();
/** Returns non-null bounds if the dimmer is showing. */
- Rect getDimBounds() {
- return mDimState != null ? mDimState.mDimBounds : null;
- }
+ abstract Rect getDimBounds();
- void dontAnimateExit() {
- if (mDimState != null) {
- mDimState.mAnimateExit = false;
- }
- }
+ abstract void dontAnimateExit();
+
+ @VisibleForTesting
+ abstract SurfaceControl getDimLayer();
/**
* Call after invoking {@link WindowContainer#prepareSurfaces} on children as
@@ -288,109 +106,5 @@
* @param t A transaction in which to update the dims.
* @return true if any Dims were updated.
*/
- boolean updateDims(SurfaceControl.Transaction t) {
- if (mDimState == null) {
- return false;
- }
-
- if (!mDimState.mDimming) {
- if (!mDimState.mAnimateExit) {
- if (mDimState.mDimLayer.isValid()) {
- t.remove(mDimState.mDimLayer);
- }
- } else {
- startDimExit(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
- }
- mDimState = null;
- return false;
- } else {
- final Rect bounds = mDimState.mDimBounds;
- // TODO: Once we use geometry from hierarchy this falls away.
- t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
- t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
- if (!mDimState.isVisible) {
- mDimState.isVisible = true;
- t.show(mDimState.mDimLayer);
- // Skip enter animation while starting window is on top of its activity
- final WindowState ws = mLastRequestedDimContainer.asWindowState();
- if (ws == null || ws.mActivityRecord == null
- || ws.mActivityRecord.mStartingData == null) {
- startDimEnter(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
- }
- }
- return true;
- }
- }
-
- private void startDimEnter(WindowContainer container, SurfaceAnimator animator,
- SurfaceControl.Transaction t) {
- startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */);
- }
-
- private void startDimExit(WindowContainer container, SurfaceAnimator animator,
- SurfaceControl.Transaction t) {
- startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */);
- }
-
- private void startAnim(WindowContainer container, SurfaceAnimator animator,
- SurfaceControl.Transaction t, float startAlpha, float endAlpha) {
- mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
- new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
- mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */,
- ANIMATION_TYPE_DIMMER);
- }
-
- private long getDimDuration(WindowContainer container) {
- // If there's no container, then there isn't an animation occurring while dimming. Set the
- // duration to 0 so it immediately dims to the set alpha.
- if (container == null) {
- return 0;
- }
-
- // Otherwise use the same duration as the animation on the WindowContainer
- AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
- final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
- return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
- : animationAdapter.getDurationHint();
- }
-
- private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec {
- private final long mDuration;
- private final float mFromAlpha;
- private final float mToAlpha;
-
- AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) {
- mFromAlpha = fromAlpha;
- mToAlpha = toAlpha;
- mDuration = duration;
- }
-
- @Override
- public long getDuration() {
- return mDuration;
- }
-
- @Override
- public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
- final float fraction = getFraction(currentPlayTime);
- final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha;
- t.setAlpha(sc, alpha);
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("from="); pw.print(mFromAlpha);
- pw.print(" to="); pw.print(mToAlpha);
- pw.print(" duration="); pw.println(mDuration);
- }
-
- @Override
- public void dumpDebugInner(ProtoOutputStream proto) {
- final long token = proto.start(ALPHA);
- proto.write(FROM, mFromAlpha);
- proto.write(TO, mToAlpha);
- proto.write(DURATION_MS, mDuration);
- proto.end(token);
- }
- }
+ abstract boolean updateDims(SurfaceControl.Transaction t);
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index df26b10..f51bf7f 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -54,7 +54,6 @@
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
-
/**
* Container for grouping WindowContainer below DisplayContent.
*
@@ -786,7 +785,7 @@
* DisplayArea that can be dimmed.
*/
static class Dimmable extends DisplayArea<DisplayArea> {
- private final Dimmer mDimmer = new Dimmer(this);
+ private final Dimmer mDimmer = Dimmer.create(this);
Dimmable(WindowManagerService wms, Type type, String name, int featureId) {
super(wms, type, name, featureId);
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index c21930d..1fa7d2a 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -51,7 +51,8 @@
private final Rect mOldWindowCrop = new Rect();
InputConsumerImpl(WindowManagerService service, IBinder token, String name,
- InputChannel inputChannel, int clientPid, UserHandle clientUser, int displayId) {
+ InputChannel inputChannel, int clientPid, UserHandle clientUser, int displayId,
+ SurfaceControl.Transaction t) {
mService = service;
mToken = token;
mName = name;
@@ -82,6 +83,7 @@
.setName("Input Consumer " + name)
.setCallsite("InputConsumerImpl")
.build();
+ mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
}
void linkToDeathRecipient() {
@@ -129,14 +131,12 @@
void show(SurfaceControl.Transaction t, WindowContainer w) {
t.show(mInputSurface);
- mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
t.setInputWindowInfo(mInputSurface, mWindowHandle);
t.setRelativeLayer(mInputSurface, w.getSurfaceControl(), 1);
}
void show(SurfaceControl.Transaction t, int layer) {
t.show(mInputSurface);
- mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
t.setInputWindowInfo(mInputSurface, mWindowHandle);
t.setLayer(mInputSurface, layer);
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index af307ec3..5c0bc28 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -224,7 +224,7 @@
}
final InputConsumerImpl consumer = new InputConsumerImpl(mService, token, name,
- inputChannel, clientPid, clientUser, mDisplayId);
+ inputChannel, clientPid, clientUser, mDisplayId, mInputTransaction);
switch (name) {
case INPUT_CONSUMER_WALLPAPER:
consumer.mWindowHandle.inputConfig |= InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER;
@@ -675,11 +675,6 @@
w.getKeyInterceptionInfo());
if (w.mWinAnimator.hasSurface()) {
- // Update trusted overlay changes here because they are tied to input info. Input
- // changes can be updated even if surfaces aren't.
- inputWindowHandle.setTrustedOverlay(mInputTransaction,
- w.mWinAnimator.mSurfaceController.mSurfaceControl,
- w.isWindowTrustedOverlay());
populateInputWindowHandle(inputWindowHandle, w);
setInputWindowInfoIfNeeded(mInputTransaction,
w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 02f5c21..cd114fc 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -68,6 +68,7 @@
private final Rect mTmpRect = new Rect();
private final InsetsStateController mStateController;
private final InsetsSourceControl mFakeControl;
+ private final Consumer<Transaction> mSetLeashPositionConsumer;
private @Nullable InsetsSourceControl mControl;
private @Nullable InsetsControlTarget mControlTarget;
private @Nullable InsetsControlTarget mPendingControlTarget;
@@ -85,16 +86,7 @@
private boolean mInsetsHintStale = true;
private @Flags int mFlagsFromFrameProvider;
private @Flags int mFlagsFromServer;
-
- private final Consumer<Transaction> mSetLeashPositionConsumer = t -> {
- if (mControl != null) {
- final SurfaceControl leash = mControl.getLeash();
- if (leash != null) {
- final Point position = mControl.getSurfacePosition();
- t.setPosition(leash, position.x, position.y);
- }
- }
- };
+ private boolean mHasPendingPosition;
/** The visibility override from the current controlling window. */
private boolean mClientVisible;
@@ -129,6 +121,21 @@
source.getId(), source.getType(), null /* leash */, false /* initialVisible */,
new Point(), Insets.NONE);
mControllable = (InsetsPolicy.CONTROLLABLE_TYPES & source.getType()) != 0;
+ mSetLeashPositionConsumer = t -> {
+ if (mControl != null) {
+ final SurfaceControl leash = mControl.getLeash();
+ if (leash != null) {
+ final Point position = mControl.getSurfacePosition();
+ t.setPosition(leash, position.x, position.y);
+ }
+ }
+ if (mHasPendingPosition) {
+ mHasPendingPosition = false;
+ if (mPendingControlTarget != mControlTarget) {
+ mStateController.notifyControlTargetChanged(mPendingControlTarget, this);
+ }
+ }
+ };
}
InsetsSource getSource() {
@@ -185,9 +192,8 @@
mWindowContainer.getInsetsSourceProviders().put(mSource.getId(), this);
if (mControllable) {
mWindowContainer.setControllableInsetProvider(this);
- if (mPendingControlTarget != null) {
+ if (mPendingControlTarget != mControlTarget) {
updateControlForTarget(mPendingControlTarget, true /* force */);
- mPendingControlTarget = null;
}
}
}
@@ -344,6 +350,7 @@
changed = true;
if (windowState != null && windowState.getWindowFrames().didFrameSizeChange()
&& windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) {
+ mHasPendingPosition = true;
windowState.applyWithNextDraw(mSetLeashPositionConsumer);
} else {
Transaction t = mWindowContainer.getSyncTransaction();
@@ -465,18 +472,23 @@
// to control the window for now.
return;
}
+ mPendingControlTarget = target;
if (mWindowContainer != null && mWindowContainer.getSurfaceControl() == null) {
// if window doesn't have a surface, set it null and return.
setWindowContainer(null, null, null);
}
if (mWindowContainer == null) {
- mPendingControlTarget = target;
return;
}
if (target == mControlTarget && !force) {
return;
}
+ if (mHasPendingPosition) {
+ // Don't create a new leash while having a pending position. Otherwise, the position
+ // will be changed earlier than expected, which can cause flicker.
+ return;
+ }
if (target == null) {
// Cancelling the animation will invoke onAnimationCancelled, resetting all the fields.
mWindowContainer.cancelAnimation();
@@ -618,6 +630,7 @@
}
pw.print(prefix);
pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching);
+ pw.print("mHasPendingPosition="); pw.print(mHasPendingPosition);
pw.println();
if (mWindowContainer != null) {
pw.print(prefix + "mWindowContainer=");
@@ -631,7 +644,7 @@
pw.print(prefix + "mControlTarget=");
pw.println(mControlTarget);
}
- if (mPendingControlTarget != null) {
+ if (mPendingControlTarget != mControlTarget) {
pw.print(prefix + "mPendingControlTarget=");
pw.println(mPendingControlTarget);
}
@@ -652,7 +665,8 @@
if (mControlTarget != null && mControlTarget.getWindow() != null) {
mControlTarget.getWindow().dumpDebug(proto, CONTROL_TARGET, logLevel);
}
- if (mPendingControlTarget != null && mPendingControlTarget.getWindow() != null) {
+ if (mPendingControlTarget != null && mPendingControlTarget != mControlTarget
+ && mPendingControlTarget.getWindow() != null) {
mPendingControlTarget.getWindow().dumpDebug(proto, PENDING_CONTROL_TARGET, logLevel);
}
if (mFakeControlTarget != null && mFakeControlTarget.getWindow() != null) {
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 081ebe0..c4d0129 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -278,6 +278,12 @@
notifyPendingInsetsControlChanged();
}
+ void notifyControlTargetChanged(@Nullable InsetsControlTarget target,
+ InsetsSourceProvider provider) {
+ onControlTargetChanged(provider, target, false /* fake */);
+ notifyPendingInsetsControlChanged();
+ }
+
void notifyControlRevoked(@NonNull InsetsControlTarget previousControlTarget,
InsetsSourceProvider provider) {
removeFromControlMaps(previousControlTarget, provider, false /* fake */);
diff --git a/services/core/java/com/android/server/wm/LegacyDimmer.java b/services/core/java/com/android/server/wm/LegacyDimmer.java
new file mode 100644
index 0000000..3265e60
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LegacyDimmer.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
+import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
+import static com.android.server.wm.AlphaAnimationSpecProto.TO;
+import static com.android.server.wm.AnimationSpecProto.ALPHA;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+
+public class LegacyDimmer extends Dimmer {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+ // This is in milliseconds.
+ private static final int DEFAULT_DIM_ANIM_DURATION = 200;
+ DimState mDimState;
+ private WindowContainer mLastRequestedDimContainer;
+ private final SurfaceAnimatorStarter mSurfaceAnimatorStarter;
+
+ private class DimAnimatable implements SurfaceAnimator.Animatable {
+ private SurfaceControl mDimLayer;
+
+ private DimAnimatable(SurfaceControl dimLayer) {
+ mDimLayer = dimLayer;
+ }
+
+ @Override
+ public SurfaceControl.Transaction getSyncTransaction() {
+ return mHost.getSyncTransaction();
+ }
+
+ @Override
+ public SurfaceControl.Transaction getPendingTransaction() {
+ return mHost.getPendingTransaction();
+ }
+
+ @Override
+ public void commitPendingTransaction() {
+ mHost.commitPendingTransaction();
+ }
+
+ @Override
+ public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
+ }
+
+ @Override
+ public void onAnimationLeashLost(SurfaceControl.Transaction t) {
+ }
+
+ @Override
+ public SurfaceControl.Builder makeAnimationLeash() {
+ return mHost.makeAnimationLeash();
+ }
+
+ @Override
+ public SurfaceControl getAnimationLeashParent() {
+ return mHost.getSurfaceControl();
+ }
+
+ @Override
+ public SurfaceControl getSurfaceControl() {
+ return mDimLayer;
+ }
+
+ @Override
+ public SurfaceControl getParentSurfaceControl() {
+ return mHost.getSurfaceControl();
+ }
+
+ @Override
+ public int getSurfaceWidth() {
+ // This will determine the size of the leash created. This should be the size of the
+ // host and not the dim layer since the dim layer may get bigger during animation. If
+ // that occurs, the leash size cannot change so we need to ensure the leash is big
+ // enough that the dim layer can grow.
+ // This works because the mHost will be a Task which has the display bounds.
+ return mHost.getSurfaceWidth();
+ }
+
+ @Override
+ public int getSurfaceHeight() {
+ // See getSurfaceWidth() above for explanation.
+ return mHost.getSurfaceHeight();
+ }
+
+ void removeSurface() {
+ if (mDimLayer != null && mDimLayer.isValid()) {
+ getSyncTransaction().remove(mDimLayer);
+ }
+ mDimLayer = null;
+ }
+ }
+
+ @VisibleForTesting
+ class DimState {
+ /**
+ * The layer where property changes should be invoked on.
+ */
+ SurfaceControl mDimLayer;
+ boolean mDimming;
+ boolean mIsVisible;
+
+ // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
+ final Rect mDimBounds = new Rect();
+
+ /**
+ * Determines whether the dim layer should animate before destroying.
+ */
+ boolean mAnimateExit = true;
+
+ /**
+ * Used for Dims not associated with a WindowContainer.
+ * See {@link Dimmer#adjustRelativeLayer(WindowContainer, int)} for details on Dim
+ * lifecycle.
+ */
+ boolean mDontReset;
+ SurfaceAnimator mSurfaceAnimator;
+
+ DimState(SurfaceControl dimLayer) {
+ mDimLayer = dimLayer;
+ mDimming = true;
+ final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer);
+ mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> {
+ if (!mDimming) {
+ dimAnimatable.removeSurface();
+ }
+ }, mHost.mWmService);
+ }
+ }
+
+ @VisibleForTesting
+ interface SurfaceAnimatorStarter {
+ void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
+ AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type);
+ }
+
+ protected LegacyDimmer(WindowContainer host) {
+ this(host, SurfaceAnimator::startAnimation);
+ }
+
+ LegacyDimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) {
+ super(host);
+ mSurfaceAnimatorStarter = surfaceAnimatorStarter;
+ }
+
+ private DimState obtainDimState(WindowContainer container) {
+ if (mDimState == null) {
+ try {
+ final SurfaceControl ctl = makeDimLayer();
+ mDimState = new DimState(ctl);
+ } catch (Surface.OutOfResourcesException e) {
+ Log.w(TAG, "OutOfResourcesException creating dim surface");
+ }
+ }
+
+ mLastRequestedDimContainer = container;
+ return mDimState;
+ }
+
+ private SurfaceControl makeDimLayer() {
+ return mHost.makeChildSurface(null)
+ .setParent(mHost.getSurfaceControl())
+ .setColorLayer()
+ .setName("Dim Layer for - " + mHost.getName())
+ .setCallsite("Dimmer.makeDimLayer")
+ .build();
+ }
+
+ @Override
+ SurfaceControl getDimLayer() {
+ return mDimState != null ? mDimState.mDimLayer : null;
+ }
+
+ @Override
+ void resetDimStates() {
+ if (mDimState == null) {
+ return;
+ }
+ if (!mDimState.mDontReset) {
+ mDimState.mDimming = false;
+ }
+ }
+
+ @Override
+ Rect getDimBounds() {
+ return mDimState != null ? mDimState.mDimBounds : null;
+ }
+
+ @Override
+ void dontAnimateExit() {
+ if (mDimState != null) {
+ mDimState.mAnimateExit = false;
+ }
+ }
+
+ @Override
+ protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) {
+ final DimState d = obtainDimState(container);
+ if (d == null) {
+ return;
+ }
+
+ // The dim method is called from WindowState.prepareSurfaces(), which is always called
+ // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
+ // relative to the highest Z layer with a dim.
+ SurfaceControl.Transaction t = mHost.getPendingTransaction();
+ t.setAlpha(d.mDimLayer, alpha);
+ t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
+ d.mDimming = true;
+ }
+
+ @Override
+ protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) {
+ final DimState d = mDimState;
+ if (d != null) {
+ SurfaceControl.Transaction t = mHost.getPendingTransaction();
+ t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
+ }
+ }
+
+ @Override
+ boolean updateDims(SurfaceControl.Transaction t) {
+ if (mDimState == null) {
+ return false;
+ }
+
+ if (!mDimState.mDimming) {
+ if (!mDimState.mAnimateExit) {
+ if (mDimState.mDimLayer.isValid()) {
+ t.remove(mDimState.mDimLayer);
+ }
+ } else {
+ startDimExit(mLastRequestedDimContainer,
+ mDimState.mSurfaceAnimator, t);
+ }
+ mDimState = null;
+ return false;
+ } else {
+ final Rect bounds = mDimState.mDimBounds;
+ // TODO: Once we use geometry from hierarchy this falls away.
+ t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
+ t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
+ if (!mDimState.mIsVisible) {
+ mDimState.mIsVisible = true;
+ t.show(mDimState.mDimLayer);
+ // Skip enter animation while starting window is on top of its activity
+ final WindowState ws = mLastRequestedDimContainer.asWindowState();
+ if (ws == null || ws.mActivityRecord == null
+ || ws.mActivityRecord.mStartingData == null) {
+ startDimEnter(mLastRequestedDimContainer,
+ mDimState.mSurfaceAnimator, t);
+ }
+ }
+ return true;
+ }
+ }
+
+ private long getDimDuration(WindowContainer container) {
+ // Use the same duration as the animation on the WindowContainer
+ AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
+ final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
+ return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
+ : animationAdapter.getDurationHint();
+ }
+
+ private void startDimEnter(WindowContainer container, SurfaceAnimator animator,
+ SurfaceControl.Transaction t) {
+ startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */);
+ }
+
+ private void startDimExit(WindowContainer container, SurfaceAnimator animator,
+ SurfaceControl.Transaction t) {
+ startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */);
+ }
+
+ private void startAnim(WindowContainer container, SurfaceAnimator animator,
+ SurfaceControl.Transaction t, float startAlpha, float endAlpha) {
+ mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
+ new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
+ mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */,
+ ANIMATION_TYPE_DIMMER);
+ }
+
+ private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec {
+ private final long mDuration;
+ private final float mFromAlpha;
+ private final float mToAlpha;
+
+ AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) {
+ mFromAlpha = fromAlpha;
+ mToAlpha = toAlpha;
+ mDuration = duration;
+ }
+
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ @Override
+ public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
+ final float fraction = getFraction(currentPlayTime);
+ final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha;
+ t.setAlpha(sc, alpha);
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("from="); pw.print(mFromAlpha);
+ pw.print(" to="); pw.print(mToAlpha);
+ pw.print(" duration="); pw.println(mDuration);
+ }
+
+ @Override
+ public void dumpDebugInner(ProtoOutputStream proto) {
+ final long token = proto.start(ALPHA);
+ proto.write(FROM, mFromAlpha);
+ proto.write(TO, mToAlpha);
+ proto.write(DURATION_MS, mDuration);
+ proto.end(token);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 458786f..f8c39d0 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -16,6 +16,7 @@
mariiasand@google.com
rgl@google.com
yunfanc@google.com
+wilsonshih@google.com
per-file BackgroundActivityStartController.java = set noparent
-per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com
\ No newline at end of file
+per-file BackgroundActivityStartController.java = brufino@google.com, topjohnwu@google.com, achim@google.com, ogunwale@google.com, louischang@google.com, lus@google.com
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index cf6a1fe..7a442e7 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2211,6 +2211,10 @@
mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent(
organizedTf);
}
+
+ if (taskDisplayArea.getFocusedRootTask() == rootTask) {
+ taskDisplayArea.clearPreferredTopFocusableRootTask();
+ }
} finally {
mService.continueWindowLayout();
try {
diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java
new file mode 100644
index 0000000..2549bbf
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SmoothDimmer.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
+import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
+import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
+import static com.android.server.wm.AlphaAnimationSpecProto.TO;
+import static com.android.server.wm.AnimationSpecProto.ALPHA;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.io.PrintWriter;
+
+class SmoothDimmer extends Dimmer {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+ private static final float EPSILON = 0.0001f;
+ // This is in milliseconds.
+ private static final int DEFAULT_DIM_ANIM_DURATION = 200;
+ DimState mDimState;
+ private WindowContainer mLastRequestedDimContainer;
+ private final AnimationAdapterFactory mAnimationAdapterFactory;
+
+ @VisibleForTesting
+ class DimState {
+ /**
+ * The layer where property changes should be invoked on.
+ */
+ SurfaceControl mDimLayer;
+ boolean mDimming;
+ boolean mIsVisible;
+
+ // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
+ final Rect mDimBounds = new Rect();
+
+ /**
+ * Determines whether the dim layer should animate before destroying.
+ */
+ boolean mAnimateExit = true;
+
+ /**
+ * Used for Dims not associated with a WindowContainer.
+ * See {@link Dimmer#adjustRelativeLayer(WindowContainer, int)} for details on Dim
+ * lifecycle.
+ */
+ boolean mDontReset;
+
+ Change mCurrentProperties;
+ Change mRequestedProperties;
+ private AnimationSpec mAlphaAnimationSpec;
+ private AnimationAdapter mLocalAnimationAdapter;
+
+ static class Change {
+ private float mAlpha = -1f;
+ private int mBlurRadius = -1;
+ private WindowContainer mDimmingContainer = null;
+ private int mRelativeLayer = -1;
+ private boolean mSkipAnimation = false;
+
+ Change() {}
+
+ Change(Change other) {
+ mAlpha = other.mAlpha;
+ mBlurRadius = other.mBlurRadius;
+ mDimmingContainer = other.mDimmingContainer;
+ mRelativeLayer = other.mRelativeLayer;
+ }
+
+ @Override
+ public String toString() {
+ return "Dim state: alpha=" + mAlpha + ", blur=" + mBlurRadius + ", container="
+ + mDimmingContainer + ", relativePosition=" + mRelativeLayer
+ + ", skipAnimation=" + mSkipAnimation;
+ }
+ }
+
+ DimState(SurfaceControl dimLayer) {
+ mDimLayer = dimLayer;
+ mDimming = true;
+ mCurrentProperties = new Change();
+ mRequestedProperties = new Change();
+ }
+
+ void setExitParameters(WindowContainer container) {
+ setRequestedRelativeParent(container, -1 /* relativeLayer */);
+ setRequestedAppearance(0f /* alpha */, 0 /* blur */);
+ }
+
+ // Sets a requested change without applying it immediately
+ void setRequestedRelativeParent(WindowContainer relativeParent, int relativeLayer) {
+ mRequestedProperties.mDimmingContainer = relativeParent;
+ mRequestedProperties.mRelativeLayer = relativeLayer;
+ }
+
+ // Sets a requested change without applying it immediately
+ void setRequestedAppearance(float alpha, int blurRadius) {
+ mRequestedProperties.mAlpha = alpha;
+ mRequestedProperties.mBlurRadius = blurRadius;
+ }
+
+ /**
+ * Commit the last changes we received. Called after
+ * {@link Change#setExitParameters(WindowContainer)},
+ * {@link Change#setRequestedRelativeParent(WindowContainer, int)}, or
+ * {@link Change#setRequestedAppearance(float, int)}
+ */
+ void applyChanges(SurfaceControl.Transaction t) {
+ if (mRequestedProperties.mDimmingContainer == null) {
+ Log.e(TAG, this + " does not have a dimming container. Have you forgotten to "
+ + "call adjustRelativeLayer?");
+ return;
+ }
+ if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) {
+ Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer
+ + "does not have a surface");
+ return;
+ }
+ if (!mDimState.mIsVisible) {
+ mDimState.mIsVisible = true;
+ t.show(mDimState.mDimLayer);
+ }
+ t.setRelativeLayer(mDimLayer,
+ mRequestedProperties.mDimmingContainer.getSurfaceControl(),
+ mRequestedProperties.mRelativeLayer);
+
+ if (aspectChanged()) {
+ if (isAnimating()) {
+ mLocalAnimationAdapter.onAnimationCancelled(mDimLayer);
+ }
+ if (mRequestedProperties.mSkipAnimation
+ || (!dimmingContainerChanged() && mDimming)) {
+ // If the dimming container has not changed, then it is running its own
+ // animation, thus we can directly set the values we get requested, unless it's
+ // the exiting animation
+ ProtoLog.d(WM_DEBUG_DIMMER,
+ "Dim %s skipping animation and directly setting alpha=%f, blur=%d",
+ mDimLayer, mRequestedProperties.mAlpha,
+ mRequestedProperties.mBlurRadius);
+ t.setAlpha(mDimLayer, mRequestedProperties.mAlpha);
+ t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius);
+ mRequestedProperties.mSkipAnimation = false;
+ } else {
+ startAnimation(t);
+ }
+ }
+ mCurrentProperties = new Change(mRequestedProperties);
+ }
+
+ private void startAnimation(SurfaceControl.Transaction t) {
+ mAlphaAnimationSpec = getRequestedAnimationSpec(mRequestedProperties.mAlpha,
+ mRequestedProperties.mBlurRadius);
+ mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec,
+ mHost.mWmService.mSurfaceAnimationRunner);
+
+ mLocalAnimationAdapter.startAnimation(mDimLayer, t,
+ ANIMATION_TYPE_DIMMER, (type, animator) -> {
+ t.setAlpha(mDimLayer, mRequestedProperties.mAlpha);
+ t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius);
+ if (mRequestedProperties.mAlpha == 0f && !mDimming) {
+ ProtoLog.d(WM_DEBUG_DIMMER,
+ "Removing dim surface %s on transaction %s", mDimLayer, t);
+ t.remove(mDimLayer);
+ }
+ mLocalAnimationAdapter = null;
+ mAlphaAnimationSpec = null;
+ });
+ }
+
+ private boolean isAnimating() {
+ return mAlphaAnimationSpec != null;
+ }
+
+ private boolean aspectChanged() {
+ return Math.abs(mRequestedProperties.mAlpha - mCurrentProperties.mAlpha) > EPSILON
+ || mRequestedProperties.mBlurRadius != mCurrentProperties.mBlurRadius;
+ }
+
+ private boolean dimmingContainerChanged() {
+ return mRequestedProperties.mDimmingContainer != mCurrentProperties.mDimmingContainer;
+ }
+
+ private AnimationSpec getRequestedAnimationSpec(float targetAlpha, int targetBlur) {
+ final float startAlpha;
+ final int startBlur;
+ if (mAlphaAnimationSpec != null) {
+ startAlpha = mAlphaAnimationSpec.mCurrentAlpha;
+ startBlur = mAlphaAnimationSpec.mCurrentBlur;
+ } else {
+ startAlpha = Math.max(mCurrentProperties.mAlpha, 0f);
+ startBlur = Math.max(mCurrentProperties.mBlurRadius, 0);
+ }
+ long duration = (long) (getDimDuration(mRequestedProperties.mDimmingContainer)
+ * Math.abs(targetAlpha - startAlpha));
+
+ ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on dim layer %s, requested by %s, "
+ + "alpha: %f -> %f, blur: %d -> %d",
+ mDimLayer, mRequestedProperties.mDimmingContainer, startAlpha, targetAlpha,
+ startBlur, targetBlur);
+ return new AnimationSpec(
+ new AnimationExtremes<>(startAlpha, targetAlpha),
+ new AnimationExtremes<>(startBlur, targetBlur),
+ duration
+ );
+ }
+ }
+
+ protected SmoothDimmer(WindowContainer host) {
+ this(host, new AnimationAdapterFactory());
+ }
+
+ @VisibleForTesting
+ SmoothDimmer(WindowContainer host, AnimationAdapterFactory animationFactory) {
+ super(host);
+ mAnimationAdapterFactory = animationFactory;
+ }
+
+ private DimState obtainDimState(WindowContainer container) {
+ if (mDimState == null) {
+ try {
+ final SurfaceControl ctl = makeDimLayer();
+ mDimState = new DimState(ctl);
+ } catch (Surface.OutOfResourcesException e) {
+ Log.w(TAG, "OutOfResourcesException creating dim surface");
+ }
+ }
+
+ mLastRequestedDimContainer = container;
+ return mDimState;
+ }
+
+ private SurfaceControl makeDimLayer() {
+ return mHost.makeChildSurface(null)
+ .setParent(mHost.getSurfaceControl())
+ .setColorLayer()
+ .setName("Dim Layer for - " + mHost.getName())
+ .setCallsite("Dimmer.makeDimLayer")
+ .build();
+ }
+
+ @Override
+ SurfaceControl getDimLayer() {
+ return mDimState != null ? mDimState.mDimLayer : null;
+ }
+
+ @Override
+ void resetDimStates() {
+ if (mDimState == null) {
+ return;
+ }
+ if (!mDimState.mDontReset) {
+ mDimState.mDimming = false;
+ }
+ }
+
+ @Override
+ Rect getDimBounds() {
+ return mDimState != null ? mDimState.mDimBounds : null;
+ }
+
+ @Override
+ void dontAnimateExit() {
+ if (mDimState != null) {
+ mDimState.mAnimateExit = false;
+ }
+ }
+
+ @Override
+ protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) {
+ final DimState d = obtainDimState(container);
+ mDimState.setRequestedAppearance(alpha, blurRadius);
+ d.mDimming = true;
+ }
+
+ @Override
+ protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) {
+ if (mDimState != null) {
+ mDimState.setRequestedRelativeParent(container, relativeLayer);
+ }
+ }
+
+ boolean updateDims(SurfaceControl.Transaction t) {
+ if (mDimState == null) {
+ return false;
+ }
+
+ if (!mDimState.mDimming) {
+ // No one is dimming anymore, fade out dim and remove
+ if (!mDimState.mAnimateExit) {
+ if (mDimState.mDimLayer.isValid()) {
+ t.remove(mDimState.mDimLayer);
+ }
+ } else {
+ mDimState.setExitParameters(
+ mDimState.mRequestedProperties.mDimmingContainer);
+ mDimState.applyChanges(t);
+ }
+ mDimState = null;
+ return false;
+ }
+ final Rect bounds = mDimState.mDimBounds;
+ // TODO: Once we use geometry from hierarchy this falls away.
+ t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
+ t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
+ // Skip enter animation while starting window is on top of its activity
+ final WindowState ws = mLastRequestedDimContainer.asWindowState();
+ if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null
+ && ws.mActivityRecord.mStartingData != null) {
+ mDimState.mRequestedProperties.mSkipAnimation = true;
+ }
+ mDimState.applyChanges(t);
+ return true;
+ }
+
+ private long getDimDuration(WindowContainer container) {
+ // Use the same duration as the animation on the WindowContainer
+ AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
+ final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
+ return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
+ : animationAdapter.getDurationHint();
+ }
+
+ private static class AnimationExtremes<T> {
+ final T mStartValue;
+ final T mFinishValue;
+
+ AnimationExtremes(T fromValue, T toValue) {
+ mStartValue = fromValue;
+ mFinishValue = toValue;
+ }
+ }
+
+ private static class AnimationSpec implements LocalAnimationAdapter.AnimationSpec {
+ private final long mDuration;
+ private final AnimationExtremes<Float> mAlpha;
+ private final AnimationExtremes<Integer> mBlur;
+
+ float mCurrentAlpha = 0;
+ int mCurrentBlur = 0;
+
+ AnimationSpec(AnimationExtremes<Float> alpha,
+ AnimationExtremes<Integer> blur, long duration) {
+ mAlpha = alpha;
+ mBlur = blur;
+ mDuration = duration;
+ }
+
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ @Override
+ public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
+ final float fraction = getFraction(currentPlayTime);
+ mCurrentAlpha =
+ fraction * (mAlpha.mFinishValue - mAlpha.mStartValue) + mAlpha.mStartValue;
+ mCurrentBlur =
+ (int) fraction * (mBlur.mFinishValue - mBlur.mStartValue) + mBlur.mStartValue;
+ t.setAlpha(sc, mCurrentAlpha);
+ t.setBackgroundBlurRadius(sc, mCurrentBlur);
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("from_alpha="); pw.print(mAlpha.mStartValue);
+ pw.print(" to_alpha="); pw.print(mAlpha.mFinishValue);
+ pw.print(prefix); pw.print("from_blur="); pw.print(mBlur.mStartValue);
+ pw.print(" to_blur="); pw.print(mBlur.mFinishValue);
+ pw.print(" duration="); pw.println(mDuration);
+ }
+
+ @Override
+ public void dumpDebugInner(ProtoOutputStream proto) {
+ final long token = proto.start(ALPHA);
+ proto.write(FROM, mAlpha.mStartValue);
+ proto.write(TO, mAlpha.mFinishValue);
+ proto.write(DURATION_MS, mDuration);
+ proto.end(token);
+ }
+ }
+
+ static class AnimationAdapterFactory {
+
+ public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec,
+ SurfaceAnimationRunner runner) {
+ return new LocalAnimationAdapter(alphaAnimationSpec, runner);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 408ea6e..c3de4d5 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -49,7 +49,8 @@
* {@link AnimationAdapter}. When the animation is done animating, our callback to finish the
* animation will be invoked, at which we reparent the children back to the original parent.
*/
-class SurfaceAnimator {
+@VisibleForTesting
+public class SurfaceAnimator {
private static final String TAG = TAG_WITH_CLASS_NAME ? "SurfaceAnimator" : TAG_WM;
@@ -617,7 +618,8 @@
* Callback to be passed into {@link AnimationAdapter#startAnimation} to be invoked by the
* component that is running the animation when the animation is finished.
*/
- interface OnAnimationFinishedCallback {
+ @VisibleForTesting
+ public interface OnAnimationFinishedCallback {
void onAnimationFinished(@AnimationType int type, AnimationAdapter anim);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8385615..4922e90 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -199,6 +199,7 @@
import com.android.server.am.ActivityManagerService;
import com.android.server.am.AppTimeTracker;
import com.android.server.uri.NeededUriGrants;
+import com.android.window.flags.Flags;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -488,10 +489,6 @@
private boolean mForceShowForAllUsers;
- /** When set, will force the task to report as invisible. */
- static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
- static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
- private int mForceHiddenFlags = 0;
private boolean mForceTranslucent = false;
// The display category name for this task.
@@ -2858,7 +2855,8 @@
}
/** Bounds of the task to be used for dimming, as well as touch related tests. */
- void getDimBounds(Rect out) {
+ @Override
+ void getDimBounds(@NonNull Rect out) {
if (isRootTask()) {
getBounds(out);
return;
@@ -3306,7 +3304,8 @@
// Once at the root task level, we want to check {@link #isTranslucent(ActivityRecord)}.
// If true, we want to get the Dimmer from the level above since we don't want to animate
// the dim with the Task.
- if (!isRootTask() || isTranslucent(null)) {
+ if (!isRootTask() || (Flags.dimmerRefactor() && isTranslucentAndVisible())
+ || isTranslucent(null)) {
return super.getDimmer();
}
@@ -4492,20 +4491,13 @@
* Sets/unsets the forced-hidden state flag for this task depending on {@param set}.
* @return Whether the force hidden state changed
*/
- boolean setForceHidden(int flags, boolean set) {
- int newFlags = mForceHiddenFlags;
- if (set) {
- newFlags |= flags;
- } else {
- newFlags &= ~flags;
- }
- if (mForceHiddenFlags == newFlags) {
- return false;
- }
-
+ @Override
+ boolean setForceHidden(@FlagForceHidden int flags, boolean set) {
final boolean wasHidden = isForceHidden();
final boolean wasVisible = isVisible();
- mForceHiddenFlags = newFlags;
+ if (!super.setForceHidden(flags, set)) {
+ return false;
+ }
final boolean nowHidden = isForceHidden();
if (wasHidden != nowHidden) {
final String reason = "setForceHidden";
@@ -4536,11 +4528,6 @@
return super.isAlwaysOnTop();
}
- @Override
- protected boolean isForceHidden() {
- return mForceHiddenFlags != 0;
- }
-
boolean isForceHiddenForPinnedTask() {
return (mForceHiddenFlags & FLAG_FORCE_HIDDEN_FOR_PINNED_TASK) != 0;
}
@@ -5648,6 +5635,8 @@
if (noAnimation) {
mDisplayContent.prepareAppTransition(TRANSIT_NONE);
mTaskSupervisor.mNoAnimActivities.add(top);
+ mTransitionController.collect(top);
+ mTransitionController.setNoAnimation(top);
ActivityOptions.abort(options);
} else {
updateTransitLocked(TRANSIT_TO_FRONT, options);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 50bc825..906b3b5 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -103,6 +103,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.am.HostingRecord;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -209,7 +210,26 @@
*/
int mMinHeight;
- Dimmer mDimmer = new Dimmer(this);
+ Dimmer mDimmer = Flags.dimmerRefactor()
+ ? new SmoothDimmer(this) : new LegacyDimmer(this);
+
+ /** Apply the dim layer on the embedded TaskFragment. */
+ static final int EMBEDDED_DIM_AREA_TASK_FRAGMENT = 0;
+
+ /** Apply the dim layer on the parent Task for an embedded TaskFragment. */
+ static final int EMBEDDED_DIM_AREA_PARENT_TASK = 1;
+
+ /**
+ * The type of dim layer area for an embedded TaskFragment.
+ */
+ @IntDef(prefix = {"EMBEDDED_DIM_AREA_"}, value = {
+ EMBEDDED_DIM_AREA_TASK_FRAGMENT,
+ EMBEDDED_DIM_AREA_PARENT_TASK,
+ })
+ @interface EmbeddedDimArea {}
+
+ @EmbeddedDimArea
+ private int mEmbeddedDimArea = EMBEDDED_DIM_AREA_TASK_FRAGMENT;
/** This task fragment will be removed when the cleanup of its children are done. */
private boolean mIsRemovalRequested;
@@ -342,6 +362,19 @@
*/
private boolean mIsolatedNav;
+ /** When set, will force the task to report as invisible. */
+ static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
+ static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
+ static final int FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG = 1 << 2;
+
+ @IntDef(prefix = {"FLAG_FORCE_HIDDEN_"}, value = {
+ FLAG_FORCE_HIDDEN_FOR_PINNED_TASK,
+ FLAG_FORCE_HIDDEN_FOR_TASK_ORG,
+ FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG,
+ }, flag = true)
+ @interface FlagForceHidden {}
+ protected int mForceHiddenFlags = 0;
+
final Point mLastSurfaceSize = new Point();
private final Rect mTmpBounds = new Rect();
@@ -825,7 +858,25 @@
* Returns whether this TaskFragment is currently forced to be hidden for any reason.
*/
protected boolean isForceHidden() {
- return false;
+ return mForceHiddenFlags != 0;
+ }
+
+ /**
+ * Sets/unsets the forced-hidden state flag for this task depending on {@param set}.
+ * @return Whether the force hidden state changed
+ */
+ boolean setForceHidden(@FlagForceHidden int flags, boolean set) {
+ int newFlags = mForceHiddenFlags;
+ if (set) {
+ newFlags |= flags;
+ } else {
+ newFlags &= ~flags;
+ }
+ if (mForceHiddenFlags == newFlags) {
+ return false;
+ }
+ mForceHiddenFlags = newFlags;
+ return true;
}
protected boolean isForceTranslucent() {
@@ -969,7 +1020,7 @@
// A TaskFragment isn't translucent if it has at least one visible activity that occludes
// this TaskFragment.
return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this,
- starting) == null;
+ starting, true /* ignoringKeyguard */) == null;
}
/**
@@ -982,7 +1033,20 @@
return true;
}
// Including finishing Activity if the TaskFragment is becoming invisible in the transition.
- return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null;
+ return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this,
+ true /* ignoringKeyguard */) == null;
+ }
+
+ /**
+ * Like {@link #isTranslucent(ActivityRecord)} but evaluating the actual visibility of the
+ * windows rather than their visibility ignoring keyguard.
+ */
+ boolean isTranslucentAndVisible() {
+ if (!isAttached() || isForceHidden() || isForceTranslucent()) {
+ return true;
+ }
+ return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this, null,
+ false /* ignoringKeyguard */) == null;
}
ActivityRecord getTopNonFinishingActivity() {
@@ -2929,14 +2993,27 @@
@Override
Dimmer getDimmer() {
- // If the window is in an embedded TaskFragment, we want to dim at the TaskFragment.
- if (asTask() == null) {
+ // If this is in an embedded TaskFragment and we want the dim applies on the TaskFragment.
+ if (mIsEmbedded && mEmbeddedDimArea == EMBEDDED_DIM_AREA_TASK_FRAGMENT) {
return mDimmer;
}
return super.getDimmer();
}
+ /** Bounds to be used for dimming, as well as touch related tests. */
+ void getDimBounds(@NonNull Rect out) {
+ if (mIsEmbedded && mEmbeddedDimArea == EMBEDDED_DIM_AREA_PARENT_TASK) {
+ out.set(getTask().getBounds());
+ } else {
+ out.set(getBounds());
+ }
+ }
+
+ void setEmbeddedDimArea(@EmbeddedDimArea int embeddedDimArea) {
+ mEmbeddedDimArea = embeddedDimArea;
+ }
+
@Override
void prepareSurfaces() {
if (asTask() != null) {
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 04164c2..ff766be 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -51,7 +51,6 @@
import android.window.ITaskFragmentOrganizerController;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOperation;
-import android.window.TaskFragmentOrganizerToken;
import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
@@ -745,9 +744,9 @@
}
}
- boolean isSystemOrganizer(@NonNull TaskFragmentOrganizerToken token) {
+ boolean isSystemOrganizer(@NonNull IBinder organizerToken) {
final TaskFragmentOrganizerState state =
- mTaskFragmentOrganizerState.get(token.asBinder());
+ mTaskFragmentOrganizerState.get(organizerToken);
return state != null && state.mIsSystemOrganizer;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f3fb7c4..7d65c61 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -418,8 +418,8 @@
if (transientRoot == null) continue;
final WindowContainer<?> rootParent = transientRoot.getParent();
if (rootParent == null || rootParent.getTopChild() == transientRoot) continue;
- final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor
- .mOpaqueActivityHelper.getOpaqueActivity(rootParent);
+ final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor.mOpaqueActivityHelper
+ .getOpaqueActivity(rootParent, true /* ignoringKeyguard */);
if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) {
occludedCount++;
}
@@ -3321,8 +3321,8 @@
mFrozen.add(wc);
final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc));
changeInfo.mSnapshot = snapshotSurface;
- if (isDisplayRotation) {
- // This isn't cheap, so only do it for display rotations.
+ if (changeInfo.mRotation != wc.mDisplayContent.getRotation()) {
+ // This isn't cheap, so only do it for rotation change.
changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma(
buffer, screenshotBuffer.getColorSpace());
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index f509463..8ac21e4 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -757,7 +757,7 @@
final TransitionRequestInfo request = new TransitionRequestInfo(transition.mType,
startTaskInfo, pipTaskInfo, remoteTransition, displayChange,
- transition.getFlags());
+ transition.getFlags(), transition.getSyncId());
transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos();
transition.mLogger.mRequest = request;
@@ -1067,12 +1067,20 @@
// legacy sync
mSyncEngine.startSyncSet(queued.mLegacySync);
}
- // Post this so that the now-playing transition logic isn't interrupted.
- mAtm.mH.post(() -> {
- synchronized (mAtm.mGlobalLock) {
- queued.mOnStartCollect.onCollectStarted(true /* deferred */);
- }
- });
+ if (queued.mTransition != null
+ && queued.mTransition.mType == WindowManager.TRANSIT_SLEEP) {
+ // SLEEP transitions are special in that they don't collect anything (in fact if they
+ // do collect things it can cause problems). So, we need to run it's onCollectStarted
+ // immediately.
+ queued.mOnStartCollect.onCollectStarted(true /* deferred */);
+ } else {
+ // Post this so that the now-playing transition logic isn't interrupted.
+ mAtm.mH.post(() -> {
+ synchronized (mAtm.mGlobalLock) {
+ queued.mOnStartCollect.onCollectStarted(true /* deferred */);
+ }
+ });
+ }
}
void moveToPlaying(Transition transition) {
@@ -1584,8 +1592,8 @@
TransitionInfo mInfo;
private String buildOnSendLog() {
- StringBuilder sb = new StringBuilder("Sent Transition #").append(mSyncId)
- .append(" createdAt=").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
+ StringBuilder sb = new StringBuilder("Sent Transition (#").append(mSyncId)
+ .append(") createdAt=").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
if (mRequest != null) {
sb.append(" via request=").append(mRequest);
}
@@ -1609,7 +1617,8 @@
void logOnSend() {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnSendLog());
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, " startWCT=%s", mStartWCT);
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, " info=%s", mInfo);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, " info=%s",
+ mInfo.toString(" " /* prefix */));
}
private static String toMsString(long nanos) {
@@ -1617,8 +1626,8 @@
}
private String buildOnFinishLog() {
- StringBuilder sb = new StringBuilder("Finish Transition #").append(mSyncId)
- .append(": created at ").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
+ StringBuilder sb = new StringBuilder("Finish Transition (#").append(mSyncId)
+ .append("): created at ").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
sb.append(" collect-started=").append(toMsString(mCollectTimeNs - mCreateTimeNs));
if (mRequestTimeNs != 0) {
sb.append(" request-sent=").append(toMsString(mRequestTimeNs - mCreateTimeNs));
diff --git a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
index 41c1e79..c071396 100644
--- a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
+++ b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
@@ -70,6 +70,9 @@
}
boolean isVisibilityUnknown(ActivityRecord r) {
+ if (mUnknownApps.isEmpty()) {
+ return false;
+ }
return mUnknownApps.containsKey(r);
}
@@ -90,6 +93,9 @@
}
void appRemovedOrHidden(@NonNull ActivityRecord activity) {
+ if (mUnknownApps.isEmpty()) {
+ return;
+ }
if (DEBUG_UNKNOWN_APP_VISIBILITY) {
Slog.d(TAG, "App removed or hidden activity=" + activity);
}
@@ -117,8 +123,11 @@
* Notifies that {@param activity} has finished resuming.
*/
void notifyAppResumedFinished(@NonNull ActivityRecord activity) {
- if (mUnknownApps.containsKey(activity)
- && mUnknownApps.get(activity) == UNKNOWN_STATE_WAITING_RESUME) {
+ if (mUnknownApps.isEmpty()) {
+ return;
+ }
+ final Integer state = mUnknownApps.get(activity);
+ if (state != null && state == UNKNOWN_STATE_WAITING_RESUME) {
if (DEBUG_UNKNOWN_APP_VISIBILITY) {
Slog.d(TAG, "App resume finished activity=" + activity);
}
@@ -130,13 +139,16 @@
* Notifies that {@param activity} has relaid out.
*/
void notifyRelayouted(@NonNull ActivityRecord activity) {
- if (!mUnknownApps.containsKey(activity)) {
+ if (mUnknownApps.isEmpty()) {
+ return;
+ }
+ final Integer state = mUnknownApps.get(activity);
+ if (state == null) {
return;
}
if (DEBUG_UNKNOWN_APP_VISIBILITY) {
Slog.d(TAG, "App relayouted appWindow=" + activity);
}
- int state = mUnknownApps.get(activity);
if (state == UNKNOWN_STATE_WAITING_RELAYOUT || activity.mStartingWindow != null) {
mUnknownApps.put(activity, UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE);
mDisplayContent.notifyKeyguardFlagsChanged();
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 674ff48..94e66ff 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -536,7 +536,7 @@
window.mWallpaperY = y;
window.mWallpaperXStep = xStep;
window.mWallpaperYStep = yStep;
- updateWallpaperOffsetLocked(window, true);
+ updateWallpaperOffsetLocked(window, !mService.mFlags.mWallpaperOffsetAsync);
}
}
@@ -561,7 +561,7 @@
if (window.mWallpaperDisplayOffsetX != x || window.mWallpaperDisplayOffsetY != y) {
window.mWallpaperDisplayOffsetX = x;
window.mWallpaperDisplayOffsetY = y;
- updateWallpaperOffsetLocked(window, true);
+ updateWallpaperOffsetLocked(window, !mService.mFlags.mWallpaperOffsetAsync);
}
}
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 50ef52a..1ed1431 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -117,7 +117,8 @@
final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
final WindowState wallpaper = mChildren.get(wallpaperNdx);
- if (wallpaperController.updateWallpaperOffset(wallpaper, sync)) {
+ if (wallpaperController.updateWallpaperOffset(wallpaper,
+ sync && !mWmService.mFlags.mWallpaperOffsetAsync)) {
// We only want to be synchronous with one wallpaper.
sync = false;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index 00b9b4c..4667710 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -45,5 +45,9 @@
final boolean mSyncWindowConfigUpdateFlag = Flags.syncWindowConfigUpdateFlag();
+ final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag();
+
+ final boolean mWallpaperOffsetAsync = Flags.wallpaperOffsetAsync();
+
/* End Available Flags */
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f339d24..88f72f9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2261,6 +2261,7 @@
}
}
+ final boolean wasTrustedOverlay = win.isWindowTrustedOverlay();
flagChanges = win.mAttrs.flags ^ attrs.flags;
privateFlagChanges = win.mAttrs.privateFlags ^ attrs.privateFlags;
attrChanges = win.mAttrs.copyFrom(attrs);
@@ -2273,6 +2274,9 @@
if (layoutChanged && win.providesDisplayDecorInsets()) {
configChanged = displayPolicy.updateDecorInsetsInfo();
}
+ if (wasTrustedOverlay != win.isWindowTrustedOverlay()) {
+ win.updateTrustedOverlay();
+ }
if (win.mActivityRecord != null && ((flagChanges & FLAG_SHOW_WHEN_LOCKED) != 0
|| (flagChanges & FLAG_DISMISS_KEYGUARD) != 0)) {
win.mActivityRecord.checkKeyguardFlagsChanged();
@@ -5299,7 +5303,11 @@
public void displayReady() {
synchronized (mGlobalLock) {
if (mMaxUiWidth > 0) {
- mRoot.forAllDisplays(displayContent -> displayContent.setMaxUiWidth(mMaxUiWidth));
+ mRoot.forAllDisplays(dc -> {
+ if (dc.mDisplay.getType() == Display.TYPE_INTERNAL) {
+ dc.setMaxUiWidth(mMaxUiWidth);
+ }
+ });
}
applyForcedPropertiesForDefaultDisplay();
mAnimator.ready();
@@ -8453,16 +8461,18 @@
return true;
}
// For a task session, find the activity identified by the launch cookie.
- final WindowContainerToken wct = getTaskWindowContainerTokenForLaunchCookie(
+ final WindowContainerInfo wci = getTaskWindowContainerInfoForLaunchCookie(
incomingSession.getTokenToRecord());
- if (wct == null) {
+ if (wci == null) {
Slog.w(TAG, "Handling a new recording session; unable to find the "
+ "WindowContainerToken");
return false;
}
// Replace the launch cookie in the session details with the task's
// WindowContainerToken.
- incomingSession.setTokenToRecord(wct.asBinder());
+ incomingSession.setTokenToRecord(wci.getToken().asBinder());
+ // Also replace the UNKNOWN target UID with the actual UID.
+ incomingSession.setTargetUid(wci.getUid());
mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
WindowManagerService.this);
return true;
@@ -8790,21 +8800,41 @@
mAtmService.setFocusedTask(task.mTaskId, touchedActivity);
}
+ @VisibleForTesting
+ static class WindowContainerInfo {
+ private final int mUid;
+ @NonNull private final WindowContainerToken mToken;
+
+ private WindowContainerInfo(int uid, @NonNull WindowContainerToken token) {
+ this.mUid = uid;
+ this.mToken = token;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ @NonNull
+ public WindowContainerToken getToken() {
+ return mToken;
+ }
+ }
+
/**
- * Retrieve the {@link WindowContainerToken} of the task that contains the activity started
- * with the given launch cookie.
+ * Retrieve the {@link WindowContainerInfo} of the task that contains the activity started with
+ * the given launch cookie.
*
* @param launchCookie the launch cookie set on the {@link ActivityOptions} when starting an
- * activity
+ * activity
* @return a token representing the task containing the activity started with the given launch
- * cookie, or {@code null} if the token couldn't be found.
+ * cookie, or {@code null} if the token couldn't be found.
*/
@VisibleForTesting
@Nullable
- WindowContainerToken getTaskWindowContainerTokenForLaunchCookie(@NonNull IBinder launchCookie) {
+ WindowContainerInfo getTaskWindowContainerInfoForLaunchCookie(@NonNull IBinder launchCookie) {
// Find the activity identified by the launch cookie.
- final ActivityRecord targetActivity = mRoot.getActivity(
- activity -> activity.mLaunchCookie == launchCookie);
+ final ActivityRecord targetActivity =
+ mRoot.getActivity(activity -> activity.mLaunchCookie == launchCookie);
if (targetActivity == null) {
Slog.w(TAG, "Unable to find the activity for this launch cookie");
return null;
@@ -8819,7 +8849,7 @@
Slog.w(TAG, "Unable to find the WindowContainerToken for " + targetActivity.getName());
return null;
}
- return taskWindowContainerToken;
+ return new WindowContainerInfo(targetActivity.getUid(), taskWindowContainerToken);
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index dd9a88f..5ed6caf 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -24,7 +24,9 @@
import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
@@ -34,6 +36,8 @@
import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN;
+import static android.window.WindowContainerTransaction.Change.CHANGE_FOCUSABLE;
+import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN;
import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION;
@@ -61,6 +65,7 @@
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
+import static com.android.server.wm.TaskFragment.FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -821,6 +826,7 @@
return TRANSACT_EFFECTS_NONE;
}
+ int effects = TRANSACT_EFFECTS_NONE;
// When the TaskFragment is resized, we may want to create a change transition for it, for
// which we want to defer the surface update until we determine whether or not to start
// change transition.
@@ -843,7 +849,14 @@
c.getConfiguration().windowConfiguration.setBounds(absBounds);
taskFragment.setRelativeEmbeddedBounds(relBounds);
}
- final int effects = applyChanges(taskFragment, c);
+ if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
+ if (taskFragment.setForceHidden(
+ FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG, c.getHidden())) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ }
+ effects |= applyChanges(taskFragment, c);
+
if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) {
taskFragment.initializeChangeTransition(mTmpBounds0);
}
@@ -1393,6 +1406,24 @@
taskFragment.setIsolatedNav(isolatedNav);
break;
}
+ case OP_TYPE_REORDER_TO_BOTTOM_OF_TASK: {
+ final Task task = taskFragment.getTask();
+ if (task != null) {
+ task.mChildren.remove(taskFragment);
+ task.mChildren.add(0, taskFragment);
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ break;
+ }
+ case OP_TYPE_REORDER_TO_TOP_OF_TASK: {
+ final Task task = taskFragment.getTask();
+ if (task != null) {
+ task.mChildren.remove(taskFragment);
+ task.mChildren.add(taskFragment);
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ break;
+ }
}
return effects;
}
@@ -1420,6 +1451,18 @@
return false;
}
+ if ((opType == OP_TYPE_REORDER_TO_BOTTOM_OF_TASK
+ || opType == OP_TYPE_REORDER_TO_TOP_OF_TASK)
+ && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
+ final Throwable exception = new SecurityException(
+ "Only a system organizer can perform OP_TYPE_REORDER_TO_BOTTOM_OF_TASK or "
+ + "OP_TYPE_REORDER_TO_TOP_OF_TASK."
+ );
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+ opType, exception);
+ return false;
+ }
+
final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken();
return secondaryFragmentToken == null
|| validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType,
@@ -1920,6 +1963,11 @@
* For config change on {@link TaskFragment}, we only support the following operations:
* {@link WindowContainerTransaction#setRelativeBounds(WindowContainerToken, Rect)},
* {@link WindowContainerTransaction#setWindowingMode(WindowContainerToken, int)}.
+ *
+ * For a system organizer, we additionally support
+ * {@link WindowContainerTransaction#setHidden(WindowContainerToken, boolean)}, and
+ * {@link WindowContainerTransaction#setFocusable(WindowContainerToken, boolean)}. See
+ * {@link TaskFragmentOrganizerController#registerOrganizer(ITaskFragmentOrganizer, boolean)}
*/
private void enforceTaskFragmentConfigChangeAllowed(@NonNull String func,
@Nullable WindowContainer wc, @NonNull WindowContainerTransaction.Change change,
@@ -1938,31 +1986,49 @@
throw new SecurityException(msg);
}
- final int changeMask = change.getChangeMask();
- final int configSetMask = change.getConfigSetMask();
- final int windowSetMask = change.getWindowSetMask();
- if (changeMask == 0 && configSetMask == 0 && windowSetMask == 0
- && change.getWindowingMode() >= 0) {
- // The change contains only setWindowingMode, which is allowed.
- return;
+ final int originalChangeMask = change.getChangeMask();
+ final int originalConfigSetMask = change.getConfigSetMask();
+ final int originalWindowSetMask = change.getWindowSetMask();
+
+ int changeMaskToBeChecked = originalChangeMask;
+ int configSetMaskToBeChecked = originalConfigSetMask;
+ int windowSetMaskToBeChecked = originalWindowSetMask;
+
+ if (mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
+ // System organizer is allowed to update the hidden and focusable state.
+ // We unset the CHANGE_HIDDEN and CHANGE_FOCUSABLE bits because they are checked here.
+ changeMaskToBeChecked &= ~CHANGE_HIDDEN;
+ changeMaskToBeChecked &= ~CHANGE_FOCUSABLE;
}
- if (changeMask != CHANGE_RELATIVE_BOUNDS
- || configSetMask != ActivityInfo.CONFIG_WINDOW_CONFIGURATION
- || windowSetMask != WindowConfiguration.WINDOW_CONFIG_BOUNDS) {
- // None of the change should be requested from a TaskFragment organizer except
- // setRelativeBounds and setWindowingMode.
- // For setRelativeBounds, we don't need to check whether it is outside of the Task
+
+ // setRelativeBounds is allowed.
+ if ((changeMaskToBeChecked & CHANGE_RELATIVE_BOUNDS) != 0
+ && (configSetMaskToBeChecked & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
+ && (windowSetMaskToBeChecked & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) {
+ // For setRelativeBounds, we don't need to check whether it is outside the Task
// bounds, because it is possible that the Task is also resizing, for which we don't
// want to throw an exception. The bounds will be adjusted in
// TaskFragment#translateRelativeBoundsToAbsoluteBounds.
- String msg = "Permission Denial: " + func + " from pid="
- + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
- + " trying to apply changes of changeMask=" + changeMask
- + " configSetMask=" + configSetMask + " windowSetMask=" + windowSetMask
- + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
+ changeMaskToBeChecked &= ~CHANGE_RELATIVE_BOUNDS;
+ configSetMaskToBeChecked &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
+ windowSetMaskToBeChecked &= ~WindowConfiguration.WINDOW_CONFIG_BOUNDS;
}
+
+ if (changeMaskToBeChecked == 0 && configSetMaskToBeChecked == 0
+ && windowSetMaskToBeChecked == 0) {
+ // All the changes have been checked.
+ // Note that setWindowingMode is always allowed, so we don't need to check the mask.
+ return;
+ }
+
+ final String msg = "Permission Denial: " + func + " from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " trying to apply changes of changeMask=" + originalChangeMask
+ + " configSetMask=" + originalConfigSetMask
+ + " windowSetMask=" + originalWindowSetMask
+ + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
}
private void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams,
@@ -2019,7 +2085,7 @@
TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
taskFragment.setTaskFragmentOrganizer(organizerToken,
ownerActivity.getUid(), ownerActivity.info.processName,
- mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken));
+ mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder()));
final int position;
if (creationParams.getPairedPrimaryFragmentToken() != null) {
// When there is a paired primary TaskFragment, we want to place the new TaskFragment
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4beec2b..3a793e9 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -186,6 +186,7 @@
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyCache;
+import android.app.servertransaction.WindowStateResizeItem;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Matrix;
@@ -1188,7 +1189,20 @@
}
}
- public boolean isWindowTrustedOverlay() {
+ @Override
+ void setInitialSurfaceControlProperties(SurfaceControl.Builder b) {
+ super.setInitialSurfaceControlProperties(b);
+ if (surfaceTrustedOverlay() && isWindowTrustedOverlay()) {
+ getPendingTransaction().setTrustedOverlay(mSurfaceControl, true);
+ }
+ }
+
+ void updateTrustedOverlay() {
+ mInputWindowHandle.setTrustedOverlay(getPendingTransaction(), mSurfaceControl,
+ isWindowTrustedOverlay());
+ }
+
+ boolean isWindowTrustedOverlay() {
return InputMonitor.isTrustedOverlay(mAttrs.type)
|| ((mAttrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0
&& mSession.mCanAddInternalSystemWindow)
@@ -2755,12 +2769,7 @@
// bounds, as they would be used to display the dim layer.
final TaskFragment taskFragment = getTaskFragment();
if (taskFragment != null) {
- final Task task = taskFragment.asTask();
- if (task != null) {
- task.getDimBounds(mTmpRect);
- } else {
- mTmpRect.set(taskFragment.getBounds());
- }
+ taskFragment.getDimBounds(mTmpRect);
} else if (getRootTask() != null) {
getRootTask().getDimBounds(mTmpRect);
}
@@ -3732,30 +3741,44 @@
markRedrawForSyncReported();
- try {
- mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
- getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
- syncWithBuffers ? mSyncSeqId : -1, isDragResizing);
- if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
- .getMergedConfiguration().windowConfiguration.getRotation()) {
- mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Requested redraw for orientation change: %s", this);
+ if (mWmService.mFlags.mWindowStateResizeItemFlag) {
+ getProcess().scheduleClientTransactionItem(
+ WindowStateResizeItem.obtain(mClient, mClientWindowFrames, reportDraw,
+ mLastReportedConfiguration, getCompatInsetsState(), forceRelayout,
+ alwaysConsumeSystemBars, displayId,
+ syncWithBuffers ? mSyncSeqId : -1, isDragResizing));
+ onResizePostDispatched(drawPending, prevRotation, displayId);
+ } else {
+ // TODO(b/301870955): cleanup after launch
+ try {
+ mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
+ getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
+ syncWithBuffers ? mSyncSeqId : -1, isDragResizing);
+ onResizePostDispatched(drawPending, prevRotation, displayId);
+ } catch (RemoteException e) {
+ // Cancel orientation change of this window to avoid blocking unfreeze display.
+ setOrientationChanging(false);
+ mLastFreezeDuration = (int) (SystemClock.elapsedRealtime()
+ - mWmService.mDisplayFreezeTime);
+ Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e);
}
-
- if (mWmService.mAccessibilityController.hasCallbacks()) {
- mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId);
- }
- } catch (RemoteException e) {
- // Cancel orientation change of this window to avoid blocking unfreeze display.
- setOrientationChanging(false);
- mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- - mWmService.mDisplayFreezeTime);
- Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e);
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
+ private void onResizePostDispatched(boolean drawPending, int prevRotation, int displayId) {
+ if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
+ .getMergedConfiguration().windowConfiguration.getRotation()) {
+ mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Requested redraw for orientation change: %s", this);
+ }
+
+ if (mWmService.mAccessibilityController.hasCallbacks()) {
+ mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId);
+ }
+ }
+
boolean inRelaunchingActivity() {
return mActivityRecord != null && mActivityRecord.isRelaunching();
}
@@ -5113,8 +5136,8 @@
private void applyDims() {
if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind())
- && mToken.isVisibleRequested() && isVisibleNow() && !mHidden
- && mTransitionController.canApplyDim(getTask())) {
+ && (Dimmer.DIMMER_REFACTOR ? mWinAnimator.getShown() : isVisibleNow())
+ && !mHidden && mTransitionController.canApplyDim(getTask())) {
// Only show the Dimmer when the following is satisfied:
// 1. The window has the flag FLAG_DIM_BEHIND or blur behind is requested
// 2. The WindowToken is not hidden so dims aren't shown when the window is exiting.
@@ -5124,7 +5147,13 @@
mIsDimming = true;
final float dimAmount = (mAttrs.flags & FLAG_DIM_BEHIND) != 0 ? mAttrs.dimAmount : 0;
final int blurRadius = shouldDrawBlurBehind() ? mAttrs.getBlurBehindRadius() : 0;
- getDimmer().dimBelow(this, dimAmount, blurRadius);
+ // If the window is visible from surface flinger perspective (mWinAnimator.getShown())
+ // but not window manager visible (!isVisibleNow()), it can still be the parent of the
+ // dim, but can not create a new surface or continue a dim alone.
+ if (isVisibleNow()) {
+ getDimmer().adjustAppearance(this, dimAmount, blurRadius);
+ }
+ getDimmer().adjustRelativeLayer(this, -1 /* relativeLayer */);
}
}
@@ -5184,14 +5213,16 @@
void prepareSurfaces() {
mIsDimming = false;
if (mHasSurface) {
- applyDims();
+ if (!Dimmer.DIMMER_REFACTOR) {
+ applyDims();
+ }
updateSurfacePositionNonOrganized();
// Send information to SurfaceFlinger about the priority of the current window.
updateFrameRateSelectionPriorityIfNeeded();
updateScaleIfNeeded();
mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
- if (surfaceTrustedOverlay()) {
- getSyncTransaction().setTrustedOverlay(mSurfaceControl, isWindowTrustedOverlay());
+ if (Dimmer.DIMMER_REFACTOR) {
+ applyDims();
}
}
super.prepareSurfaces();
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index e434f29..7d21dbf 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -569,6 +569,7 @@
&& asActivityRecord() != null && isVisible()) {
// Trigger an activity level rotation transition.
mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_CHANGE, this);
+ mTransitionController.collectVisibleChange(this);
mTransitionController.setReady(this);
}
final int originalRotation = getWindowConfiguration().getRotation();
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index bc70658..24ee163 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -70,7 +70,7 @@
"com_android_server_UsbHostManager.cpp",
"com_android_server_vibrator_VibratorController.cpp",
"com_android_server_vibrator_VibratorManagerService.cpp",
- "com_android_server_PersistentDataBlockService.cpp",
+ "com_android_server_pdb_PersistentDataBlockService.cpp",
"com_android_server_am_LowMemDetector.cpp",
"com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
"com_android_server_sensor_SensorService.cpp",
@@ -189,7 +189,7 @@
"android.hardware.thermal@1.0",
"android.hardware.thermal-V1-ndk",
"android.hardware.tv.input@1.0",
- "android.hardware.tv.input-V1-ndk",
+ "android.hardware.tv.input-V2-ndk",
"android.hardware.vibrator-V2-cpp",
"android.hardware.vibrator@1.0",
"android.hardware.vibrator@1.1",
@@ -244,5 +244,5 @@
filegroup {
name: "lib_oomConnection_native",
- srcs: ["com_android_server_am_OomConnection.cpp",],
+ srcs: ["com_android_server_am_OomConnection.cpp"],
}
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 7e8ce60..0e45f61 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -20,6 +20,7 @@
per-file com_android_server_location_* = file:/location/java/android/location/OWNERS
per-file com_android_server_locksettings_* = file:/services/core/java/com/android/server/locksettings/OWNERS
per-file com_android_server_net_* = file:/services/core/java/com/android/server/net/OWNERS
+per-file com_android_server_pdb_* = file:/services/core/java/com/android/server/pdb/OWNERS
per-file com_android_server_pm_* = file:/services/core/java/com/android/server/pm/OWNERS
per-file com_android_server_power_* = file:/services/core/java/com/android/server/power/OWNERS
per-file com_android_server_powerstats_* = file:/services/core/java/com/android/server/powerstats/OWNERS
diff --git a/services/core/jni/com_android_server_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_PersistentDataBlockService.cpp
deleted file mode 100644
index 97e69fb..0000000
--- a/services/core/jni/com_android_server_PersistentDataBlockService.cpp
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <android_runtime/AndroidRuntime.h>
-#include <nativehelper/JNIHelp.h>
-#include <jni.h>
-#include <nativehelper/ScopedUtfChars.h>
-
-#include <utils/misc.h>
-#include <sys/ioctl.h>
-#include <sys/mount.h>
-#include <utils/Log.h>
-
-
-#include <inttypes.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <string.h>
-
-namespace android {
-
- uint64_t get_block_device_size(int fd)
- {
- uint64_t size = 0;
- int ret;
-
- ret = ioctl(fd, BLKGETSIZE64, &size);
-
- if (ret)
- return 0;
-
- return size;
- }
-
- int wipe_block_device(int fd)
- {
- uint64_t range[2];
- int ret;
- uint64_t len = get_block_device_size(fd);
-
- range[0] = 0;
- range[1] = len;
-
- if (range[1] == 0)
- return 0;
-
- ret = ioctl(fd, BLKSECDISCARD, &range);
- if (ret < 0) {
- ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno));
- range[0] = 0;
- range[1] = len;
- ret = ioctl(fd, BLKDISCARD, &range);
- if (ret < 0) {
- ALOGE("Discard failed: %s\n", strerror(errno));
- return -1;
- } else {
- ALOGE("Wipe via secure discard failed, used non-secure discard instead\n");
- return 0;
- }
-
- }
-
- return ret;
- }
-
- static jlong com_android_server_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath)
- {
- ScopedUtfChars path(env, jpath);
- int fd = open(path.c_str(), O_RDONLY);
-
- if (fd < 0)
- return 0;
-
- const uint64_t size = get_block_device_size(fd);
-
- close(fd);
-
- return size;
- }
-
- static int com_android_server_PersistentDataBlockService_wipe(JNIEnv *env, jclass, jstring jpath) {
- ScopedUtfChars path(env, jpath);
- int fd = open(path.c_str(), O_WRONLY);
-
- if (fd < 0)
- return 0;
-
- const int ret = wipe_block_device(fd);
-
- close(fd);
-
- return ret;
- }
-
- static const JNINativeMethod sMethods[] = {
- /* name, signature, funcPtr */
- {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_PersistentDataBlockService_getBlockDeviceSize},
- {"nativeWipe", "(Ljava/lang/String;)I", (void*)com_android_server_PersistentDataBlockService_wipe},
- };
-
- int register_android_server_PersistentDataBlockService(JNIEnv* env)
- {
- return jniRegisterNativeMethods(env, "com/android/server/PersistentDataBlockService",
- sMethods, NELEM(sMethods));
- }
-
-} /* namespace android */
\ No newline at end of file
diff --git a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
index b256f16..1844d30 100644
--- a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
+++ b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
@@ -24,33 +24,33 @@
#include "utils/Log.h"
namespace android {
-static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray juids,
+static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray jappIds,
jfloatArray jthresholds) {
- if (juids == nullptr || jthresholds == nullptr) return;
+ if (jappIds == nullptr || jthresholds == nullptr) return;
- ScopedIntArrayRO uids(env, juids);
+ ScopedIntArrayRO appIds(env, jappIds);
ScopedFloatArrayRO thresholds(env, jthresholds);
- if (uids.size() != thresholds.size()) {
- ALOGE("uids size exceeds thresholds size!");
+ if (appIds.size() != thresholds.size()) {
+ ALOGE("appIds size exceeds thresholds size!");
return;
}
- std::vector<int32_t> uidVector;
+ std::vector<int32_t> appIdVector;
std::vector<float> thresholdVector;
- size_t size = uids.size();
- uidVector.reserve(size);
+ size_t size = appIds.size();
+ appIdVector.reserve(size);
thresholdVector.reserve(size);
for (int i = 0; i < size; i++) {
- uidVector.push_back(static_cast<int32_t>(uids[i]));
+ appIdVector.push_back(static_cast<int32_t>(appIds[i]));
thresholdVector.push_back(static_cast<float>(thresholds[i]));
}
- SurfaceComposerClient::updateSmallAreaDetection(uidVector, thresholdVector);
+ SurfaceComposerClient::updateSmallAreaDetection(appIdVector, thresholdVector);
}
-static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint uid,
+static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint appId,
jfloat threshold) {
- SurfaceComposerClient::setSmallAreaDetectionThreshold(uid, threshold);
+ SurfaceComposerClient::setSmallAreaDetectionThreshold(appId, threshold);
}
static const JNINativeMethod gMethods[] = {
diff --git a/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp
new file mode 100644
index 0000000..1e3cfd0
--- /dev/null
+++ b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android_runtime/AndroidRuntime.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <utils/Log.h>
+#include <utils/misc.h>
+
+namespace android {
+
+uint64_t get_block_device_size(int fd) {
+ uint64_t size = 0;
+ int ret;
+
+ ret = ioctl(fd, BLKGETSIZE64, &size);
+
+ if (ret) return 0;
+
+ return size;
+}
+
+int wipe_block_device(int fd) {
+ uint64_t range[2];
+ int ret;
+ uint64_t len = get_block_device_size(fd);
+
+ range[0] = 0;
+ range[1] = len;
+
+ if (range[1] == 0) return 0;
+
+ ret = ioctl(fd, BLKSECDISCARD, &range);
+ if (ret < 0) {
+ ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno));
+ range[0] = 0;
+ range[1] = len;
+ ret = ioctl(fd, BLKDISCARD, &range);
+ if (ret < 0) {
+ ALOGE("Discard failed: %s\n", strerror(errno));
+ return -1;
+ } else {
+ ALOGE("Wipe via secure discard failed, used non-secure discard instead\n");
+ return 0;
+ }
+ }
+
+ return ret;
+}
+
+static jlong com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env,
+ jclass,
+ jstring jpath) {
+ ScopedUtfChars path(env, jpath);
+ int fd = open(path.c_str(), O_RDONLY);
+
+ if (fd < 0) return 0;
+
+ const uint64_t size = get_block_device_size(fd);
+
+ close(fd);
+
+ return size;
+}
+
+static int com_android_server_pdb_PersistentDataBlockService_wipe(JNIEnv *env, jclass,
+ jstring jpath) {
+ ScopedUtfChars path(env, jpath);
+ int fd = open(path.c_str(), O_WRONLY);
+
+ if (fd < 0) return 0;
+
+ const int ret = wipe_block_device(fd);
+
+ close(fd);
+
+ return ret;
+}
+
+static const JNINativeMethod sMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J",
+ (void *)com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize},
+ {"nativeWipe", "(Ljava/lang/String;)I",
+ (void *)com_android_server_pdb_PersistentDataBlockService_wipe},
+};
+
+int register_android_server_pdb_PersistentDataBlockService(JNIEnv *env) {
+ return jniRegisterNativeMethods(env, "com/android/server/pdb/PersistentDataBlockService",
+ sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
\ No newline at end of file
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index df44895..11734da 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -47,7 +47,7 @@
int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*);
int register_android_server_tv_TvUinputBridge(JNIEnv* env);
int register_android_server_tv_TvInputHal(JNIEnv* env);
-int register_android_server_PersistentDataBlockService(JNIEnv* env);
+int register_android_server_pdb_PersistentDataBlockService(JNIEnv* env);
int register_android_server_Watchdog(JNIEnv* env);
int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
int register_android_server_SyntheticPasswordManager(JNIEnv* env);
@@ -108,7 +108,7 @@
register_android_server_BatteryStatsService(env);
register_android_server_tv_TvUinputBridge(env);
register_android_server_tv_TvInputHal(env);
- register_android_server_PersistentDataBlockService(env);
+ register_android_server_pdb_PersistentDataBlockService(env);
register_android_server_HardwarePropertiesManagerService(env);
register_android_server_storage_AppFuse(env);
register_android_server_SyntheticPasswordManager(env);
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
index 68e2c9a..dc05462 100644
--- a/services/core/jni/tvinput/JTvInputHal.cpp
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -147,7 +147,6 @@
}
int JTvInputHal::setTvMessageEnabled(int deviceId, int streamId, int type, bool enabled) {
- Mutex::Autolock autoLock(&mLock);
if (!mTvInput->setTvMessageEnabled(deviceId, streamId,
static_cast<AidlTvMessageEventType>(type), enabled)
.isOk()) {
@@ -188,7 +187,7 @@
void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) {
{
- Mutex::Autolock autoLock(&mLock);
+ Mutex::Autolock autoLock(&mStreamLock);
mConnections.add(info.deviceId, KeyedVector<int, Connection>());
}
JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -275,7 +274,7 @@
void JTvInputHal::onDeviceUnavailable(int deviceId) {
{
- Mutex::Autolock autoLock(&mLock);
+ Mutex::Autolock autoLock(&mStreamLock);
KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
for (size_t i = 0; i < connections.size(); ++i) {
removeStream(deviceId, connections.keyAt(i));
@@ -289,7 +288,7 @@
void JTvInputHal::onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus) {
{
- Mutex::Autolock autoLock(&mLock);
+ Mutex::Autolock autoLock(&mStreamLock);
KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
for (size_t i = 0; i < connections.size(); ++i) {
removeStream(deviceId, connections.keyAt(i));
@@ -330,7 +329,7 @@
void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) {
sp<BufferProducerThread> thread;
{
- Mutex::Autolock autoLock(&mLock);
+ Mutex::Autolock autoLock(&mStreamLock);
KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
Connection& connection = connections.editValueFor(streamId);
if (connection.mThread == NULL) {
@@ -369,12 +368,20 @@
}
JTvInputHal::TvMessageEventWrapper JTvInputHal::TvMessageEventWrapper::createEventWrapper(
- const AidlTvMessageEvent& aidlTvMessageEvent) {
+ const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage) {
+ auto messageList = aidlTvMessageEvent.messages;
TvMessageEventWrapper event;
- event.messages.insert(event.messages.begin(), std::begin(aidlTvMessageEvent.messages) + 1,
- std::end(aidlTvMessageEvent.messages));
+ // Handle backwards compatibility for V1
+ if (isLegacyMessage) {
+ event.deviceId = messageList[0].groupId;
+ event.messages.insert(event.messages.begin(), std::begin(messageList) + 1,
+ std::end(messageList));
+ } else {
+ event.deviceId = aidlTvMessageEvent.deviceId;
+ event.messages.insert(event.messages.begin(), std::begin(messageList),
+ std::end(messageList));
+ }
event.streamId = aidlTvMessageEvent.streamId;
- event.deviceId = aidlTvMessageEvent.messages[0].groupId;
event.type = aidlTvMessageEvent.type;
return event;
}
@@ -450,15 +457,30 @@
::ndk::ScopedAStatus JTvInputHal::TvInputCallback::notifyTvMessageEvent(
const AidlTvMessageEvent& event) {
const std::string DEVICE_ID_SUBTYPE = "device_id";
- if (event.messages.size() > 1 && event.messages[0].subType == DEVICE_ID_SUBTYPE) {
- mHal->mLooper
- ->sendMessage(new NotifyTvMessageHandler(mHal,
- TvMessageEventWrapper::createEventWrapper(
- event)),
- static_cast<int>(event.type));
+ ::ndk::ScopedAStatus status = ::ndk::ScopedAStatus::ok();
+ int32_t aidlVersion = 0;
+ if (mHal->mTvInput->getAidlInterfaceVersion(&aidlVersion).isOk() && event.messages.size() > 0) {
+ bool validLegacyMessage = aidlVersion == 1 &&
+ event.messages[0].subType == DEVICE_ID_SUBTYPE && event.messages.size() > 1;
+ bool validTvMessage = aidlVersion > 1 && event.messages.size() > 0;
+ if (validLegacyMessage || validTvMessage) {
+ mHal->mLooper->sendMessage(
+ new NotifyTvMessageHandler(mHal,
+ TvMessageEventWrapper::
+ createEventWrapper(event,
+ validLegacyMessage)),
+ static_cast<int>(event.type));
+ } else {
+ status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ ALOGE("The TVMessage event was malformed for HAL version: %d", aidlVersion);
+ }
+ } else {
+ status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ ALOGE("The TVMessage event was empty or the HAL version (version: %d) could not "
+ "be inferred.",
+ aidlVersion);
}
-
- return ::ndk::ScopedAStatus::ok();
+ return status;
}
JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr<AidlITvInput>& aidlTvInput)
@@ -522,4 +544,12 @@
}
}
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::getAidlInterfaceVersion(int32_t* _aidl_return) {
+ if (mIsHidl) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ } else {
+ return mAidlTvInput->getInterfaceVersion(_aidl_return);
+ }
+}
+
} // namespace android
diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h
index b7b4b16..6026a10 100644
--- a/services/core/jni/tvinput/JTvInputHal.h
+++ b/services/core/jni/tvinput/JTvInputHal.h
@@ -138,7 +138,7 @@
TvMessageEventWrapper() {}
static TvMessageEventWrapper createEventWrapper(
- const AidlTvMessageEvent& aidlTvMessageEvent);
+ const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage);
int streamId;
int deviceId;
@@ -195,6 +195,7 @@
::ndk::ScopedAStatus getTvMessageQueueDesc(
MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue, int32_t in_deviceId,
int32_t in_streamId);
+ ::ndk::ScopedAStatus getAidlInterfaceVersion(int32_t* _aidl_return);
private:
::ndk::ScopedAStatus hidlSetCallback(const std::shared_ptr<TvInputCallback>& in_callback);
@@ -220,7 +221,6 @@
void onTvMessage(int deviceId, int streamId, AidlTvMessageEventType type,
AidlTvMessage& message, signed char data[], int dataLength);
- Mutex mLock;
Mutex mStreamLock;
jweak mThiz;
sp<Looper> mLooper;
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index debd891..215934f 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -272,12 +272,12 @@
<xs:element name="brightnessDecreaseDebounceMillis" type="xs:nonNegativeInteger">
<xs:annotation name="final"/>
</xs:element>
- <!-- Animation time for brightness increase in millis -->
- <xs:element name="brightnessIncreaseDurationMillis" type="xs:nonNegativeInteger">
+ <!-- Animation speed for brightness increase. In framework brightness units per second. -->
+ <xs:element name="screenBrightnessRampIncrease" type="nonNegativeDecimal">
<xs:annotation name="final"/>
</xs:element>
- <!-- Animation time for brightness decrease in millis -->
- <xs:element name="brightnessDecreaseDurationMillis" type="xs:nonNegativeInteger">
+ <!-- Animation speed for brightness decrease. In framework brightness units per second. -->
+ <xs:element name="screenBrightnessRampDecrease" type="nonNegativeDecimal">
<xs:annotation name="final"/>
</xs:element>
</xs:complexType>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 2d27f0c..f7e0043 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -179,15 +179,15 @@
public class HdrBrightnessConfig {
ctor public HdrBrightnessConfig();
method public final java.math.BigInteger getBrightnessDecreaseDebounceMillis();
- method public final java.math.BigInteger getBrightnessDecreaseDurationMillis();
method public final java.math.BigInteger getBrightnessIncreaseDebounceMillis();
- method public final java.math.BigInteger getBrightnessIncreaseDurationMillis();
method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getBrightnessMap();
+ method public final java.math.BigDecimal getScreenBrightnessRampDecrease();
+ method public final java.math.BigDecimal getScreenBrightnessRampIncrease();
method public final void setBrightnessDecreaseDebounceMillis(java.math.BigInteger);
- method public final void setBrightnessDecreaseDurationMillis(java.math.BigInteger);
method public final void setBrightnessIncreaseDebounceMillis(java.math.BigInteger);
- method public final void setBrightnessIncreaseDurationMillis(java.math.BigInteger);
method public final void setBrightnessMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+ method public final void setScreenBrightnessRampDecrease(java.math.BigDecimal);
+ method public final void setScreenBrightnessRampIncrease(java.math.BigDecimal);
}
public class HighBrightnessMode {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 25e8475..922f69c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -40,6 +40,7 @@
import android.app.admin.PolicyValue;
import android.app.admin.TargetUser;
import android.app.admin.UserRestrictionPolicyKey;
+import android.app.admin.flags.FlagUtils;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -65,7 +66,6 @@
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.devicepolicy.flags.FlagUtils;
import com.android.server.utils.Slogf;
import libcore.io.IoUtils;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 49af89b..8509155 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -330,6 +330,7 @@
import android.app.admin.UnsafeStateException;
import android.app.admin.UserRestrictionPolicyKey;
import android.app.admin.WifiSsidPolicy;
+import android.app.admin.flags.FlagUtils;
import android.app.backup.IBackupManager;
import android.app.compat.CompatChanges;
import android.app.role.OnRoleHoldersChangedListener;
@@ -486,14 +487,13 @@
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
-import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo;
-import com.android.server.devicepolicy.flags.FlagUtils;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import com.android.server.pm.DefaultCrossProfileIntentFilter;
import com.android.server.pm.DefaultCrossProfileIntentFiltersUtils;
import com.android.server.pm.PackageManagerLocal;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp b/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp
deleted file mode 100644
index 1a45782..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp
+++ /dev/null
@@ -1,16 +0,0 @@
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-aconfig_declarations {
- name: "device_policy_aconfig_flags",
- package: "com.android.server.devicepolicy.flags",
- srcs: [
- "flags.aconfig",
- ],
-}
-
-java_aconfig_library {
- name: "device_policy_aconfig_flags_lib",
- aconfig_declarations: "device_policy_aconfig_flags",
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig b/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
deleted file mode 100644
index 0dde496..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
+++ /dev/null
@@ -1,14 +0,0 @@
-package: "com.android.server.devicepolicy.flags"
-
-flag {
- name: "policy_engine_migration_v2_enabled"
- namespace: "enterprise"
- description: "V2 of the policy engine migrations for Android V"
- bug: "289520697"
-}
-flag {
- name: "device_policy_size_tracking_enabled"
- namespace: "enterprise"
- description: "Add feature to track the total policy size and have a max threshold."
- bug: "281543351"
-}
\ No newline at end of file
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 59f1edc..924e2f8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -161,6 +161,7 @@
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.NativeTombstoneManagerService;
import com.android.server.os.SchedulingPolicyService;
+import com.android.server.pdb.PersistentDataBlockService;
import com.android.server.people.PeopleService;
import com.android.server.permission.access.AccessCheckingService;
import com.android.server.pm.ApexManager;
@@ -337,6 +338,8 @@
"com.android.clockwork.modes.ModeManagerService";
private static final String WEAR_DISPLAY_SERVICE_CLASS =
"com.android.clockwork.display.WearDisplayService";
+ private static final String WEAR_DEBUG_SERVICE_CLASS =
+ "com.android.clockwork.debug.WearDebugService";
private static final String WEAR_TIME_SERVICE_CLASS =
"com.android.clockwork.time.WearTimeService";
private static final String WEAR_SETTINGS_SERVICE_CLASS =
@@ -2635,6 +2638,12 @@
mSystemServiceManager.startService(WEAR_DISPLAY_SERVICE_CLASS);
t.traceEnd();
+ if (Build.IS_DEBUGGABLE) {
+ t.traceBegin("StartWearDebugService");
+ mSystemServiceManager.startService(WEAR_DEBUG_SERVICE_CLASS);
+ t.traceEnd();
+ }
+
t.traceBegin("StartWearTimeService");
mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
t.traceEnd();
diff --git a/services/midi/Android.bp b/services/midi/Android.bp
index 5adcfba..a385fe3 100644
--- a/services/midi/Android.bp
+++ b/services/midi/Android.bp
@@ -18,5 +18,8 @@
name: "services.midi",
defaults: ["platform_service_defaults"],
srcs: [":services.midi-sources"],
- libs: ["services.core"],
+ libs: [
+ "services.core",
+ "aconfig_midi_flags_java_lib",
+ ],
}
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 486ddb4..2f47cc7 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -16,6 +16,8 @@
package com.android.server.midi;
+import static com.android.media.midi.flags.Flags.virtualUmp;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
@@ -1391,7 +1393,6 @@
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private void addLegacyPackageDeviceServer(ServiceInfo serviceInfo, int userId) {
- Log.d(TAG, "addLegacyPackageDeviceServer()" + userId);
XmlResourceParser parser = null;
try {
@@ -1529,7 +1530,6 @@
@RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
private void addUmpPackageDeviceServer(ServiceInfo serviceInfo, int userId) {
- Log.d(TAG, "addUmpPackageDeviceServer()" + userId);
XmlResourceParser parser = null;
try {
@@ -1551,6 +1551,12 @@
return;
}
+ if (!virtualUmp()) {
+ Log.w(TAG, "Skipping MIDI device service " + serviceInfo.packageName
+ + ": virtual UMP flag not enabled");
+ return;
+ }
+
Bundle properties = null;
int numPorts = 0;
boolean isPrivate = false;
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index f3ac7d5..12cd0f6 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -143,8 +143,8 @@
val result: Result,
val componentName: ComponentName? = ComponentName(pkgName, COMPONENT_CLASS_NAME)
) {
- constructor(pkgName: String, appType: AppType, exception: Class<out Exception>)
- : this(pkgName, appType, Result.Exception(exception))
+ constructor(pkgName: String, appType: AppType, exception: Class<out Exception>) :
+ this(pkgName, appType, Result.Exception(exception))
val expectedLabel = when (result) {
Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> TEST_LABEL
@@ -299,11 +299,9 @@
.hideAsFinal()
private fun makePkgSetting(pkgName: String, pkg: AndroidPackageInternal) =
- PackageSetting(
- pkgName, null, File("/test"),
- null, null, null, null, 0, 0, 0, 0, null, null, null, null, null,
- UUID.fromString("3f9d52b7-d7b4-406a-a1da-d9f19984c72c")
- ).apply {
+ PackageSetting(pkgName, null, File("/test"), 0, 0,
+ UUID.fromString("3f9d52b7-d7b4-406a-a1da-d9f19984c72c"))
+ .apply {
if (params.isSystem) {
this.flags = this.flags or ApplicationInfo.FLAG_SYSTEM
}
@@ -373,7 +371,7 @@
whenever(this.isCallerRecents(anyInt())) { false }
}
val mockAppsFilter: AppsFilterImpl = mockThrowOnUnmocked {
- whenever(this.shouldFilterApplication(any<PackageDataSnapshot>(), anyInt(),
+ whenever(this.shouldFilterApplication(any<PackageDataSnapshot>(), anyInt(),
any<PackageSetting>(), any<PackageSetting>(), anyInt())) { false }
whenever(this.snapshot()) { this@mockThrowOnUnmocked }
whenever(registerObserver(any())).thenCallRealMethod()
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 2889c74..aaad669 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -24,7 +24,9 @@
import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND;
import static android.content.pm.parsing.FrameworkParsingPackageUtils.parsePublicKey;
import static android.content.res.Resources.ID_NULL;
+
import static com.android.server.pm.PackageManagerService.WRITE_USER_PACKAGE_RESTRICTIONS;
+
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
@@ -41,6 +43,7 @@
import android.annotation.NonNull;
import android.app.PropertyInvalidatedCache;
+import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.SuspendDialogInfo;
@@ -871,12 +874,20 @@
.setUid(packageSetting.getAppId())
.hideAsFinal());
- ArchiveState archiveState = new ArchiveState(
- List.of(new ArchiveState.ArchiveActivityInfo("title1", Path.of("/path1"),
- Path.of("/monochromePath1")),
- new ArchiveState.ArchiveActivityInfo("title2", Path.of("/path2"),
- Path.of("/monochromePath2"))),
- "installerTitle");
+ ArchiveState archiveState =
+ new ArchiveState(
+ List.of(
+ new ArchiveState.ArchiveActivityInfo(
+ "title1",
+ new ComponentName("pkg1", "class1"),
+ Path.of("/path1"),
+ Path.of("/monochromePath1")),
+ new ArchiveState.ArchiveActivityInfo(
+ "title2",
+ new ComponentName("pkg2", "class2"),
+ Path.of("/path2"),
+ Path.of("/monochromePath2"))),
+ "installerTitle");
packageSetting.modifyUserState(UserHandle.SYSTEM.getIdentifier()).setArchiveState(
archiveState);
settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
@@ -956,20 +967,12 @@
PACKAGE_NAME,
REAL_PACKAGE_NAME,
INITIAL_CODE_PATH /*codePath*/,
- null /*legacyNativeLibraryPathString*/,
- "x86_64" /*primaryCpuAbiString*/,
- "x86" /*secondaryCpuAbiString*/,
- null /*cpuAbiOverrideString*/,
- INITIAL_VERSION_CODE,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_HAS_CODE,
ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
- 0,
- null /*usesSdkLibraries*/,
- null /*usesSdkLibrariesVersions*/,
- null /*usesStaticLibraries*/,
- null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID())
+ .setPrimaryCpuAbi("x86_64")
+ .setSecondaryCpuAbi("x86")
+ .setLongVersionCode(INITIAL_VERSION_CODE);
origPkgSetting01.setPkg(mockAndroidPackage(origPkgSetting01));
final PackageSetting testPkgSetting01 = new PackageSetting(origPkgSetting01);
verifySettingCopy(origPkgSetting01, testPkgSetting01);
@@ -978,23 +981,15 @@
@Test
public void testPackageStateCopy02() {
final PackageSetting origPkgSetting01 = new PackageSetting(
- PACKAGE_NAME /*pkgName*/,
- REAL_PACKAGE_NAME /*realPkgName*/,
+ PACKAGE_NAME,
+ REAL_PACKAGE_NAME,
INITIAL_CODE_PATH /*codePath*/,
- null /*legacyNativeLibraryPathString*/,
- "x86_64" /*primaryCpuAbiString*/,
- "x86" /*secondaryCpuAbiString*/,
- null /*cpuAbiOverrideString*/,
- INITIAL_VERSION_CODE,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_HAS_CODE,
ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
- 0,
- null /*usesSdkLibraries*/,
- null /*usesSdkLibrariesVersions*/,
- null /*usesStaticLibraries*/,
- null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID())
+ .setPrimaryCpuAbi("x86_64")
+ .setSecondaryCpuAbi("x86")
+ .setLongVersionCode(INITIAL_VERSION_CODE);
origPkgSetting01.setUserState(0, 100, 100, 1, true, false, false, false, 0, null, false,
false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}),
new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning",
@@ -1017,20 +1012,10 @@
PACKAGE_NAME /*pkgName*/,
REAL_PACKAGE_NAME /*realPkgName*/,
UPDATED_CODE_PATH /*codePath*/,
- null /*legacyNativeLibraryPathString*/,
- null /*primaryCpuAbiString*/,
- null /*secondaryCpuAbiString*/,
- null /*cpuAbiOverrideString*/,
- UPDATED_VERSION_CODE,
0 /*pkgFlags*/,
0 /*pkgPrivateFlags*/,
- 0,
- null /*usesSdkLibraries*/,
- null /*usesSdkLibrariesVersions*/,
- null /*usesStaticLibraries*/,
- null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID())
+ .setLongVersionCode(UPDATED_VERSION_CODE);
testPkgSetting01.copyPackageSetting(origPkgSetting01, true);
verifySettingCopy(origPkgSetting01, testPkgSetting01);
verifyUserStatesCopy(origPkgSetting01.readUserState(0),
@@ -1065,7 +1050,10 @@
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
@@ -1103,7 +1091,10 @@
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
@@ -1143,7 +1134,10 @@
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
fail("Expected a PackageManagerException");
} catch (PackageManagerException expected) {
}
@@ -1179,7 +1173,10 @@
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
assertThat(testPkgSetting01.getFlags(), is(ApplicationInfo.FLAG_SYSTEM));
@@ -1224,7 +1221,10 @@
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(0));
assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1269,7 +1269,10 @@
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(10064));
assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1315,7 +1318,10 @@
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(10064));
assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1358,7 +1364,10 @@
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(0));
assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1682,20 +1691,13 @@
PACKAGE_NAME,
REAL_PACKAGE_NAME,
INITIAL_CODE_PATH /*codePath*/,
- null /*legacyNativeLibraryPathString*/,
- "x86_64" /*primaryCpuAbiString*/,
- "x86" /*secondaryCpuAbiString*/,
- null /*cpuAbiOverrideString*/,
- INITIAL_VERSION_CODE,
pkgFlags,
0 /*privateFlags*/,
- sharedUserId,
- null /*usesSdkLibraries*/,
- null /*usesSdkLibrariesVersions*/,
- null /*usesStaticLibraries*/,
- null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID())
+ .setPrimaryCpuAbi("x86_64")
+ .setSecondaryCpuAbi("x86")
+ .setLongVersionCode(INITIAL_VERSION_CODE)
+ .setSharedUserAppId(sharedUserId);
}
private PackageSetting createPackageSetting(String packageName) {
@@ -1703,20 +1705,12 @@
packageName,
packageName,
INITIAL_CODE_PATH /*codePath*/,
- null /*legacyNativeLibraryPathString*/,
- "x86_64" /*primaryCpuAbiString*/,
- "x86" /*secondaryCpuAbiString*/,
- null /*cpuAbiOverrideString*/,
- INITIAL_VERSION_CODE,
0,
0 /*privateFlags*/,
- 0,
- null /*usesSdkLibraries*/,
- null /*usesSdkLibrariesVersions*/,
- null /*usesStaticLibraries*/,
- null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID())
+ .setPrimaryCpuAbi("x86_64")
+ .setSecondaryCpuAbi("x86")
+ .setLongVersionCode(INITIAL_VERSION_CODE);
}
static @NonNull List<UserInfo> createFakeUsers() {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
index 58ae740..87a297b 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.SuspendDialogInfo;
import android.content.pm.overlay.OverlayPaths;
@@ -192,8 +193,8 @@
return new SuspendParams(dialogInfo, appExtras, launcherExtras);
}
- private static PersistableBundle createPersistableBundle(String lKey, long lValue, String sKey,
- String sValue, String dKey, double dValue) {
+ private static PersistableBundle createPersistableBundle(
+ String lKey, long lValue, String sKey, String sValue, String dKey, double dValue) {
final PersistableBundle result = new PersistableBundle(3);
if (lKey != null) {
result.putLong("com.unit_test." + lKey, lValue);
@@ -320,6 +321,7 @@
assertEquals(0L, state.getLastPackageUsageTimeInMills()[i]);
}
}
+
private static void assertLastPackageUsageSet(
PackageStateUnserialized state, int reason, long value) throws Exception {
for (int i = state.getLastPackageUsageTimeInMills().length - 1; i >= 0; --i) {
@@ -330,6 +332,7 @@
}
}
}
+
@Test
public void testPackageUseReasons() throws Exception {
PackageSetting packageSetting = Mockito.mock(PackageSetting.class);
@@ -377,6 +380,7 @@
assertTrue(testState.setOverlayPaths(new OverlayPaths.Builder().build()));
assertFalse(testState.setOverlayPaths(null));
}
+
@Test
public void testSharedLibOverlayPaths() {
final PackageUserStateImpl testState = new PackageUserStateImpl();
@@ -401,8 +405,12 @@
@Test
public void archiveState() {
PackageUserStateImpl packageUserState = new PackageUserStateImpl();
- ArchiveState.ArchiveActivityInfo archiveActivityInfo = new ArchiveState.ArchiveActivityInfo(
- "appTitle", Path.of("/path1"), Path.of("/path2"));
+ ArchiveState.ArchiveActivityInfo archiveActivityInfo =
+ new ArchiveState.ArchiveActivityInfo(
+ "appTitle",
+ new ComponentName("pkg", "class"),
+ Path.of("/path1"),
+ Path.of("/path2"));
ArchiveState archiveState = new ArchiveState(List.of(archiveActivityInfo),
"installerTitle");
packageUserState.setArchiveState(archiveState);
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index 55fde00..e71ea26 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -28,6 +28,7 @@
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.MANAGE_USB" />
<!-- Permissions needed for DisplayTransformManagerTest -->
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index 4b124ca..8cc3408 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -100,6 +100,14 @@
}
@Test
+ public void testPowerThrottlingMapId() {
+ Layout configLayout = mDeviceStateToLayoutMap.get(5);
+
+ assertEquals("concurrent1", configLayout.getAt(0).getPowerThrottlingMapId());
+ assertEquals("concurrent2", configLayout.getAt(1).getPowerThrottlingMapId());
+ }
+
+ @Test
public void testRearDisplayLayout() {
Layout configLayout = mDeviceStateToLayoutMap.get(2);
@@ -133,13 +141,15 @@
mDisplayIdProducerMock, Layout.Display.POSITION_FRONT,
/* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness1",
/* refreshRateZoneId= */ "zone1",
- /* refreshRateThermalThrottlingMapId= */ "rr1");
+ /* refreshRateThermalThrottlingMapId= */ "rr1",
+ /* powerThrottlingMapId= */ "power1");
testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L),
/* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1",
mDisplayIdProducerMock, Layout.Display.POSITION_REAR,
/* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness2",
/* refreshRateZoneId= */ "zone2",
- /* refreshRateThermalThrottlingMapId= */ "rr2");
+ /* refreshRateThermalThrottlingMapId= */ "rr2",
+ /* powerThrottlingMapId= */ "power2");
testLayout.postProcessLocked();
assertEquals(testLayout, configLayout);
@@ -200,7 +210,8 @@
mDisplayIdProducerMock, Layout.Display.POSITION_FRONT,
DisplayAddress.fromPhysicalDisplayId(123L),
/* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
- /* refreshRateThermalThrottlingMapId= */ null));
+ /* refreshRateThermalThrottlingMapId= */ null,
+ /* powerThrottlingMapId= */ null));
}
@Test
@@ -215,7 +226,8 @@
mDisplayIdProducerMock, Layout.Display.POSITION_FRONT,
DisplayAddress.fromPhysicalDisplayId(987L),
/* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
- /* refreshRateThermalThrottlingMapId= */ null));
+ /* refreshRateThermalThrottlingMapId= */ null,
+ /* powerThrottlingMapId= */ null));
}
@Test
@@ -271,7 +283,8 @@
enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
leadDisplayAddress, /* brightnessThrottlingMapId= */ null,
/* refreshRateZoneId= */ null,
- /* refreshRateThermalThrottlingMapId= */ null);
+ /* refreshRateThermalThrottlingMapId= */ null,
+ /* powerThrottlingMapId= */ null);
}
private void setupDeviceStateToLayoutMap() throws IOException {
@@ -327,7 +340,6 @@
+ "<brightnessThrottlingMapId>concurrent2</brightnessThrottlingMapId>\n"
+ "</display>\n"
+ "</layout>\n"
-
+ "<layout>\n"
+ "<state>3</state> \n"
+ "<display enabled=\"true\" defaultDisplay=\"true\" "
@@ -338,7 +350,6 @@
+ "<address>678</address>\n"
+ "</display>\n"
+ "</layout>\n"
-
+ "<layout>\n"
+ "<state>4</state> \n"
+ "<display enabled=\"true\" defaultDisplay=\"true\" >\n"
@@ -352,6 +363,20 @@
+ "</display>\n"
+ "</layout>\n"
+ "<layout>\n"
+ + "<state>5</state> \n"
+ + "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+ + "<address>345</address>\n"
+ + "<position>front</position>\n"
+ + "<powerThrottlingMapId>concurrent1</powerThrottlingMapId>\n"
+ + "</display>\n"
+ + "<display enabled=\"true\">\n"
+ + "<address>678</address>\n"
+ + "<position>rear</position>\n"
+ + "<powerThrottlingMapId>concurrent2</powerThrottlingMapId>\n"
+ + "</display>\n"
+ + "</layout>\n"
+
+ + "<layout>\n"
+ "<state>99</state> \n"
+ "<display enabled=\"true\" defaultDisplay=\"true\" "
+ "refreshRateZoneId=\"zone1\">\n"
@@ -361,6 +386,7 @@
+ "<refreshRateThermalThrottlingMapId>"
+ "rr1"
+ "</refreshRateThermalThrottlingMapId>"
+ + "<powerThrottlingMapId>power1</powerThrottlingMapId>\n"
+ "</display>\n"
+ "<display enabled=\"false\" displayGroup=\"group1\" "
+ "refreshRateZoneId=\"zone2\">\n"
@@ -370,6 +396,7 @@
+ "<refreshRateThermalThrottlingMapId>"
+ "rr2"
+ "</refreshRateThermalThrottlingMapId>"
+ + "<powerThrottlingMapId>power2</powerThrottlingMapId>\n"
+ "</display>\n"
+ "</layout>\n"
+ "</layouts>\n";
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index c12aedb..a400f12 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -90,7 +90,9 @@
.append("\n isSlowChange:")
.append(displayBrightnessState.isSlowChange())
.append("\n maxBrightness:")
- .append(displayBrightnessState.getMaxBrightness());
+ .append(displayBrightnessState.getMaxBrightness())
+ .append("\n customAnimationRate:")
+ .append(displayBrightnessState.getCustomAnimationRate());
return sb.toString();
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index c37d21a..179a9d5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -570,9 +570,9 @@
assertNotNull(data);
assertEquals(2, data.mMaxBrightnessLimits.size());
assertEquals(13000, data.mBrightnessDecreaseDebounceMillis);
- assertEquals(10000, data.mBrightnessDecreaseDurationMillis);
+ assertEquals(0.1f, data.mScreenBrightnessRampDecrease, SMALL_DELTA);
assertEquals(1000, data.mBrightnessIncreaseDebounceMillis);
- assertEquals(11000, data.mBrightnessIncreaseDurationMillis);
+ assertEquals(0.11f, data.mScreenBrightnessRampIncrease, SMALL_DELTA);
assertEquals(0.3f, data.mMaxBrightnessLimits.get(500f), SMALL_DELTA);
assertEquals(0.6f, data.mMaxBrightnessLimits.get(1200f), SMALL_DELTA);
@@ -841,9 +841,9 @@
+ " </point>\n"
+ " </brightnessMap>\n"
+ " <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis>\n"
- + " <brightnessIncreaseDurationMillis>11000</brightnessIncreaseDurationMillis>\n"
+ + " <screenBrightnessRampIncrease>0.11</screenBrightnessRampIncrease>\n"
+ " <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>\n"
- + " <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>\n"
+ + " <screenBrightnessRampDecrease>0.1</screenBrightnessRampDecrease>\n"
+ "</hdrBrightnessConfig>";
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index d021f1d..16d72e4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -117,7 +117,6 @@
import com.android.server.display.notifications.DisplayNotificationManager;
import com.android.server.input.InputManagerInternal;
import com.android.server.lights.LightsManager;
-import com.android.server.pm.UserManagerInternal;
import com.android.server.sensors.SensorManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -312,7 +311,6 @@
@Mock SensorManager mSensorManager;
@Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
@Mock PackageManagerInternal mMockPackageManagerInternal;
- @Mock UserManagerInternal mMockUserManagerInternal;
@Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
@@ -336,8 +334,6 @@
VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal);
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
- LocalServices.removeServiceForTest(UserManagerInternal.class);
- LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
// TODO: b/287945043
mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mResources = Mockito.spy(mContext.getResources());
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 8b54d6d2..47521d1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -74,6 +74,7 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.color.ColorDisplayService;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -1314,6 +1315,54 @@
}
@Test
+ public void testRampRateForClampersControllerApplied() {
+ float transitionRate = 1.5f;
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+ when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+ invocation -> DisplayBrightnessState.builder()
+ .setIsSlowChange(invocation.getArgument(2))
+ .setBrightness(invocation.getArgument(1))
+ .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+ .setCustomAnimationRate(transitionRate).build());
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
+ eq(transitionRate), anyBoolean());
+ }
+
+ @Test
+ public void testRampRateForClampersControllerNotApplied_ifDoze() {
+ float transitionRate = 1.5f;
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ dpr.dozeScreenState = Display.STATE_UNKNOWN;
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+ when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+ invocation -> DisplayBrightnessState.builder()
+ .setIsSlowChange(invocation.getArgument(2))
+ .setBrightness(invocation.getArgument(1))
+ .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+ .setCustomAnimationRate(transitionRate).build());
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), anyBoolean());
+ verify(mHolder.animator, never()).animateTo(anyFloat(), anyFloat(),
+ eq(transitionRate), anyBoolean());
+ }
+
+ @Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
public void testRampMaxTimeInteractiveThenIdle() {
// Send a display power request
@@ -1637,13 +1686,20 @@
mock(ScreenOffBrightnessSensorController.class);
final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
final HdrClamper hdrClamper = mock(HdrClamper.class);
+ BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+ when(clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+ invocation -> DisplayBrightnessState.builder()
+ .setIsSlowChange(invocation.getArgument(2))
+ .setBrightness(invocation.getArgument(1))
+ .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+ .setCustomAnimationRate(-1).build());
TestInjector injector = spy(new TestInjector(displayPowerState, animator,
automaticBrightnessController, wakelockController, brightnessMappingStrategy,
hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
- flags));
+ clamperController, flags));
final LogicalDisplay display = mock(LogicalDisplay.class);
final DisplayDevice device = mock(DisplayDevice.class);
@@ -1662,8 +1718,8 @@
return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
animator, automaticBrightnessController, wakelockController,
- screenOffBrightnessSensorController, hbmController, hdrClamper, hbmMetadata,
- brightnessMappingStrategy, injector, config);
+ screenOffBrightnessSensorController, hbmController, hdrClamper, clamperController,
+ hbmMetadata, brightnessMappingStrategy, injector, config);
}
/**
@@ -1682,6 +1738,7 @@
public final HighBrightnessModeController hbmController;
public final HdrClamper hdrClamper;
+ public final BrightnessClamperController clamperController;
public final HighBrightnessModeMetadata hbmMetadata;
public final BrightnessMappingStrategy brightnessMappingStrategy;
public final DisplayPowerController2.Injector injector;
@@ -1695,6 +1752,7 @@
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
HighBrightnessModeController hbmController,
HdrClamper hdrClamper,
+ BrightnessClamperController clamperController,
HighBrightnessModeMetadata hbmMetadata,
BrightnessMappingStrategy brightnessMappingStrategy,
DisplayPowerController2.Injector injector,
@@ -1709,6 +1767,7 @@
this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
this.hbmController = hbmController;
this.hdrClamper = hdrClamper;
+ this.clamperController = clamperController;
this.hbmMetadata = hbmMetadata;
this.brightnessMappingStrategy = brightnessMappingStrategy;
this.injector = injector;
@@ -1728,6 +1787,8 @@
private final HdrClamper mHdrClamper;
+ private final BrightnessClamperController mClamperController;
+
private final DisplayManagerFlags mFlags;
TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
@@ -1738,6 +1799,7 @@
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
HighBrightnessModeController highBrightnessModeController,
HdrClamper hdrClamper,
+ BrightnessClamperController clamperController,
DisplayManagerFlags flags) {
mDisplayPowerState = dps;
mAnimator = animator;
@@ -1748,6 +1810,7 @@
mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
mHighBrightnessModeController = highBrightnessModeController;
mHdrClamper = hdrClamper;
+ mClamperController = clamperController;
mFlags = flags;
}
@@ -1864,6 +1927,14 @@
}
@Override
+ BrightnessClamperController getBrightnessClamperController(Handler handler,
+ BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+ BrightnessClamperController.DisplayDeviceData data, Context context,
+ DisplayManagerFlags flags) {
+ return mClamperController;
+ }
+
+ @Override
DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
SensorManager sensorManager, Resources resources) {
return mDisplayWhiteBalanceControllerMock;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 9ac0062..32e2871 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -33,6 +33,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -699,7 +700,7 @@
// Turn off.
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_OFF, 0,
- 0);
+ 0, null);
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
assertThat(mListener.changedDisplays.size()).isEqualTo(1);
mListener.changedDisplays.clear();
@@ -1003,7 +1004,7 @@
// Turn on / initialize
assumeTrue(displayDevice.getDisplayDeviceConfig().hasSdrToHdrRatioSpline());
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
- 0);
+ 0, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
@@ -1012,7 +1013,7 @@
// HDR time!
Runnable goHdrRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 1f,
- 0);
+ 0, null);
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
// Display state didn't change, no listeners should have happened
assertThat(mListener.changedDisplays.size()).isEqualTo(0);
@@ -1043,7 +1044,7 @@
// Turn on / initialize
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
- 0);
+ 0, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
@@ -1070,7 +1071,7 @@
// Turn on / initialize
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
- 0);
+ 0, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
@@ -1095,7 +1096,7 @@
// Turn on / initialize
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
- 0);
+ 0, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
@@ -1118,7 +1119,7 @@
// Turn on / initialize
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
- 0);
+ 0, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
@@ -1145,9 +1146,9 @@
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(
supportedState, 0, 0, mDisplayOffloadSession);
changeStateRunnable.run();
-
- verify(mDisplayOffloader).startOffload();
}
+
+ verify(mDisplayOffloader, times(mDisplayOffloadSupportedStates.size())).startOffload();
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 065dd1f..8b13018 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -720,13 +720,15 @@
mIdProducer, POSITION_UNKNOWN,
/* leadDisplayAddress= */ null,
/* brightnessThrottlingMapId= */ "concurrent",
- /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+ /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null,
+ /* powerThrottlingMapId= */ "concurrent");
layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
/* isDefault= */ false, /* isEnabled= */ true, /* displayGroup= */ null,
mIdProducer, POSITION_UNKNOWN,
/* leadDisplayAddress= */ null,
/* brightnessThrottlingMapId= */ "concurrent",
- /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+ /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null,
+ /* powerThrottlingMapId= */ "concurrent");
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
layout = new Layout();
@@ -927,7 +929,7 @@
/* isDefault= */ false, /* isEnabled= */ true, /* displayGroupName= */ null,
mIdProducer, POSITION_REAR, /* leadDisplayAddress= */ null,
/* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
- /* refreshRateThermalThrottlingMapId= */null);
+ /* refreshRateThermalThrottlingMapId= */null, /* powerThrottlingMapId= */null);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
@@ -986,7 +988,7 @@
layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer,
Layout.Display.POSITION_UNKNOWN, /* leadDisplayAddress= */ null,
/* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
- /* refreshRateThermalThrottlingMapId= */ null);
+ /* refreshRateThermalThrottlingMapId= */ null, /* powerThrottlingMapId= */ null);
}
private void advanceTime(long timeMs) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 22d2622..ff2b1f4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -35,6 +35,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.testutils.TestHandler;
import org.junit.Before;
@@ -63,12 +64,13 @@
@Mock
private BrightnessClamper<BrightnessClamperController.DisplayDeviceData> mMockClamper;
@Mock
+ private DisplayManagerFlags mFlags;
+ @Mock
private BrightnessModifier mMockModifier;
@Mock
private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
@Mock
private DeviceConfig.Properties mMockProperties;
-
private BrightnessClamperController mClamperController;
private TestInjector mTestInjector;
@@ -135,8 +137,10 @@
float initialBrightness = 0.8f;
boolean initialSlowChange = true;
float clampedBrightness = 0.6f;
+ float customAnimationRate = 0.01f;
when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+ when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
when(mMockClamper.isActive()).thenReturn(false);
mTestInjector.mCapturedChangeListener.onChanged();
mTestHandler.flush();
@@ -148,6 +152,7 @@
assertEquals(PowerManager.BRIGHTNESS_MAX, state.getMaxBrightness(), FLOAT_TOLERANCE);
assertEquals(0,
state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+ assertEquals(-1, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
assertEquals(initialSlowChange, state.isSlowChange());
}
@@ -156,8 +161,10 @@
float initialBrightness = 0.8f;
boolean initialSlowChange = true;
float clampedBrightness = 0.6f;
+ float customAnimationRate = 0.01f;
when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+ when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
when(mMockClamper.isActive()).thenReturn(true);
mTestInjector.mCapturedChangeListener.onChanged();
mTestHandler.flush();
@@ -169,6 +176,7 @@
assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
assertEquals(BrightnessReason.MODIFIER_THROTTLED,
state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+ assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
assertFalse(state.isSlowChange());
}
@@ -177,8 +185,10 @@
float initialBrightness = 0.6f;
boolean initialSlowChange = true;
float clampedBrightness = 0.8f;
+ float customAnimationRate = 0.01f;
when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+ when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
when(mMockClamper.isActive()).thenReturn(true);
mTestInjector.mCapturedChangeListener.onChanged();
mTestHandler.flush();
@@ -190,6 +200,7 @@
assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
assertEquals(BrightnessReason.MODIFIER_THROTTLED,
state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+ assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
assertFalse(state.isSlowChange());
}
@@ -198,8 +209,10 @@
float initialBrightness = 0.8f;
boolean initialSlowChange = true;
float clampedBrightness = 0.6f;
+ float customAnimationRate = 0.01f;
when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+ when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
when(mMockClamper.isActive()).thenReturn(true);
mTestInjector.mCapturedChangeListener.onChanged();
mTestHandler.flush();
@@ -214,12 +227,13 @@
assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
assertEquals(BrightnessReason.MODIFIER_THROTTLED,
state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+ assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
assertEquals(initialSlowChange, state.isSlowChange());
}
private BrightnessClamperController createBrightnessClamperController() {
return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener,
- mMockDisplayDeviceData, mMockContext);
+ mMockDisplayDeviceData, mMockContext, mFlags);
}
private class TestInjector extends BrightnessClamperController.Injector {
@@ -247,7 +261,8 @@
List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>> getClampers(
Handler handler,
BrightnessClamperController.ClamperChangeListener clamperChangeListener,
- BrightnessClamperController.DisplayDeviceData data) {
+ BrightnessClamperController.DisplayDeviceData data,
+ DisplayManagerFlags flags) {
mCapturedChangeListener = clamperChangeListener;
return mClampers;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
new file mode 100644
index 0000000..b3f33ad
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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.display.brightness.clamper;
+
+import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.Temperature;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.testutils.FakeDeviceConfigInterface;
+import com.android.server.testutils.TestHandler;
+
+import junitparams.JUnitParamsRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@RunWith(JUnitParamsRunner.class)
+public class BrightnessPowerClamperTest {
+ private static final String TAG = "BrightnessPowerClamperTest";
+ private static final float FLOAT_TOLERANCE = 0.001f;
+
+ private static final String DISPLAY_ID = "displayId";
+ @Mock
+ private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
+ private TestPmicMonitor mPmicMonitor;
+ private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
+ new FakeDeviceConfigInterface();
+ private final TestHandler mTestHandler = new TestHandler(null);
+ private BrightnessPowerClamper mClamper;
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mClamper = new BrightnessPowerClamper(new TestInjector(), mTestHandler,
+ mMockClamperChangeListener, new TestPowerData());
+ mTestHandler.flush();
+ }
+
+ @Test
+ public void testTypeIsPower() {
+ assertEquals(BrightnessClamper.Type.POWER, mClamper.getType());
+ }
+
+ @Test
+ public void testNoThrottlingData() {
+ assertFalse(mClamper.isActive());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testPowerThrottlingNoOngoingAnimation() throws RemoteException {
+ mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE);
+ mTestHandler.flush();
+ assertFalse(mClamper.isActive());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ // update a new device config for power-throttling.
+ mClamper.onDisplayChanged(new TestPowerData(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f))));
+
+ mPmicMonitor.setAvgPowerConsumed(200f);
+ float expectedBrightness = 0.5f;
+ expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX;
+
+ mTestHandler.flush();
+ // Assume current brightness as max, as there is no throttling.
+ assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ mPmicMonitor.setThermalStatus(Temperature.THROTTLING_CRITICAL);
+ // update a new device config for power-throttling.
+ mClamper.onDisplayChanged(new TestPowerData(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 50f))));
+
+ mPmicMonitor.setAvgPowerConsumed(100f);
+ expectedBrightness = 0.5f * PowerManager.BRIGHTNESS_MAX;
+ mTestHandler.flush();
+ assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testPowerThrottlingWithOngoingAnimation() throws RemoteException {
+ mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE);
+ mTestHandler.flush();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ // update a new device config for power-throttling.
+ mClamper.onDisplayChanged(new TestPowerData(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f))));
+
+ mPmicMonitor.setAvgPowerConsumed(200f);
+ float expectedBrightness = 0.5f;
+ expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX;
+
+ mTestHandler.flush();
+ // Assume current brightness as max, as there is no throttling.
+ assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ mPmicMonitor.setThermalStatus(Temperature.THROTTLING_CRITICAL);
+ // update a new device config for power-throttling.
+ mClamper.onDisplayChanged(new TestPowerData(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 50f))));
+
+ mPmicMonitor.setAvgPowerConsumed(100f);
+ expectedBrightness = 0.5f * PowerManager.BRIGHTNESS_MAX;
+ mTestHandler.flush();
+ assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testPowerThrottlingRemoveBrightnessCap() throws RemoteException {
+ mPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
+ mTestHandler.flush();
+ assertFalse(mClamper.isActive());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ // update a new device config for power-throttling.
+ mClamper.onDisplayChanged(new TestPowerData(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f))));
+
+ mPmicMonitor.setAvgPowerConsumed(200f);
+ float expectedBrightness = 0.5f;
+ expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX;
+
+ mTestHandler.flush();
+
+ assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ mPmicMonitor.setThermalStatus(Temperature.THROTTLING_NONE);
+
+ mPmicMonitor.setAvgPowerConsumed(100f);
+ // No cap applied for Temperature.THROTTLING_NONE
+ expectedBrightness = PowerManager.BRIGHTNESS_MAX;
+ mTestHandler.flush();
+
+ // clamper should not be active anymore.
+ assertFalse(mClamper.isActive());
+ // Assume current brightness as max, as there is no throttling.
+ assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+
+ private static class TestPmicMonitor extends PmicMonitor {
+ private Temperature mCurrentTemperature;
+ private final PowerChangeListener mListener;
+ TestPmicMonitor(PowerChangeListener listener, int pollingTime) {
+ super(listener, pollingTime);
+ mListener = listener;
+ }
+ public void setAvgPowerConsumed(float power) {
+ int status = mCurrentTemperature.getStatus();
+ mListener.onChanged(power, status);
+ }
+ public void setThermalStatus(@Temperature.ThrottlingStatus int status) {
+ mCurrentTemperature = new Temperature(100, Temperature.TYPE_SKIN, "test_temp", status);
+ }
+ }
+
+ private class TestInjector extends BrightnessPowerClamper.Injector {
+ @Override
+ TestPmicMonitor getPmicMonitor(PowerChangeListener listener,
+ int pollingTime) {
+ mPmicMonitor = new TestPmicMonitor(listener, pollingTime);
+ return mPmicMonitor;
+ }
+
+ @Override
+ DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+ return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
+ }
+ }
+
+ private static class TestPowerData implements BrightnessPowerClamper.PowerData {
+
+ private final String mUniqueDisplayId;
+ private final String mDataId;
+ private final PowerThrottlingData mData;
+ private final PowerThrottlingConfigData mConfigData;
+
+ private TestPowerData() {
+ this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null);
+ }
+
+ private TestPowerData(List<ThrottlingLevel> data) {
+ this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data);
+ }
+
+ private TestPowerData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) {
+ mUniqueDisplayId = uniqueDisplayId;
+ mDataId = dataId;
+ mData = PowerThrottlingData.create(data);
+ mConfigData = new PowerThrottlingConfigData(0.1f, 10);
+ }
+
+ @NonNull
+ @Override
+ public String getUniqueDisplayId() {
+ return mUniqueDisplayId;
+ }
+
+ @NonNull
+ @Override
+ public String getPowerThrottlingDataId() {
+ return mDataId;
+ }
+
+ @Nullable
+ @Override
+ public PowerThrottlingData getPowerThrottlingData() {
+ return mData;
+ }
+
+ @Nullable
+ @Override
+ public PowerThrottlingConfigData getPowerThrottlingConfigData() {
+ return mConfigData;
+ }
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index ee187ba..8d8274c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -56,9 +56,9 @@
private static final HdrBrightnessData TEST_HDR_DATA = new HdrBrightnessData(
Map.of(500f, 0.6f),
/* brightnessIncreaseDebounceMillis= */ 1000,
- /* brightnessIncreaseDurationMillis= */ 2000,
+ /* screenBrightnessRampIncrease= */ 0.02f,
/* brightnessDecreaseDebounceMillis= */ 3000,
- /* brightnessDecreaseDurationMillis= */4000
+ /* screenBrightnessRampDecrease= */0.04f
);
private static final int WIDTH = 600;
@@ -152,8 +152,7 @@
mClock.fastForward(3000);
mTestHandler.timeAdvance();
assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
- // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4
- assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+ assertEquals(0.04, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
}
@Test
@@ -181,8 +180,7 @@
mClock.fastForward(1000);
mTestHandler.timeAdvance();
assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
- // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 2
- assertEquals(0.047137f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+ assertEquals(0.02f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
}
@Test
@@ -209,8 +207,7 @@
mClock.fastForward(3000);
mTestHandler.timeAdvance();
assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
- // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4
- assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+ assertEquals(0.04f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
}
// MsgInfo.sendTime is calculated first by adding SystemClock.uptimeMillis()
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
index c7c09b5..ec27f9d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
@@ -44,12 +44,12 @@
import android.test.mock.MockContentResolver;
import android.view.Display;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.LocalServices;
+import com.android.internal.util.test.LocalServiceKeeperRule;
import com.android.server.SystemService;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
@@ -57,6 +57,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
@@ -90,9 +91,13 @@
ColorDisplayManager.COLOR_MODE_BOOSTED,
};
+ @Rule
+ public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
+
@Before
public void setUp() {
- mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+ mContext = Mockito.spy(new ContextWrapper(
+ InstrumentationRegistry.getInstrumentation().getTargetContext()));
doReturn(mContext).when(mContext).getApplicationContext();
final Resources res = Mockito.spy(mContext.getResources());
@@ -112,43 +117,36 @@
doReturn(am).when(mContext).getSystemService(Context.ALARM_SERVICE);
mTwilightManager = new MockTwilightManager();
- LocalServices.addService(TwilightManager.class, mTwilightManager);
+ mLocalServiceKeeperRule.overrideLocalService(TwilightManager.class, mTwilightManager);
mDisplayTransformManager = Mockito.mock(DisplayTransformManager.class);
doReturn(true).when(mDisplayTransformManager).needsLinearColorMatrix();
- LocalServices.addService(DisplayTransformManager.class, mDisplayTransformManager);
+ mLocalServiceKeeperRule.overrideLocalService(
+ DisplayTransformManager.class, mDisplayTransformManager);
mDisplayManagerInternal = Mockito.mock(DisplayManagerInternal.class);
- LocalServices.removeServiceForTest(DisplayManagerInternal.class);
- LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal);
+ mLocalServiceKeeperRule.overrideLocalService(
+ DisplayManagerInternal.class, mDisplayManagerInternal);
mCds = new ColorDisplayService(mContext);
mBinderService = mCds.new BinderService();
- LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
+ mLocalServiceKeeperRule.overrideLocalService(
+ ColorDisplayService.ColorDisplayServiceInternal.class,
mCds.new ColorDisplayServiceInternal());
}
@After
public void tearDown() {
- /*
- * Wait for internal {@link Handler} to finish processing pending messages, so that test
- * code can safelyremove {@link DisplayTransformManager} mock from {@link LocalServices}.
- */
- mCds.mHandler.runWithScissors(() -> { /* nop */ }, /* timeout */ 1000);
+ // synchronously cancel all animations
+ mCds.mHandler.runWithScissors(() -> mCds.cancelAllAnimators(), /* timeout */ 1000);
mCds = null;
- LocalServices.removeServiceForTest(TwilightManager.class);
mTwilightManager = null;
- LocalServices.removeServiceForTest(DisplayTransformManager.class);
-
mUserId = UserHandle.USER_NULL;
mContext = null;
FakeSettingsProvider.clearSettingsProvider();
-
- LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
- LocalServices.removeServiceForTest(DisplayManagerInternal.class);
}
@Test
@@ -1249,10 +1247,10 @@
private void startService() {
Secure.putIntForUser(mContext.getContentResolver(), Secure.USER_SETUP_COMPLETE, 1, mUserId);
- InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- mCds.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
- mCds.onUserChanged(mUserId);
- });
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> mCds.onBootPhase(SystemService.PHASE_BOOT_COMPLETED));
+ // onUserChanged cancels running animations, and should be called in handler thread
+ mCds.mHandler.runWithScissors(() -> mCds.onUserChanged(mUserId), 1000);
}
/**
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index c4f72b3..6a95d5c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -102,6 +102,9 @@
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.testutils.FakeDeviceConfigInterface;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -121,26 +124,28 @@
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
-
@SmallTest
@RunWith(JUnitParamsRunner.class)
public class DisplayModeDirectorTest {
public static Collection<Object[]> getAppRequestedSizeTestCases() {
var appRequestedSizeTestCases = Arrays.asList(new Object[][] {
- {DEFAULT_MODE_75.getModeId(), Float.POSITIVE_INFINITY,
- DEFAULT_MODE_75.getRefreshRate(), Map.of()},
- {APP_MODE_HIGH_90.getModeId(), Float.POSITIVE_INFINITY,
- APP_MODE_HIGH_90.getRefreshRate(),
- Map.of(
+ {/*expectedBaseModeId*/ DEFAULT_MODE_75.getModeId(),
+ /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY,
+ /*expectedAppRequestedRefreshRate*/ DEFAULT_MODE_75.getRefreshRate(),
+ /*votesWithPriorities*/ Map.of()},
+ {/*expectedBaseModeId*/ APP_MODE_HIGH_90.getModeId(),
+ /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY,
+ /*expectedAppRequestedRefreshRate*/ APP_MODE_HIGH_90.getRefreshRate(),
+ /*votesWithPriorities*/ Map.of(
Vote.PRIORITY_APP_REQUEST_SIZE,
Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
APP_MODE_HIGH_90.getPhysicalHeight()),
Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()))},
- {LIMIT_MODE_70.getModeId(), Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
- Map.of(
+ {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(),
+ /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY,
+ /*expectedAppRequestedRefreshRate*/ Float.POSITIVE_INFINITY,
+ /*votesWithPriorities*/ Map.of(
Vote.PRIORITY_APP_REQUEST_SIZE,
Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
APP_MODE_HIGH_90.getPhysicalHeight()),
@@ -149,9 +154,10 @@
Vote.PRIORITY_LOW_POWER_MODE,
Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(),
LIMIT_MODE_70.getPhysicalHeight()))},
- {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(),
- LIMIT_MODE_70.getRefreshRate(),
- Map.of(
+ {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(),
+ /*expectedPhysicalRefreshRate*/ LIMIT_MODE_70.getRefreshRate(),
+ /*expectedAppRequestedRefreshRate*/ LIMIT_MODE_70.getRefreshRate(),
+ /*votesWithPriorities*/ Map.of(
Vote.PRIORITY_APP_REQUEST_SIZE,
Vote.forSize(APP_MODE_65.getPhysicalWidth(),
APP_MODE_65.getPhysicalHeight()),
@@ -160,9 +166,10 @@
Vote.PRIORITY_LOW_POWER_MODE,
Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(),
LIMIT_MODE_70.getPhysicalHeight()))},
- {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(),
- LIMIT_MODE_70.getRefreshRate(),
- Map.of(
+ {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(),
+ /*expectedPhysicalRefreshRate*/ LIMIT_MODE_70.getRefreshRate(),
+ /*expectedAppRequestedRefreshRate*/ LIMIT_MODE_70.getRefreshRate(),
+ /*votesWithPriorities*/ Map.of(
Vote.PRIORITY_APP_REQUEST_SIZE,
Vote.forSize(APP_MODE_65.getPhysicalWidth(),
APP_MODE_65.getPhysicalHeight()),
@@ -173,10 +180,12 @@
0, 0,
LIMIT_MODE_70.getPhysicalWidth(),
LIMIT_MODE_70.getPhysicalHeight(),
- 0, Float.POSITIVE_INFINITY)), false},
- {APP_MODE_65.getModeId(), APP_MODE_65.getRefreshRate(),
- APP_MODE_65.getRefreshRate(),
- Map.of(
+ 0, Float.POSITIVE_INFINITY)),
+ /*displayResolutionRangeVotingEnabled*/ false},
+ {/*expectedBaseModeId*/ APP_MODE_65.getModeId(),
+ /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(),
+ /*expectedAppRequestedRefreshRate*/ APP_MODE_65.getRefreshRate(),
+ /*votesWithPriorities*/ Map.of(
Vote.PRIORITY_APP_REQUEST_SIZE,
Vote.forSize(APP_MODE_65.getPhysicalWidth(),
APP_MODE_65.getPhysicalHeight()),
@@ -187,7 +196,40 @@
0, 0,
LIMIT_MODE_70.getPhysicalWidth(),
LIMIT_MODE_70.getPhysicalHeight(),
- 0, Float.POSITIVE_INFINITY)), true}});
+ 0, Float.POSITIVE_INFINITY)),
+ /*displayResolutionRangeVotingEnabled*/ true},
+ {/*expectedBaseModeId*/ DEFAULT_MODE_75.getModeId(),
+ /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(),
+ /*expectedAppRequestedRefreshRate*/ APP_MODE_HIGH_90.getRefreshRate(),
+ /*votesWithPriorities*/ Map.of(
+ Vote.PRIORITY_APP_REQUEST_SIZE,
+ Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
+ APP_MODE_HIGH_90.getPhysicalHeight()),
+ Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+ Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()),
+ Vote.PRIORITY_LOW_POWER_MODE,
+ Vote.forSizeAndPhysicalRefreshRatesRange(
+ 0, 0,
+ LIMIT_MODE_70.getPhysicalWidth(),
+ LIMIT_MODE_70.getPhysicalHeight(),
+ 0, APP_MODE_65.getRefreshRate())),
+ /*displayResolutionRangeVotingEnabled*/ false},
+ {/*expectedBaseModeId*/ DEFAULT_MODE_60.getModeId(), // Resolution == APP_MODE_65
+ /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(),
+ /*expectedAppRequestedRefreshRate*/ APP_MODE_65.getRefreshRate(),
+ /*votesWithPriorities*/ Map.of(
+ Vote.PRIORITY_APP_REQUEST_SIZE,
+ Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
+ APP_MODE_HIGH_90.getPhysicalHeight()),
+ Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+ Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()),
+ Vote.PRIORITY_LOW_POWER_MODE,
+ Vote.forSizeAndPhysicalRefreshRatesRange(
+ 0, 0,
+ LIMIT_MODE_70.getPhysicalWidth(),
+ LIMIT_MODE_70.getPhysicalHeight(),
+ 0, APP_MODE_65.getRefreshRate())),
+ /*displayResolutionRangeVotingEnabled*/ true}});
final var res = new ArrayList<Object[]>(appRequestedSizeTestCases.size() * 2);
@@ -218,6 +260,8 @@
private static final boolean DEBUG = false;
private static final float FLOAT_TOLERANCE = 0.01f;
+ private static final Display.Mode DEFAULT_MODE_60 = new Display.Mode(
+ /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60);
private static final Display.Mode APP_MODE_65 = new Display.Mode(
/*modeId=*/65, /*width=*/1900, /*height=*/1900, 65);
private static final Display.Mode LIMIT_MODE_70 = new Display.Mode(
@@ -227,8 +271,7 @@
private static final Display.Mode APP_MODE_HIGH_90 = new Display.Mode(
/*modeId=*/90, /*width=*/3000, /*height=*/3000, 90);
private static final Display.Mode[] TEST_MODES = new Display.Mode[] {
- new Display.Mode(
- /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60),
+ DEFAULT_MODE_60,
APP_MODE_65,
LIMIT_MODE_70,
DEFAULT_MODE_75,
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 5b51963..21e3b34 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -62,8 +62,6 @@
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE;
-import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED;
-import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED;
@@ -75,7 +73,6 @@
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WINDOW;
-import static com.android.server.alarm.AlarmManagerService.Constants.KEY_EXACT_ALARM_DENY_LIST;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_DEVICE_IDLE_FUZZ;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
@@ -85,7 +82,6 @@
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_WINDOW;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_PRIORITY_ALARM_DELAY;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_TEMPORARY_QUOTA_BUMP;
-import static com.android.server.alarm.AlarmManagerService.Constants.MAX_EXACT_ALARM_DENY_LIST_SIZE;
import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX;
import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY;
import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK;
@@ -799,47 +795,6 @@
}
@Test
- public void updatingExactAlarmDenyList() {
- ArraySet<String> denyListed = new ArraySet<>(new String[]{
- "com.example.package1",
- "com.example.package2",
- "com.example.package3",
- });
- setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST,
- "com.example.package1,com.example.package2,com.example.package3");
- assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST);
-
-
- denyListed = new ArraySet<>(new String[]{
- "com.example.package1",
- "com.example.package4",
- });
- setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST,
- "com.example.package1,com.example.package4");
- assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST);
-
- setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "");
- assertEquals(0, mService.mConstants.EXACT_ALARM_DENY_LIST.size());
- }
-
- @Test
- public void exactAlarmDenyListMaxSize() {
- final ArraySet<String> expectedSet = new ArraySet<>();
- final StringBuilder sb = new StringBuilder("package1");
- expectedSet.add("package1");
- for (int i = 2; i <= 2 * MAX_EXACT_ALARM_DENY_LIST_SIZE; i++) {
- sb.append(",package");
- sb.append(i);
- if (i <= MAX_EXACT_ALARM_DENY_LIST_SIZE) {
- expectedSet.add("package" + i);
- }
- }
- assertEquals(MAX_EXACT_ALARM_DENY_LIST_SIZE, expectedSet.size());
- setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, sb.toString());
- assertEquals(expectedSet, mService.mConstants.EXACT_ALARM_DENY_LIST);
- }
-
- @Test
public void positiveWhileIdleQuotas() {
setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, -3);
assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA);
@@ -2212,50 +2167,30 @@
}
@Test
- public void hasScheduleExactAlarmBinderCallNotDenyListedPreT() throws RemoteException {
+ public void hasScheduleExactAlarmBinderCallPreT() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT);
+ mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);
assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
- mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
- mockScheduleExactAlarmStatePreT(true, false, MODE_IGNORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_IGNORED);
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
}
@Test
- public void hasScheduleExactAlarmBinderCallDenyListedPreT() throws RemoteException {
- mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
- mockScheduleExactAlarmStatePreT(true, true, MODE_ERRORED);
- assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
- mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
- assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
- mockScheduleExactAlarmStatePreT(true, true, MODE_IGNORED);
- assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
- mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED);
- assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
- }
-
- @Test
public void hasScheduleExactAlarmBinderCallNotDeclaredPreT() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmStatePreT(false, false, MODE_DEFAULT);
+ mockScheduleExactAlarmStatePreT(false, MODE_DEFAULT);
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
- mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED);
- assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
- mockScheduleExactAlarmStatePreT(false, true, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(false, MODE_ALLOWED);
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
}
@@ -2281,41 +2216,33 @@
mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
// No permission, no exemption.
- mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
- assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
-
- // No permission, no exemption.
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
// Policy permission only, no exemption.
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
mockUseExactAlarmState(true);
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
mockUseExactAlarmState(false);
// User permission only, no exemption.
- mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT);
- assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
-
- // User permission only, no exemption.
- mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
// No permission, exemption.
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(true);
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
// No permission, exemption.
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(false);
doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID));
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
// Both permissions and exemption.
- mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
mockUseExactAlarmState(true);
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
}
@@ -2514,17 +2441,12 @@
assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
}
- private void mockScheduleExactAlarmStatePreT(boolean declared, boolean denyList, int mode) {
+ private void mockScheduleExactAlarmStatePreT(boolean declared, int mode) {
String[] requesters = declared ? new String[]{TEST_CALLING_PACKAGE} : EmptyArray.STRING;
when(mPermissionManagerInternal.getAppOpPermissionPackages(SCHEDULE_EXACT_ALARM))
.thenReturn(requesters);
mService.refreshExactAlarmCandidates();
- if (denyList) {
- setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, TEST_CALLING_PACKAGE);
- } else {
- setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "");
- }
when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID,
TEST_CALLING_PACKAGE)).thenReturn(mode);
}
@@ -2556,7 +2478,7 @@
public void alarmClockBinderCallWithoutPermission() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2630,7 +2552,7 @@
public void exactBinderCallWithAllowlist() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
// If permission is denied, only then allowlist will be checked.
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2650,7 +2572,7 @@
public void exactAllowWhileIdleBinderCallWithSEAPermission() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
@@ -2676,7 +2598,7 @@
mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
mockUseExactAlarmState(true);
- mockScheduleExactAlarmStatePreT(false, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(false, MODE_ERRORED);
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
@@ -2700,7 +2622,7 @@
public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
// If permission is denied, only then allowlist will be checked.
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2726,7 +2648,7 @@
public void exactBinderCallsWithoutPermissionWithoutAllowlist() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false);
final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2836,7 +2758,7 @@
public void binderCallWithUserAllowlist() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
when(mAppStateTracker.isUidPowerSaveUserExempt(TEST_CALLING_UID)).thenReturn(true);
@@ -3052,135 +2974,11 @@
}
@Test
- public void denyListChanged() {
- mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"});
- when(mActivityManagerInternal.getStartedUserIds()).thenReturn(EmptyArray.INT);
-
- setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5");
-
- final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
- verify(mService.mHandler, times(2)).sendMessageAtTime(messageCaptor.capture(),
- anyLong());
-
- final List<Message> messages = messageCaptor.getAllValues();
- for (final Message msg : messages) {
- assertTrue("Unwanted message sent to handler: " + msg.what,
- msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_ADDED
- || msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED);
- mService.mHandler.handleMessage(msg);
- }
-
- ArraySet<String> added = new ArraySet<>(new String[]{"p4", "p5"});
- verify(mService).handleChangesToExactAlarmDenyList(eq(added), eq(true));
-
- ArraySet<String> removed = new ArraySet<>(new String[]{"p1", "p3"});
- verify(mService).handleChangesToExactAlarmDenyList(eq(removed), eq(false));
- }
-
- @Test
- public void permissionGrantedDueToDenyList() {
- mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
- final String[] packages = {"example.package.1", "example.package.2"};
-
- final int appId1 = 232;
- final int appId2 = 431;
-
- final int userId1 = 42;
- final int userId2 = 53;
-
- registerAppIds(packages, new Integer[]{appId1, appId2});
-
- when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2});
-
- when(mPermissionManagerInternal.getAppOpPermissionPackages(
- SCHEDULE_EXACT_ALARM)).thenReturn(packages);
- mService.refreshExactAlarmCandidates();
-
- final long allowListDuration = 53442;
- when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(
- allowListDuration);
-
- mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED);
- mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT);
- mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED);
- mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED);
-
- mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false);
-
- // No permission revoked.
- verify(mService, never()).removeExactAlarmsOnPermissionRevoked(anyInt(), anyString(),
- anyBoolean());
-
- // Permission got granted only for (appId1, userId2).
- final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
- final ArgumentCaptor<UserHandle> userCaptor = ArgumentCaptor.forClass(UserHandle.class);
-
- verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), userCaptor.capture(),
- isNull(), bundleCaptor.capture());
-
- assertEquals(userId2, userCaptor.getValue().getIdentifier());
-
- // Validate the intent.
- assertEquals(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
- intentCaptor.getValue().getAction());
- assertEquals(packages[0], intentCaptor.getValue().getPackage());
-
- // Validate the options.
- final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue());
- assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
- bOptions.getTemporaryAppAllowlistType());
- assertEquals(allowListDuration, bOptions.getTemporaryAppAllowlistDuration());
- assertEquals(PowerExemptionManager.REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
- bOptions.getTemporaryAppAllowlistReasonCode());
- }
-
- @Test
- public void permissionRevokedDueToDenyList() {
- mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
- final String[] packages = {"example.package.1", "example.package.2"};
-
- final int appId1 = 232;
- final int appId2 = 431;
-
- final int userId1 = 42;
- final int userId2 = 53;
-
- registerAppIds(packages, new Integer[]{appId1, appId2});
-
- when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2});
-
- when(mPermissionManagerInternal.getAppOpPermissionPackages(
- SCHEDULE_EXACT_ALARM)).thenReturn(packages);
- mService.refreshExactAlarmCandidates();
-
- mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED);
- mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT);
- mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED);
- mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED);
-
- mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), true);
-
- // Permission got revoked only for (appId1, userId2)
- verify(mService, never()).removeExactAlarmsOnPermissionRevoked(
- eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]), eq(true));
- verify(mService, never()).removeExactAlarmsOnPermissionRevoked(
- eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]), eq(true));
- verify(mService, never()).removeExactAlarmsOnPermissionRevoked(
- eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]), eq(true));
-
- verify(mService).removeExactAlarmsOnPermissionRevoked(
- eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]), eq(true));
- }
-
- @Test
public void opChangedPermissionRevoked() throws Exception {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
assertAndHandleMessageSync(REMOVE_EXACT_ALARMS);
@@ -3193,20 +2991,7 @@
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
-
- mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
-
- verify(mService.mHandler, never()).sendMessageAtTime(
- argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong());
- }
-
- @Test
- public void opChangedNoPermissionChangeDueToDenyList() throws Exception {
- mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
-
- mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
- mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
@@ -3222,7 +3007,7 @@
when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs);
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -3482,7 +3267,7 @@
.putExtra(Intent.EXTRA_REPLACING, true);
mockUseExactAlarmState(false);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
@@ -3490,7 +3275,7 @@
assertEquals(5, mService.mAlarmStore.size());
mockUseExactAlarmState(true);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
@@ -3498,7 +3283,7 @@
assertEquals(5, mService.mAlarmStore.size());
mockUseExactAlarmState(false);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
@@ -3653,16 +3438,16 @@
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false);
- mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
+ mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);
+ assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+
+ mockScheduleExactAlarmStatePreT(false, MODE_ALLOWED);
assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
- mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
- assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
-
- mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
}
@@ -3671,11 +3456,11 @@
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
mockScheduleExactAlarmState(true);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
mockScheduleExactAlarmState(false);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index 596a3f3..b3605cc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -280,7 +280,9 @@
0, 0);
// Sleep until timeout should have triggered
- SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
+ if (wedge) {
+ SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
+ }
return app;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 410ae35..367e14b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -228,7 +228,7 @@
LocalServices.removeServiceForTest(AlarmManagerInternal.class);
LocalServices.addService(AlarmManagerInternal.class, mAlarmManagerInt);
doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
- doNothing().when(mPackageManagerInt).setPackageStoppedState(any(), anyBoolean(), anyInt());
+ doNothing().when(mPackageManagerInt).notifyComponentUsed(any(), anyInt(), any(), any());
doAnswer((invocation) -> {
return getUidForPackage(invocation.getArgument(0));
}).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM));
@@ -1014,8 +1014,9 @@
eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
// Confirm that we unstopped manifest receivers
- verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState(
- eq(receiverApp.info.packageName), eq(false), eq(UserHandle.USER_SYSTEM));
+ verify(mAms.mPackageManagerInt, atLeastOnce()).notifyComponentUsed(
+ eq(receiverApp.info.packageName), eq(UserHandle.USER_SYSTEM),
+ eq(callerApp.info.packageName), any());
}
// Confirm that we've reported expected usage events
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 76b41b7..c493f84 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -2629,7 +2629,7 @@
PROCESS_STATE_NONEXISTENT, PROCESS_STATE_NONEXISTENT,
0, 0, false, false, false, ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE,
false, false, false, hasShownUi, false, false, false, false, false, false, null,
- 0, 0, 0, true, 0, null, false);
+ 0, Long.MIN_VALUE, Long.MIN_VALUE, true, 0, null, false);
}
private ProcessRecord makeProcessRecord(ActivityManagerService service, int pid, int uid,
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
index 1ce79a5..05ac5b5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
@@ -16,8 +16,6 @@
package com.android.server.display;
-import static android.os.Process.INVALID_UID;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -35,7 +33,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.pkg.PackageStateInternal;
import org.junit.Before;
import org.junit.Rule;
@@ -55,7 +53,10 @@
@Mock
private PackageManagerInternal mMockPackageManagerInternal;
@Mock
- private UserManagerInternal mMockUserManagerInternal;
+ private PackageStateInternal mMockPkgStateA;
+ @Mock
+ private PackageStateInternal mMockPkgStateB;
+
private SmallAreaDetectionController mSmallAreaDetectionController;
@@ -64,29 +65,18 @@
private static final String PKG_NOT_INSTALLED = "com.not.installed";
private static final float THRESHOLD_A = 0.05f;
private static final float THRESHOLD_B = 0.07f;
- private static final int USER_1 = 110;
- private static final int USER_2 = 111;
- private static final int UID_A_1 = 11011111;
- private static final int UID_A_2 = 11111111;
- private static final int UID_B_1 = 11022222;
- private static final int UID_B_2 = 11122222;
+ private static final int APP_ID_A = 11111;
+ private static final int APP_ID_B = 22222;
@Before
public void setup() {
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
- LocalServices.removeServiceForTest(UserManagerInternal.class);
- LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
- when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[]{USER_1, USER_2});
- when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_1)).thenReturn(UID_A_1);
- when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_2)).thenReturn(UID_A_2);
- when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_1)).thenReturn(UID_B_1);
- when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_2)).thenReturn(UID_B_2);
- when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_1)).thenReturn(
- INVALID_UID);
- when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_2)).thenReturn(
- INVALID_UID);
+ when(mMockPackageManagerInternal.getPackageStateInternal(PKG_A)).thenReturn(mMockPkgStateA);
+ when(mMockPackageManagerInternal.getPackageStateInternal(PKG_B)).thenReturn(mMockPkgStateB);
+ when(mMockPkgStateA.getAppId()).thenReturn(APP_ID_A);
+ when(mMockPkgStateB.getAppId()).thenReturn(APP_ID_B);
mSmallAreaDetectionController = spy(new SmallAreaDetectionController(
new ContextWrapper(ApplicationProvider.getApplicationContext()),
@@ -99,9 +89,9 @@
final String property = PKG_A + ":" + THRESHOLD_A + "," + PKG_B + ":" + THRESHOLD_B;
mSmallAreaDetectionController.updateAllowlist(property);
- final int[] resultUidArray = {UID_A_1, UID_B_1, UID_A_2, UID_B_2};
- final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B, THRESHOLD_A, THRESHOLD_B};
- verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+ final int[] resultAppIdArray = {APP_ID_A, APP_ID_B};
+ final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B};
+ verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),
eq(resultThresholdArray));
}
@@ -110,9 +100,9 @@
final String property = PKG_A + "," + PKG_B + ":" + THRESHOLD_B;
mSmallAreaDetectionController.updateAllowlist(property);
- final int[] resultUidArray = {UID_B_1, UID_B_2};
- final float[] resultThresholdArray = {THRESHOLD_B, THRESHOLD_B};
- verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+ final int[] resultAppIdArray = {APP_ID_B};
+ final float[] resultThresholdArray = {THRESHOLD_B};
+ verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),
eq(resultThresholdArray));
}
@@ -122,9 +112,9 @@
PKG_A + ":" + THRESHOLD_A + "," + PKG_NOT_INSTALLED + ":" + THRESHOLD_B;
mSmallAreaDetectionController.updateAllowlist(property);
- final int[] resultUidArray = {UID_A_1, UID_A_2};
- final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_A};
- verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+ final int[] resultAppIdArray = {APP_ID_A};
+ final float[] resultThresholdArray = {THRESHOLD_A};
+ verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),
eq(resultThresholdArray));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 2f6859c..be33b1b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -165,8 +165,7 @@
null
}
whenever(mocks.settings.addPackageLPw(nullable(), nullable(), nullable(), nullable(),
- nullable(), nullable(), nullable(), nullable(), nullable(), nullable(), nullable(),
- nullable(), nullable(), nullable(), nullable(), nullable(), nullable())) {
+ nullable(), nullable(), nullable())) {
val name: String = getArgument(0)
val pendingAdd = mPendingPackageAdds.firstOrNull { it.first == name }
?: return@whenever null
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index eb50556..610ea90 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
@@ -427,6 +428,7 @@
for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
mainActivity.getLabel().toString(),
+ mainActivity.getComponentName(),
ICON_PATH, null);
activityInfos.add(activityInfo);
}
@@ -437,9 +439,11 @@
ActivityInfo activityInfo = mock(ActivityInfo.class);
LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class);
when(activity1.getLabel()).thenReturn("activity1");
+ when(activity1.getComponentName()).thenReturn(new ComponentName("pkg1", "class1"));
when(activity1.getActivityInfo()).thenReturn(activityInfo);
LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class);
when(activity2.getLabel()).thenReturn("activity2");
+ when(activity2.getComponentName()).thenReturn(new ComponentName("pkg2", "class2"));
when(activity2.getActivityInfo()).thenReturn(activityInfo);
return List.of(activity1, activity2);
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
new file mode 100644
index 0000000..2003d04
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.PersistableBundle;
+import android.util.Xml;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.PowerStats;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.text.ParseException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AggregatedPowerStatsTest {
+ private static final int TEST_POWER_COMPONENT = 1077;
+ private static final int APP_1 = 27;
+ private static final int APP_2 = 42;
+
+ private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+ private PowerStats.Descriptor mPowerComponentDescriptor;
+
+ @Before
+ public void setup() throws ParseException {
+ mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig();
+ mAggregatedPowerStatsConfig.trackPowerComponent(TEST_POWER_COMPONENT)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
+
+ mPowerComponentDescriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "fan", 2, 3,
+ PersistableBundle.forPair("speed", "fast"));
+ }
+
+ @Test
+ public void aggregation() {
+ AggregatedPowerStats stats = prepareAggregatePowerStats();
+
+ verifyAggregatedPowerStats(stats);
+ }
+
+ @Test
+ public void xmlPersistence() throws Exception {
+ AggregatedPowerStats stats = prepareAggregatePowerStats();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ serializer.setOutput(baos, "UTF-8");
+ stats.writeXml(serializer);
+ serializer.flush();
+
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new ByteArrayInputStream(baos.toByteArray()), "UTF-8");
+ AggregatedPowerStats actualStats = AggregatedPowerStats.createFromXml(parser,
+ mAggregatedPowerStatsConfig);
+
+ verifyAggregatedPowerStats(actualStats);
+ }
+
+ private AggregatedPowerStats prepareAggregatePowerStats() {
+ AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig);
+ stats.addClockUpdate(1000, 456);
+ stats.setDuration(789);
+
+ stats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON, 2000);
+ stats.setUidState(APP_1, AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
+ BatteryConsumer.PROCESS_STATE_CACHED, 2000);
+ stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND, 2000);
+
+ PowerStats ps = new PowerStats(mPowerComponentDescriptor);
+ ps.stats[0] = 100;
+ ps.stats[1] = 987;
+
+ ps.uidStats.put(APP_1, new long[]{389, 0, 739});
+ ps.uidStats.put(APP_2, new long[]{278, 314, 628});
+
+ stats.addPowerStats(ps, 3000);
+
+ stats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER, 4000);
+ stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND, 4000);
+
+ ps.stats[0] = 444;
+ ps.stats[1] = 0;
+
+ ps.uidStats.put(APP_1, new long[]{0, 0, 400});
+ ps.uidStats.put(APP_2, new long[]{100, 200, 300});
+
+ stats.addPowerStats(ps, 5000);
+
+ return stats;
+ }
+
+ private void verifyAggregatedPowerStats(AggregatedPowerStats stats) {
+ PowerStats.Descriptor descriptor = stats.getPowerComponentStats(TEST_POWER_COMPONENT)
+ .getPowerStatsDescriptor();
+ assertThat(descriptor.powerComponentId).isEqualTo(TEST_POWER_COMPONENT);
+ assertThat(descriptor.name).isEqualTo("fan");
+ assertThat(descriptor.statsArrayLength).isEqualTo(2);
+ assertThat(descriptor.uidStatsArrayLength).isEqualTo(3);
+ assertThat(descriptor.extras.getString("speed")).isEqualTo("fast");
+
+ assertThat(getDeviceStats(stats,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON))
+ .isEqualTo(new long[]{322, 987});
+
+ assertThat(getDeviceStats(stats,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER))
+ .isEqualTo(new long[]{222, 0});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_1,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED))
+ .isEqualTo(new long[]{259, 0, 492});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_1,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_CACHED))
+ .isEqualTo(new long[]{129, 0, 446});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_1,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
+ BatteryConsumer.PROCESS_STATE_CACHED))
+ .isEqualTo(new long[]{0, 0, 200});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_2,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED))
+ .isEqualTo(new long[]{185, 209, 418});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_2,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND))
+ .isEqualTo(new long[]{142, 204, 359});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_2,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND))
+ .isEqualTo(new long[]{50, 100, 150});
+ }
+
+ private static long[] getDeviceStats(AggregatedPowerStats stats, int... states) {
+ long[] out = new long[states.length];
+ stats.getPowerComponentStats(TEST_POWER_COMPONENT).getDeviceStats(out, states);
+ return out;
+ }
+
+ private static long[] getUidDeviceStats(AggregatedPowerStats stats, int uid, int... states) {
+ long[] out = new long[states.length];
+ stats.getPowerComponentStats(TEST_POWER_COMPONENT).getUidStats(out, uid, states);
+ return out;
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index 5a2d2e3..663af5d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -42,6 +42,7 @@
import androidx.test.InstrumentationRegistry;
+import com.android.internal.os.Clock;
import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.PowerProfile;
@@ -214,6 +215,7 @@
public class TestBatteryStatsImpl extends BatteryStatsImpl {
public TestBatteryStatsImpl(Context context) {
+ super(Clock.SYSTEM_CLOCK, null);
mPowerProfile = new PowerProfile(context, true /* forTest */);
SparseArray<int[]> cpusByPolicy = new SparseArray<>();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
index 77124d0..abb3be7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
@@ -113,15 +113,15 @@
public void constrainedIteration() {
prepareHistory();
- // Initial time is 3000
+ // Initial time is 1000_000
assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 0),
- 3_000L, 3_000L, 1003_000L, 2003_000L, 2004_000L);
- assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1000_000, 0),
- 1003_000L, 2003_000L, 2004_000L);
- assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 2000_000L),
- 3_000L, 3_000L, 1003_000L);
+ 1000_000L, 1000_000L, 2000_000L, 3000_000L, 3001_000L);
+ assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(2000_000, 0),
+ 2000_000L, 3000_000L, 3001_000L);
+ assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 3000_000L),
+ 1000_000L, 1000_000L, 2000_000L);
assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1003_000L, 2004_000L),
- 1003_000L, 2003_000L);
+ 2000_000L);
}
private void prepareHistory() {
@@ -144,7 +144,7 @@
ArrayList<Long> actualTimestamps = new ArrayList<>();
while (iterator.hasNext()) {
BatteryStats.HistoryItem item = iterator.next();
- actualTimestamps.add(item.currentTime);
+ actualTimestamps.add(item.time);
}
assertThat(actualTimestamps).isEqualTo(Arrays.asList(expectedTimestamps));
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index f22296a..1dd499c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -41,6 +41,7 @@
import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerStats;
import org.junit.Before;
@@ -69,6 +70,7 @@
private File mSystemDir;
private File mHistoryDir;
private final MockClock mClock = new MockClock();
+ private final MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
private BatteryStatsHistory mHistory;
private BatteryStats.HistoryPrinter mHistoryPrinter;
@Mock
@@ -94,7 +96,7 @@
mClock.realtime = 123;
mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
- mStepDetailsCalculator, mClock, mTracer) {
+ mStepDetailsCalculator, mClock, mMonotonicClock, mTracer) {
@Override
public boolean readFileToParcel(Parcel out, AtomicFile file) {
mReadFiles.add(file.getBaseFile().getName());
@@ -210,7 +212,7 @@
mClock.realtime = 1000 * i;
fileList.add(mClock.realtime + ".bh");
- mHistory.startNextFile();
+ mHistory.startNextFile(mClock.realtime);
createActiveFile(mHistory);
verifyFileNames(mHistory, fileList);
verifyActiveFile(mHistory, mClock.realtime + ".bh");
@@ -218,7 +220,7 @@
// create file 32
mClock.realtime = 1000 * 32;
- mHistory.startNextFile();
+ mHistory.startNextFile(mClock.realtime);
createActiveFile(mHistory);
fileList.add("32000.bh");
fileList.remove(0);
@@ -229,7 +231,7 @@
// create file 33
mClock.realtime = 1000 * 33;
- mHistory.startNextFile();
+ mHistory.startNextFile(mClock.realtime);
createActiveFile(mHistory);
// verify file 1 is deleted
fileList.add("33000.bh");
@@ -240,7 +242,7 @@
// create a new BatteryStatsHistory object, it will pick up existing history files.
BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
- null, mClock, mTracer);
+ null, mClock, mMonotonicClock, mTracer);
// verify constructor can pick up all files from file system.
verifyFileNames(history2, fileList);
verifyActiveFile(history2, "33000.bh");
@@ -262,7 +264,7 @@
// create file 1.
mClock.realtime = 2345678;
- history2.startNextFile();
+ history2.startNextFile(mClock.realtime);
createActiveFile(history2);
verifyFileNames(history2, Arrays.asList("1234567.bh", "2345678.bh"));
verifyActiveFile(history2, "2345678.bh");
@@ -336,14 +338,14 @@
mHistory.recordEvent(mClock.realtime, mClock.uptime,
BatteryStats.HistoryItem.EVENT_JOB_START, "job", 42);
- mHistory.startNextFile(); // 1000.bh
+ mHistory.startNextFile(mClock.realtime); // 1000.bh
mClock.realtime = 2000;
mClock.uptime = 2000;
mHistory.recordEvent(mClock.realtime, mClock.uptime,
BatteryStats.HistoryItem.EVENT_JOB_FINISH, "job", 42);
- mHistory.startNextFile(); // 2000.bh
+ mHistory.startNextFile(mClock.realtime); // 2000.bh
mClock.realtime = 3000;
mClock.uptime = 3000;
@@ -351,7 +353,7 @@
HistoryItem.EVENT_ALARM, "alarm", 42);
// Flush accumulated history to disk
- mHistory.startNextFile();
+ mHistory.startNextFile(mClock.realtime);
}
private void verifyActiveFile(BatteryStatsHistory history, String file) {
@@ -518,7 +520,7 @@
// Keep the preserved part of history short - we only need to capture the very tail of
// history.
mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 1, 6000,
- mStepDetailsCalculator, mClock, mTracer);
+ mStepDetailsCalculator, mClock, mMonotonicClock, mTracer);
mHistory.forceRecordAllHistory();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index 5df0acb..b1da1fc 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -40,6 +40,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
import org.junit.Rule;
@@ -64,6 +65,8 @@
new BatteryUsageStatsRule(12345, mHistoryDir)
.setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0)
.setAveragePower(PowerProfile.POWER_AUDIO, 720.0);
+ private MockClock mMockClock = mStatsRule.getMockClock();
+
@Test
public void test_getBatteryUsageStats() {
BatteryStatsImpl batteryStats = prepareBatteryStats();
@@ -369,17 +372,23 @@
public void testAggregateBatteryStats() {
Context context = InstrumentationRegistry.getContext();
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
- mStatsRule.setCurrentTime(5 * MINUTE_IN_MS);
+ MonotonicClock monotonicClock = new MonotonicClock(0, mStatsRule.getMockClock());
+
+ setTime(5 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
- BatteryUsageStatsStore batteryUsageStatsStore = new BatteryUsageStatsStore(context,
- batteryStats, new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"),
- new TestHandler(), Integer.MAX_VALUE);
- batteryUsageStatsStore.onSystemReady();
+
+ PowerStatsStore powerStatsStore = new PowerStatsStore(
+ new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"),
+ new TestHandler(), null);
BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
- batteryStats, batteryUsageStatsStore);
+ batteryStats, powerStatsStore);
+
+ batteryStats.setBatteryResetListener(reason ->
+ powerStatsStore.storeBatteryUsageStats(monotonicClock.monotonicTime(),
+ provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT)));
synchronized (batteryStats) {
batteryStats.noteFlashlightOnLocked(APP_UID,
@@ -389,7 +398,7 @@
batteryStats.noteFlashlightOffLocked(APP_UID,
20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
}
- mStatsRule.setCurrentTime(25 * MINUTE_IN_MS);
+ setTime(25 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
@@ -402,7 +411,7 @@
batteryStats.noteFlashlightOffLocked(APP_UID,
50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
}
- mStatsRule.setCurrentTime(55 * MINUTE_IN_MS);
+ setTime(55 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
@@ -416,7 +425,7 @@
batteryStats.noteFlashlightOffLocked(APP_UID,
70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS);
}
- mStatsRule.setCurrentTime(75 * MINUTE_IN_MS);
+ setTime(75 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
@@ -430,7 +439,7 @@
batteryStats.noteFlashlightOffLocked(APP_UID,
90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS);
}
- mStatsRule.setCurrentTime(95 * MINUTE_IN_MS);
+ setTime(95 * MINUTE_IN_MS);
// Include the first and the second snapshot, but not the third or current
BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
@@ -457,29 +466,41 @@
.of(180.0);
}
+ private void setTime(long timeMs) {
+ mMockClock.currentTime = timeMs;
+ mMockClock.realtime = timeMs;
+ }
+
@Test
public void testAggregateBatteryStats_incompatibleSnapshot() {
Context context = InstrumentationRegistry.getContext();
MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
- BatteryUsageStatsStore batteryUsageStatsStore = mock(BatteryUsageStatsStore.class);
+ PowerStatsStore powerStatsStore = mock(PowerStatsStore.class);
- when(batteryUsageStatsStore.listBatteryUsageStatsTimestamps())
- .thenReturn(new long[]{1000, 2000});
-
- when(batteryUsageStatsStore.loadBatteryUsageStats(1000)).thenReturn(
+ PowerStatsSpan span0 = new PowerStatsSpan(0);
+ span0.addTimeFrame(0, 1000, 1234);
+ span0.addSection(new BatteryUsageStatsSection(
new BatteryUsageStats.Builder(batteryStats.getCustomEnergyConsumerNames())
- .setStatsDuration(1234).build());
+ .setStatsDuration(1234).build()));
- // Add a snapshot, with a different set of custom power components. It should
- // be skipped by the aggregation.
- when(batteryUsageStatsStore.loadBatteryUsageStats(2000)).thenReturn(
+ PowerStatsSpan span1 = new PowerStatsSpan(1);
+ span1.addTimeFrame(0, 2000, 4321);
+ span1.addSection(new BatteryUsageStatsSection(
new BatteryUsageStats.Builder(new String[]{"different"})
- .setStatsDuration(4321).build());
+ .setStatsDuration(4321).build()));
+
+ when(powerStatsStore.getTableOfContents()).thenReturn(
+ List.of(span0.getMetadata(), span1.getMetadata()));
+
+ when(powerStatsStore.loadPowerStatsSpan(0, BatteryUsageStatsSection.TYPE))
+ .thenReturn(span0);
+ when(powerStatsStore.loadPowerStatsSpan(1, BatteryUsageStatsSection.TYPE))
+ .thenReturn(span1);
BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
- batteryStats, batteryUsageStatsStore);
+ batteryStats, powerStatsStore);
BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
.aggregateSnapshots(0, 3000)
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 93cbea6..3579fce 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -88,6 +88,10 @@
mBatteryStats.onSystemReady();
}
+ public MockClock getMockClock() {
+ return mMockClock;
+ }
+
public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) {
mPowerProfile.forceInitForTesting(mContext, xmlId);
return this;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java
deleted file mode 100644
index b846e3a..0000000
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2021 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.power.stats;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-
-import android.content.Context;
-import android.os.BatteryManager;
-import android.os.BatteryUsageStats;
-import android.os.BatteryUsageStatsQuery;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Xml;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.os.PowerProfile;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-
-@RunWith(AndroidJUnit4.class)
-@SuppressWarnings("GuardedBy")
-public class BatteryUsageStatsStoreTest {
- private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024;
-
- private final MockClock mMockClock = new MockClock();
- private MockBatteryStatsImpl mBatteryStats;
- private BatteryUsageStatsStore mBatteryUsageStatsStore;
- private BatteryUsageStatsProvider mBatteryUsageStatsProvider;
- private File mStoreDirectory;
-
- @Before
- public void setup() {
- mMockClock.currentTime = 123;
- mBatteryStats = new MockBatteryStatsImpl(mMockClock);
- mBatteryStats.setNoAutoReset(true);
- mBatteryStats.setPowerProfile(mock(PowerProfile.class));
- mBatteryStats.onSystemReady();
-
- Context context = InstrumentationRegistry.getContext();
-
- mStoreDirectory = new File(context.getCacheDir(), "BatteryUsageStatsStoreTest");
- clearDirectory(mStoreDirectory);
-
- mBatteryUsageStatsStore = new BatteryUsageStatsStore(context, mBatteryStats,
- mStoreDirectory, new TestHandler(), MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
- mBatteryUsageStatsStore.onSystemReady();
-
- mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats);
- }
-
- @Test
- public void testStoreSnapshot() {
- mMockClock.currentTime = 1_600_000;
- mMockClock.realtime = 1000;
- mMockClock.uptime = 1000;
-
- prepareBatteryStats();
-
- mMockClock.realtime = 1_000_000;
- mMockClock.uptime = 1_000_000;
- mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-
- final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
- assertThat(timestamps).hasLength(1);
- assertThat(timestamps[0]).isEqualTo(1_600_000);
-
- final BatteryUsageStats batteryUsageStats = mBatteryUsageStatsStore.loadBatteryUsageStats(
- 1_600_000);
- assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(123);
- assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(1_600_000);
- assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000);
- assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(5);
- assertThat(batteryUsageStats.getDischargeDurationMs()).isEqualTo(1_000_000 - 1_000);
- assertThat(batteryUsageStats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE).getConsumedPower())
- .isEqualTo(600); // (3_600_000 - 3_000_000) / 1000
- }
-
- @Test
- public void testGarbageCollectOldSnapshots() throws Exception {
- prepareBatteryStats();
-
- mMockClock.realtime = 10_000_000;
- mMockClock.uptime = 10_000_000;
- mMockClock.currentTime = 10_000_000;
-
- final int snapshotFileSize = getSnapshotFileSize();
- final int numberOfSnapshots =
- (int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / snapshotFileSize);
- for (int i = 0; i < numberOfSnapshots + 2; i++) {
- mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-
- mMockClock.realtime += 10_000_000;
- mMockClock.uptime += 10_000_000;
- mMockClock.currentTime += 10_000_000;
- prepareBatteryStats();
- }
-
- final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
- Arrays.sort(timestamps);
- assertThat(timestamps).hasLength(numberOfSnapshots);
- // Two snapshots (10_000_000 and 20_000_000) should have been discarded
- assertThat(timestamps[0]).isEqualTo(30_000_000);
- assertThat(getDirectorySize(mStoreDirectory))
- .isAtMost(MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
- }
-
- @Test
- public void testRemoveAllSnapshots() throws Exception {
- prepareBatteryStats();
-
- for (int i = 0; i < 3; i++) {
- mMockClock.realtime += 10_000_000;
- mMockClock.uptime += 10_000_000;
- mMockClock.currentTime += 10_000_000;
- prepareBatteryStats();
-
- mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
- }
-
- assertThat(getDirectorySize(mStoreDirectory)).isNotEqualTo(0);
-
- mBatteryUsageStatsStore.removeAllSnapshots();
-
- assertThat(getDirectorySize(mStoreDirectory)).isEqualTo(0);
- }
-
- @Test
- public void testSavingStatsdAtomPullTimestamp() {
- mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234);
- assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
- .isEqualTo(1234);
- mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(5478);
- assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
- .isEqualTo(5478);
- }
-
- private void prepareBatteryStats() {
- mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
- /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0,
- mMockClock.realtime, mMockClock.uptime, mMockClock.currentTime);
- mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
- /* plugType */ 0, 85, 72, 3700, 3_000_000, 4_000_000, 0,
- mMockClock.realtime + 500_000, mMockClock.uptime + 500_000,
- mMockClock.currentTime + 500_000);
- }
-
- private void clearDirectory(File dir) {
- if (dir.exists()) {
- for (File child : dir.listFiles()) {
- if (child.isDirectory()) {
- clearDirectory(child);
- }
- child.delete();
- }
- }
- }
-
- private long getDirectorySize(File dir) {
- long size = 0;
- if (dir.exists()) {
- for (File child : dir.listFiles()) {
- if (child.isDirectory()) {
- size += getDirectorySize(child);
- } else {
- size += child.length();
- }
- }
- }
- return size;
- }
-
- private int getSnapshotFileSize() throws IOException {
- BatteryUsageStats stats = mBatteryUsageStatsProvider.getBatteryUsageStats(
- new BatteryUsageStatsQuery.Builder()
- .setMaxStatsAgeMs(0)
- .includePowerModels()
- .build());
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- TypedXmlSerializer serializer = Xml.newBinarySerializer();
- serializer.setOutput(out, StandardCharsets.UTF_8.name());
- serializer.startDocument(null, true);
- stats.writeXml(serializer);
- serializer.endDocument();
- return out.toByteArray().length;
- }
-
- private static class TestHandler extends Handler {
- TestHandler() {
- super(Looper.getMainLooper());
- }
-
- @Override
- public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
- msg.getCallback().run();
- return true;
- }
- }
-}
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 4ecee9f..30a73181 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
@@ -122,7 +122,7 @@
// 4 * 10 = 40 bits needed to represent the composite state
MultiStateStats.States[] states = new MultiStateStats.States[10];
for (int i = 0; i < states.length; i++) {
- states[i] = new MultiStateStats.States(true, labels);
+ states[i] = new MultiStateStats.States("foo", true, labels);
}
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
() -> new MultiStateStats.Factory(DIMENSION_COUNT, states));
@@ -191,8 +191,8 @@
private static MultiStateStats.Factory makeFactory(boolean trackBatteryState,
boolean trackProcState, boolean trackScreenState) {
return new MultiStateStats.Factory(DIMENSION_COUNT,
- new MultiStateStats.States(trackBatteryState, "plugged-in", "on-battery"),
- new MultiStateStats.States(trackProcState,
+ new MultiStateStats.States("bs", trackBatteryState, "plugged-in", "on-battery"),
+ new MultiStateStats.States("ps", trackProcState,
BatteryConsumer.processStateToString(
BatteryConsumer.PROCESS_STATE_UNSPECIFIED),
BatteryConsumer.processStateToString(
@@ -203,7 +203,7 @@
BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE),
BatteryConsumer.processStateToString(
BatteryConsumer.PROCESS_STATE_CACHED)),
- new MultiStateStats.States(trackScreenState, "screen-off", "plugged-in"));
+ new MultiStateStats.States("scr", trackScreenState, "screen-off", "plugged-in"));
}
private FactorySubject assertThatCpuPerformanceStatsFactory(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
index 47de443..b52fc8a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
@@ -24,11 +24,14 @@
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.PersistableBundle;
+import android.text.format.DateFormat;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerStats;
import org.junit.Before;
@@ -36,16 +39,19 @@
import org.junit.runner.RunWith;
import java.text.ParseException;
-import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.List;
+import java.util.TimeZone;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class PowerStatsAggregatorTest {
private static final int TEST_POWER_COMPONENT = 77;
private static final int TEST_UID = 42;
+ private static final long START_TIME = 1234;
private final MockClock mClock = new MockClock();
- private long mStartTime;
+ private final MonotonicClock mMonotonicClock = new MonotonicClock(START_TIME, mClock);
private BatteryStatsHistory mHistory;
private PowerStatsAggregator mAggregator;
private int mAggregatedStatsCount;
@@ -53,25 +59,25 @@
@Before
public void setup() throws ParseException {
mHistory = new BatteryStatsHistory(32, 1024,
- mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock);
- mStartTime = new SimpleDateFormat("yyyy-MM-dd HH:mm")
- .parse("2008-09-23 08:00").getTime();
- mClock.currentTime = mStartTime;
+ mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock,
+ mMonotonicClock);
- PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory);
- builder.trackPowerComponent(TEST_POWER_COMPONENT)
+ AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+ config.trackPowerComponent(TEST_POWER_COMPONENT)
.trackDeviceStates(
- PowerStatsAggregator.STATE_POWER,
- PowerStatsAggregator.STATE_SCREEN)
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
.trackUidStates(
- PowerStatsAggregator.STATE_POWER,
- PowerStatsAggregator.STATE_SCREEN,
- PowerStatsAggregator.STATE_PROCESS_STATE);
- mAggregator = builder.build();
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
+ mAggregator = new PowerStatsAggregator(config, mHistory);
}
@Test
public void stateUpdates() {
+ mClock.currentTime = 1222156800000L; // An important date in world history
+
mHistory.forceRecordAllHistory();
mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true);
mHistory.recordStateStartEvent(mClock.realtime, mClock.uptime,
@@ -98,15 +104,32 @@
mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID,
BatteryConsumer.PROCESS_STATE_BACKGROUND);
- advance(3000);
+ advance(1000);
+
+ mClock.currentTime += 60 * 60 * 1000; // one hour
+ mHistory.recordCurrentTimeChange(mClock.realtime, mClock.uptime, mClock.currentTime);
+
+ advance(2000);
powerStats.stats = new long[]{20000};
powerStats.uidStats.put(TEST_UID, new long[]{4444});
mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
- mAggregator.aggregateBatteryStats(0, 0, stats -> {
+ mAggregator.aggregatePowerStats(0, 0, stats -> {
assertThat(mAggregatedStatsCount++).isEqualTo(0);
- assertThat(stats.getStartTime()).isEqualTo(mStartTime);
+ assertThat(stats.getStartTime()).isEqualTo(START_TIME);
+
+ List<AggregatedPowerStats.ClockUpdate> clockUpdates = stats.getClockUpdates();
+ assertThat(clockUpdates).hasSize(2);
+
+ AggregatedPowerStats.ClockUpdate clockUpdate0 = clockUpdates.get(0);
+ assertThat(clockUpdate0.monotonicTime).isEqualTo(1234);
+ assertThat(formatDateTime(clockUpdate0.currentTime)).isEqualTo("2008-09-23 08:00:00");
+
+ AggregatedPowerStats.ClockUpdate clockUpdate1 = clockUpdates.get(1);
+ assertThat(clockUpdate1.monotonicTime).isEqualTo(1234 + 3000);
+ assertThat(formatDateTime(clockUpdate1.currentTime)).isEqualTo("2008-09-23 09:00:03");
+
assertThat(stats.getDuration()).isEqualTo(5000);
long[] values = new long[1];
@@ -115,40 +138,47 @@
TEST_POWER_COMPONENT);
assertThat(powerComponentStats.getDeviceStats(values, new int[]{
- PowerStatsAggregator.POWER_STATE_OTHER,
- PowerStatsAggregator.SCREEN_STATE_ON}))
+ AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON}))
.isTrue();
assertThat(values).isEqualTo(new long[]{10000});
assertThat(powerComponentStats.getDeviceStats(values, new int[]{
- PowerStatsAggregator.POWER_STATE_BATTERY,
- PowerStatsAggregator.SCREEN_STATE_OTHER}))
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER}))
.isTrue();
assertThat(values).isEqualTo(new long[]{20000});
assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
- PowerStatsAggregator.POWER_STATE_OTHER,
- PowerStatsAggregator.SCREEN_STATE_ON,
+ AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
BatteryConsumer.PROCESS_STATE_FOREGROUND}))
.isTrue();
assertThat(values).isEqualTo(new long[]{1234});
assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
- PowerStatsAggregator.POWER_STATE_BATTERY,
- PowerStatsAggregator.SCREEN_STATE_OTHER,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
BatteryConsumer.PROCESS_STATE_FOREGROUND}))
.isTrue();
assertThat(values).isEqualTo(new long[]{1111});
assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
- PowerStatsAggregator.POWER_STATE_BATTERY,
- PowerStatsAggregator.SCREEN_STATE_OTHER,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
BatteryConsumer.PROCESS_STATE_BACKGROUND}))
.isTrue();
assertThat(values).isEqualTo(new long[]{3333});
});
}
+ @NonNull
+ private static CharSequence formatDateTime(long timeInMillis) {
+ Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+ cal.setTimeInMillis(timeInMillis);
+ return DateFormat.format("yyyy-MM-dd hh:mm:ss", cal);
+ }
+
@Test
public void incompatiblePowerStats() {
mHistory.forceRecordAllHistory();
@@ -181,39 +211,39 @@
mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 50, /* plugged */ true);
- mAggregator.aggregateBatteryStats(0, 0, stats -> {
+ mAggregator.aggregatePowerStats(0, 0, stats -> {
long[] values = new long[1];
PowerComponentAggregatedPowerStats powerComponentStats =
stats.getPowerComponentStats(TEST_POWER_COMPONENT);
if (mAggregatedStatsCount == 0) {
- assertThat(stats.getStartTime()).isEqualTo(mStartTime);
+ assertThat(stats.getStartTime()).isEqualTo(START_TIME);
assertThat(stats.getDuration()).isEqualTo(2000);
assertThat(powerComponentStats.getDeviceStats(values, new int[]{
- PowerStatsAggregator.POWER_STATE_OTHER,
- PowerStatsAggregator.SCREEN_STATE_ON}))
+ AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON}))
.isTrue();
assertThat(values).isEqualTo(new long[]{10000});
assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
- PowerStatsAggregator.POWER_STATE_OTHER,
- PowerStatsAggregator.SCREEN_STATE_ON,
+ AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
BatteryConsumer.PROCESS_STATE_FOREGROUND}))
.isTrue();
assertThat(values).isEqualTo(new long[]{1234});
} else if (mAggregatedStatsCount == 1) {
- assertThat(stats.getStartTime()).isEqualTo(mStartTime + 2000);
+ assertThat(stats.getStartTime()).isEqualTo(START_TIME + 2000);
assertThat(stats.getDuration()).isEqualTo(1000);
assertThat(powerComponentStats.getDeviceStats(values, new int[]{
- PowerStatsAggregator.POWER_STATE_BATTERY,
- PowerStatsAggregator.SCREEN_STATE_ON}))
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON}))
.isTrue();
assertThat(values).isEqualTo(new long[]{20000});
assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
- PowerStatsAggregator.POWER_STATE_BATTERY,
- PowerStatsAggregator.SCREEN_STATE_ON,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
BatteryConsumer.PROCESS_STATE_FOREGROUND}))
.isTrue();
assertThat(values).isEqualTo(new long[]{4444});
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
new file mode 100644
index 0000000..0e58787
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.BatteryConsumer;
+import android.os.BatteryManager;
+import android.os.BatteryUsageStats;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.MonotonicClock;
+import com.android.internal.os.PowerProfile;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+public class PowerStatsSchedulerTest {
+ private PowerStatsStore mPowerStatsStore;
+ private Handler mHandler;
+ private MockClock mClock = new MockClock();
+ private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
+ private MockBatteryStatsImpl mBatteryStats;
+ private BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+ private PowerStatsScheduler mPowerStatsScheduler;
+ private PowerProfile mPowerProfile;
+ private PowerStatsAggregator mPowerStatsAggregator;
+ private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+
+ @Before
+ public void setup() {
+ final Context context = InstrumentationRegistry.getContext();
+
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+
+ mClock.currentTime = Instant.parse("2023-01-02T03:04:05.00Z").toEpochMilli();
+ mClock.realtime = 7654321;
+
+ HandlerThread bgThread = new HandlerThread("bg thread");
+ bgThread.start();
+ File systemDir = context.getCacheDir();
+ mHandler = new Handler(bgThread.getLooper());
+ mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig();
+ mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, mAggregatedPowerStatsConfig);
+ mPowerProfile = mock(PowerProfile.class);
+ when(mPowerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT)).thenReturn(1000000.0);
+ mBatteryStats = new MockBatteryStatsImpl(mClock).setPowerProfile(mPowerProfile);
+ mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats);
+ mPowerStatsAggregator = mock(PowerStatsAggregator.class);
+ mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator,
+ TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), mPowerStatsStore, mClock,
+ mMonotonicClock, mHandler, mBatteryStats, mBatteryUsageStatsProvider);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void storeAggregatePowerStats() {
+ mPowerStatsStore.reset();
+
+ assertThat(mPowerStatsStore.getTableOfContents()).isEmpty();
+
+ mPowerStatsStore.storeAggregatedPowerStats(
+ createAggregatedPowerStats(mMonotonicClock.monotonicTime(), mClock.currentTime,
+ 123));
+
+ long delayBeforeAggregating = TimeUnit.MINUTES.toMillis(90);
+ mClock.realtime += delayBeforeAggregating;
+ mClock.currentTime += delayBeforeAggregating;
+
+ doAnswer(invocation -> {
+ // The first span is longer than 30 min, because the end time is being aligned with
+ // the wall clock. Subsequent spans should be precisely 30 minutes.
+ long startTime = invocation.getArgument(0);
+ long endTime = invocation.getArgument(1);
+ Consumer<AggregatedPowerStats> consumer = invocation.getArgument(2);
+
+ long startTimeWallClock =
+ mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime);
+ long endTimeWallClock =
+ mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime);
+
+ assertThat(startTime).isEqualTo(7654321 + 123);
+ assertThat(endTime - startTime).isAtLeast(TimeUnit.MINUTES.toMillis(30));
+ assertThat(Instant.ofEpochMilli(endTimeWallClock))
+ .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+
+ consumer.accept(
+ createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime));
+ return null;
+ }).doAnswer(invocation -> {
+ long startTime = invocation.getArgument(0);
+ long endTime = invocation.getArgument(1);
+ Consumer<AggregatedPowerStats> consumer = invocation.getArgument(2);
+
+ long startTimeWallClock =
+ mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime);
+ long endTimeWallClock =
+ mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime);
+
+ assertThat(Instant.ofEpochMilli(startTimeWallClock))
+ .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+ assertThat(Instant.ofEpochMilli(endTimeWallClock))
+ .isEqualTo(Instant.parse("2023-01-02T04:30:00Z"));
+
+ consumer.accept(
+ createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime));
+ return null;
+ }).when(mPowerStatsAggregator).aggregatePowerStats(anyLong(), anyLong(),
+ any(Consumer.class));
+
+ mPowerStatsScheduler.schedulePowerStatsAggregation();
+ ConditionVariable done = new ConditionVariable();
+ mHandler.post(done::open);
+ done.block();
+
+ verify(mPowerStatsAggregator, times(2))
+ .aggregatePowerStats(anyLong(), anyLong(), any(Consumer.class));
+
+ List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents();
+ assertThat(contents).hasSize(3);
+ // Skip the first entry, which was placed in the store at the beginning of this test
+ PowerStatsSpan.TimeFrame timeFrame1 = contents.get(1).getTimeFrames().get(0);
+ PowerStatsSpan.TimeFrame timeFrame2 = contents.get(2).getTimeFrames().get(0);
+ assertThat(timeFrame1.startMonotonicTime).isEqualTo(7654321 + 123);
+ assertThat(timeFrame2.startMonotonicTime)
+ .isEqualTo(timeFrame1.startMonotonicTime + timeFrame1.duration);
+ assertThat(Instant.ofEpochMilli(timeFrame2.startTime))
+ .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+ assertThat(Duration.ofMillis(timeFrame2.duration)).isEqualTo(Duration.ofMinutes(30));
+ }
+
+ private AggregatedPowerStats createAggregatedPowerStats(long monotonicTime, long currentTime,
+ long duration) {
+ AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig);
+ stats.addClockUpdate(monotonicTime, currentTime);
+ stats.setDuration(duration);
+ return stats;
+ }
+
+ @Test
+ public void storeBatteryUsageStatsOnReset() {
+ mBatteryStats.forceRecordAllHistory();
+ synchronized (mBatteryStats) {
+ mBatteryStats.setOnBatteryLocked(mClock.realtime, mClock.uptime, true,
+ BatteryManager.BATTERY_STATUS_DISCHARGING, 50, 0);
+ }
+
+ mPowerStatsScheduler.start(/* schedulePeriodicPowerStatsCollection */false);
+
+ assertThat(mPowerStatsStore.getTableOfContents()).isEmpty();
+
+ mPowerStatsScheduler.start(true);
+
+ synchronized (mBatteryStats) {
+ mBatteryStats.noteFlashlightOnLocked(42, mClock.realtime, mClock.uptime);
+ }
+
+ mClock.realtime += 60000;
+ mClock.currentTime += 60000;
+
+ synchronized (mBatteryStats) {
+ mBatteryStats.noteFlashlightOffLocked(42, mClock.realtime, mClock.uptime);
+ }
+
+ mClock.realtime += 60000;
+ mClock.currentTime += 60000;
+
+ // Battery stats reset should have the side-effect of saving accumulated battery usage stats
+ synchronized (mBatteryStats) {
+ mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
+ }
+
+ // Await completion
+ ConditionVariable done = new ConditionVariable();
+ mHandler.post(done::open);
+ done.block();
+
+ List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents();
+ assertThat(contents).hasSize(1);
+
+ PowerStatsSpan.Metadata metadata = contents.get(0);
+
+ PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
+ BatteryUsageStatsSection.TYPE);
+ assertThat(span).isNotNull();
+
+ List<PowerStatsSpan.TimeFrame> timeFrames = span.getMetadata().getTimeFrames();
+ assertThat(timeFrames).hasSize(1);
+ assertThat(timeFrames.get(0).startMonotonicTime).isEqualTo(7654321);
+ assertThat(timeFrames.get(0).duration).isEqualTo(120000);
+
+ List<PowerStatsSpan.Section> sections = span.getSections();
+ assertThat(sections).hasSize(1);
+
+ PowerStatsSpan.Section section = sections.get(0);
+ assertThat(section.getType()).isEqualTo(BatteryUsageStatsSection.TYPE);
+ BatteryUsageStats bus = ((BatteryUsageStatsSection) section).getBatteryUsageStats();
+ assertThat(bus.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+ .isEqualTo(60000);
+ }
+
+ @Test
+ public void alignToWallClock() {
+ // Expect the aligned value to be adjusted by 1 min 30 sec - rounded to the next 15 min
+ assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15),
+ 123 + TimeUnit.HOURS.toMillis(2),
+ Instant.parse("2007-12-03T10:13:30.00Z").toEpochMilli())).isEqualTo(
+ 123 + Duration.parse("PT1M30S").toMillis());
+
+ // Expect the aligned value to be adjusted by 2 min 45 sec - rounded to the next 15 min
+ assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15),
+ 123 + TimeUnit.HOURS.toMillis(2),
+ Instant.parse("2007-12-03T10:57:15.00Z").toEpochMilli())).isEqualTo(
+ 123 + Duration.parse("PT2M45S").toMillis());
+
+ // Expect the aligned value to be adjusted by 15 sec - rounded to the next 1 min
+ assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(1),
+ 123 + TimeUnit.HOURS.toMillis(2),
+ Instant.parse("2007-12-03T10:14:45.00Z").toEpochMilli())).isEqualTo(
+ 123 + Duration.parse("PT15S").toMillis());
+
+ // Expect the aligned value to be adjusted by 1 hour 46 min 30 sec -
+ // rounded to the next 3 hours
+ assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.HOURS.toMillis(3),
+ 123 + TimeUnit.HOURS.toMillis(9),
+ Instant.parse("2007-12-03T10:13:30.00Z").toEpochMilli())).isEqualTo(
+ 123 + Duration.parse("PT1H46M30S").toMillis());
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java
new file mode 100644
index 0000000..d3628b5
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SuppressWarnings("GuardedBy")
+public class PowerStatsStoreTest {
+ private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024;
+
+ private PowerStatsStore mPowerStatsStore;
+ private File mStoreDirectory;
+
+ @Before
+ public void setup() {
+ Context context = InstrumentationRegistry.getContext();
+
+ mStoreDirectory = new File(context.getCacheDir(), "PowerStatsStoreTest");
+ clearDirectory(mStoreDirectory);
+
+ mPowerStatsStore = new PowerStatsStore(mStoreDirectory,
+ MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES,
+ new TestHandler(),
+ (sectionType, parser) -> {
+ if (sectionType.equals(TestSection.TYPE)) {
+ return TestSection.readXml(parser);
+ }
+ return null;
+ });
+ }
+
+ @Test
+ public void garbageCollectOldSpans() throws Exception {
+ int spanSize = 500;
+ final int numberOfSnaps =
+ (int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / spanSize);
+ for (int i = 0; i < numberOfSnaps + 2; i++) {
+ PowerStatsSpan span = new PowerStatsSpan(i);
+ span.addSection(new TestSection(i, spanSize));
+ mPowerStatsStore.storePowerStatsSpan(span);
+ }
+
+ assertThat(getDirectorySize(mStoreDirectory))
+ .isAtMost(MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
+
+ List<PowerStatsSpan.Metadata> toc = mPowerStatsStore.getTableOfContents();
+ assertThat(toc.size()).isLessThan(numberOfSnaps);
+ int minPreservedSpanId = numberOfSnaps - toc.size();
+ for (PowerStatsSpan.Metadata metadata : toc) {
+ assertThat(metadata.getId()).isAtLeast(minPreservedSpanId);
+ }
+ }
+
+ @Test
+ public void reset() throws Exception {
+ for (int i = 0; i < 3; i++) {
+ PowerStatsSpan span = new PowerStatsSpan(i);
+ span.addSection(new TestSection(i, 42));
+ mPowerStatsStore.storePowerStatsSpan(span);
+ }
+
+ assertThat(getDirectorySize(mStoreDirectory)).isNotEqualTo(0);
+
+ mPowerStatsStore.reset();
+
+ assertThat(getDirectorySize(mStoreDirectory)).isEqualTo(0);
+ }
+
+ private void clearDirectory(File dir) {
+ if (dir.exists()) {
+ for (File child : dir.listFiles()) {
+ if (child.isDirectory()) {
+ clearDirectory(child);
+ }
+ child.delete();
+ }
+ }
+ }
+
+ private long getDirectorySize(File dir) {
+ long size = 0;
+ if (dir.exists()) {
+ for (File child : dir.listFiles()) {
+ if (child.isDirectory()) {
+ size += getDirectorySize(child);
+ } else {
+ size += child.length();
+ }
+ }
+ }
+ return size;
+ }
+
+ private static class TestSection extends PowerStatsSpan.Section {
+ public static final String TYPE = "much-text";
+
+ private final int mSize;
+ private final int mValue;
+
+ TestSection(int value, int size) {
+ super(TYPE);
+ mSize = size;
+ mValue = value;
+ }
+
+ @Override
+ void write(TypedXmlSerializer serializer) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < mSize; i++) {
+ sb.append("X");
+ }
+ serializer.startTag(null, "much-text");
+ serializer.attributeInt(null, "value", mValue);
+ serializer.text(sb.toString());
+ serializer.endTag(null, "much-text");
+ }
+
+ public static TestSection readXml(TypedXmlPullParser parser) throws XmlPullParserException {
+ TestSection section = null;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG
+ && parser.getName().equals("much-text"))) {
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals("much-text")) {
+ section = new TestSection(parser.getAttributeInt(null, "value"), 0);
+ }
+ }
+ return section;
+ }
+ }
+
+ private static class TestHandler extends Handler {
+ TestHandler() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+ msg.getCallback().run();
+ return true;
+ }
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index 2ffe4aa..df46054 100644
--- a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -1079,7 +1079,7 @@
GetSupportedPowerMonitorsResult result = new GetSupportedPowerMonitorsResult();
mService.getSupportedPowerMonitorsImpl(result);
assertThat(result.powerMonitors).isNotNull();
- assertThat(Arrays.stream(result.powerMonitors).map(pm -> pm.name).toList())
+ assertThat(Arrays.stream(result.powerMonitors).map(PowerMonitor::getName).toList())
.containsAtLeast(
"energyconsumer0",
"BLUETOOTH/1",
@@ -1130,7 +1130,7 @@
mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);
Map<String, PowerMonitor> map =
Arrays.stream(supportedPowerMonitorsResult.powerMonitors)
- .collect(Collectors.toMap(pm -> pm.name, pm -> pm));
+ .collect(Collectors.toMap(PowerMonitor::getName, pm -> pm));
PowerMonitor consumer1 = map.get("energyconsumer0");
PowerMonitor consumer2 = map.get("BLUETOOTH/1");
PowerMonitor measurement1 = map.get("[channelname0]:channelsubsystem0");
@@ -1196,6 +1196,6 @@
supportedPowerMonitorsResult = new GetSupportedPowerMonitorsResult();
mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);
assertThat(Arrays.stream(supportedPowerMonitorsResult.powerMonitors)
- .map(pm -> pm.name).toList()).contains("energyconsumer0");
+ .map(PowerMonitor::getName).toList()).contains("energyconsumer0");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 49f22ec..b9e45ba 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.accessibility;
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
@@ -33,6 +34,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
@@ -53,14 +55,21 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManagerGlobal;
+import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.LocaleList;
import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.testing.TestableContext;
+import android.util.ArraySet;
import android.view.Display;
import android.view.DisplayAdjustments;
import android.view.DisplayInfo;
@@ -93,8 +102,13 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
/**
* APCT tests for {@link AccessibilityManagerService}.
@@ -104,6 +118,10 @@
public final A11yTestableContext mTestableContext = new A11yTestableContext(
ApplicationProvider.getApplicationContext());
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final int ACTION_ID = 20;
private static final String LABEL = "label";
private static final String INTENT_ACTION = "TESTACTION";
@@ -204,6 +222,8 @@
mA11yms.getCurrentUserIdLocked());
when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
mMockResolveInfo.serviceInfo = mock(ServiceInfo.class);
+ mMockResolveInfo.serviceInfo.packageName = "packageName";
+ mMockResolveInfo.serviceInfo.name = "className";
mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class);
when(mMockBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient);
@@ -581,6 +601,141 @@
ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
}
+ @Test
+ public void testPackagesForceStopped_disablesRelevantService() {
+ final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
+ info_a.setComponentName(COMPONENT_NAME);
+ final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo();
+ info_b.setComponentName(new ComponentName("package", "class"));
+
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.clear();
+ userState.mInstalledServices.add(info_a);
+ userState.mInstalledServices.add(info_b);
+ userState.mEnabledServices.clear();
+ userState.mEnabledServices.add(info_a.getComponentName());
+ userState.mEnabledServices.add(info_b.getComponentName());
+
+ synchronized (mA11yms.getLock()) {
+ mA11yms.onPackagesForceStoppedLocked(
+ new String[]{info_a.getComponentName().getPackageName()}, userState);
+ }
+
+ //Assert user state change
+ userState = mA11yms.getCurrentUserState();
+ assertThat(userState.mEnabledServices).containsExactly(info_b.getComponentName());
+ //Assert setting change
+ final Set<ComponentName> componentsFromSetting = new ArraySet<>();
+ mA11yms.readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userState.mUserId, componentsFromSetting);
+ assertThat(componentsFromSetting).containsExactly(info_b.getComponentName());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DISABLE_CONTINUOUS_SHORTCUT_ON_FORCE_STOP)
+ public void testPackagesForceStopped_fromContinuousService_removesButtonTarget() {
+ final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
+ info_a.setComponentName(COMPONENT_NAME);
+ info_a.flags = FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+ final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo();
+ info_b.setComponentName(new ComponentName("package", "class"));
+
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.clear();
+ userState.mInstalledServices.add(info_a);
+ userState.mInstalledServices.add(info_b);
+ userState.mAccessibilityButtonTargets.clear();
+ userState.mAccessibilityButtonTargets.add(info_a.getComponentName().flattenToString());
+ userState.mAccessibilityButtonTargets.add(info_b.getComponentName().flattenToString());
+
+ // despite force stopping both packages, only the first service has the relevant flag,
+ // so only the first should be removed.
+ synchronized (mA11yms.getLock()) {
+ mA11yms.onPackagesForceStoppedLocked(
+ new String[]{
+ info_a.getComponentName().getPackageName(),
+ info_b.getComponentName().getPackageName()},
+ userState);
+ }
+
+ //Assert user state change
+ userState = mA11yms.getCurrentUserState();
+ assertThat(userState.mAccessibilityButtonTargets).containsExactly(
+ info_b.getComponentName().flattenToString());
+ //Assert setting change
+ final Set<String> targetsFromSetting = new ArraySet<>();
+ mA11yms.readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+ userState.mUserId, str -> str, targetsFromSetting);
+ assertThat(targetsFromSetting).containsExactly(info_b.getComponentName().flattenToString());
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
+ // Test old behavior to validate lock detection for the old (locked access) case.
+ public void testPackageMonitorScanPackages_scansWhileHoldingLock() {
+ setupAccessibilityServiceConnection(0);
+ final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
+ when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(List.of(mMockResolveInfo));
+ when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
+
+ final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
+ packageIntent.setData(Uri.parse("test://package"));
+ packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked());
+ packageIntent.putExtra(Intent.EXTRA_REPLACING, true);
+ mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent);
+
+ assertThat(lockState.get()).containsExactly(true);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
+ public void testPackageMonitorScanPackages_scansWithoutHoldingLock() {
+ setupAccessibilityServiceConnection(0);
+ final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
+ when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(List.of(mMockResolveInfo));
+ when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
+
+ final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
+ packageIntent.setData(Uri.parse("test://package"));
+ packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked());
+ packageIntent.putExtra(Intent.EXTRA_REPLACING, true);
+ mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent);
+
+ assertThat(lockState.get()).containsExactly(false);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
+ public void testSwitchUserScanPackages_scansWithoutHoldingLock() {
+ setupAccessibilityServiceConnection(0);
+ final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
+ when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(List.of(mMockResolveInfo));
+ when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
+
+ mA11yms.switchUser(mA11yms.getCurrentUserIdLocked() + 1);
+
+ assertThat(lockState.get()).containsExactly(false);
+ }
+
+ // Single package intents can trigger multiple PackageMonitor callbacks.
+ // Collect the state of the lock in a set, since tests only care if calls
+ // were all locked or all unlocked.
+ private AtomicReference<Set<Boolean>> collectLockStateWhilePackageScanning() {
+ final AtomicReference<Set<Boolean>> lockState =
+ new AtomicReference<>(new HashSet<Boolean>());
+ doAnswer((Answer<XmlResourceParser>) invocation -> {
+ lockState.updateAndGet(set -> {
+ set.add(mA11yms.unsafeIsLockHeld());
+ return set;
+ });
+ return null;
+ }).when(mMockResolveInfo.serviceInfo).loadXmlMetaData(any(), any());
+ return lockState;
+ }
+
private void mockManageAccessibilityGranted(TestableContext context) {
context.getTestablePermissions().setPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
PackageManager.PERMISSION_GRANTED);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 63281b7..71007f5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -155,7 +155,7 @@
mUserState.mAccessibilityButtonTargets.add(COMPONENT_NAME.flattenToString());
mUserState.setTargetAssignedToAccessibilityButton(COMPONENT_NAME.flattenToString());
mUserState.setTouchExplorationEnabledLocked(true);
- mUserState.setDisplayMagnificationEnabledLocked(true);
+ mUserState.setMagnificationSingleFingerTripleTapEnabledLocked(true);
mUserState.setAutoclickEnabledLocked(true);
mUserState.setUserNonInteractiveUiTimeoutLocked(30);
mUserState.setUserInteractiveUiTimeoutLocked(30);
@@ -177,7 +177,7 @@
assertTrue(mUserState.mAccessibilityButtonTargets.isEmpty());
assertNull(mUserState.getTargetAssignedToAccessibilityButton());
assertFalse(mUserState.isTouchExplorationEnabledLocked());
- assertFalse(mUserState.isDisplayMagnificationEnabledLocked());
+ assertFalse(mUserState.isMagnificationSingleFingerTripleTapEnabledLocked());
assertFalse(mUserState.isAutoclickEnabledLocked());
assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked());
assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 1cd61e9..efcdbd4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -44,6 +44,10 @@
import android.graphics.PointF;
import android.os.Looper;
import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.DexmakerShareClassLoaderRule;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -56,6 +60,7 @@
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.EventStreamTransformation;
+import com.android.server.accessibility.Flags;
import com.android.server.accessibility.utils.GestureLogParser;
import com.android.server.testutils.OffsettableClock;
@@ -76,6 +81,7 @@
import java.util.ArrayList;
import java.util.List;
+
@RunWith(AndroidJUnit4.class)
public class TouchExplorerTest {
@@ -119,6 +125,9 @@
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
/**
* {@link TouchExplorer#sendDownForAllNotInjectedPointers} injecting events with the same object
* is resulting {@link ArgumentCaptor} to capture events with last state. Before implementation
@@ -161,11 +170,16 @@
goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
// Wait for transiting to touch exploring state.
mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
- moveEachPointers(mLastEvent, p(10, 10));
- send(mLastEvent);
+ assertState(STATE_TOUCH_EXPLORING);
+ // Manually construct the next move event. Using moveEachPointers() will batch the move
+ // event which produces zero movement for some reason.
+ float[] x = new float[1];
+ float[] y = new float[1];
+ x[0] = mLastEvent.getX(0) + mTouchSlop;
+ y[0] = mLastEvent.getY(0) + mTouchSlop;
+ send(manyPointerEvent(ACTION_MOVE, x, y));
goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER);
assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
- assertState(STATE_TOUCH_EXPLORING);
}
/**
@@ -173,7 +187,8 @@
* change the coordinates.
*/
@Test
- public void testOneFingerMoveWithExtraMoveEvents() {
+ @RequiresFlagsEnabled(Flags.FLAG_REDUCE_TOUCH_EXPLORATION_SENSITIVITY)
+ public void testOneFingerMoveWithExtraMoveEvents_generatesOneMoveEvent() {
goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
// Inject a set of move events that have the same coordinates as the down event.
moveEachPointers(mLastEvent, p(0, 0));
@@ -181,7 +196,33 @@
// Wait for transition to touch exploring state.
mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
// Now move for real.
- moveEachPointers(mLastEvent, p(10, 10));
+ moveAtLeastTouchSlop(mLastEvent);
+ send(mLastEvent);
+ // One more move event with no change.
+ moveEachPointers(mLastEvent, p(0, 0));
+ send(mLastEvent);
+ goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER);
+ assertCapturedEvents(
+ ACTION_HOVER_ENTER,
+ ACTION_HOVER_MOVE,
+ ACTION_HOVER_EXIT);
+ }
+
+ /**
+ * Test the case where ACTION_DOWN is followed by a number of ACTION_MOVE events that do not
+ * change the coordinates.
+ */
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_REDUCE_TOUCH_EXPLORATION_SENSITIVITY)
+ public void testOneFingerMoveWithExtraMoveEvents_generatesThreeMoveEvent() {
+ goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
+ // Inject a set of move events that have the same coordinates as the down event.
+ moveEachPointers(mLastEvent, p(0, 0));
+ send(mLastEvent);
+ // Wait for transition to touch exploring state.
+ mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
+ // Now move for real.
+ moveAtLeastTouchSlop(mLastEvent);
send(mLastEvent);
// One more move event with no change.
moveEachPointers(mLastEvent, p(0, 0));
@@ -242,7 +283,7 @@
moveEachPointers(mLastEvent, p(0, 0), p(0, 0));
send(mLastEvent);
// Now move for real.
- moveEachPointers(mLastEvent, p(10, 10), p(10, 10));
+ moveEachPointers(mLastEvent, p(mTouchSlop, mTouchSlop), p(mTouchSlop, mTouchSlop));
send(mLastEvent);
goToStateClearFrom(STATE_DRAGGING_2FINGERS);
assertCapturedEvents(ACTION_DOWN, ACTION_MOVE, ACTION_MOVE, ACTION_MOVE, ACTION_UP);
@@ -251,7 +292,7 @@
@Test
public void testUpEvent_OneFingerMove_clearStateAndInjectHoverEvents() {
goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
- moveEachPointers(mLastEvent, p(10, 10));
+ moveAtLeastTouchSlop(mLastEvent);
send(mLastEvent);
// Wait 10 ms to make sure that hover enter and exit are not scheduled for the same moment.
mHandler.fastForward(10);
@@ -277,7 +318,7 @@
// Wait for the finger moving to the second view.
mHandler.fastForward(oneThirdUserIntentTimeout);
- moveEachPointers(mLastEvent, p(10, 10));
+ moveAtLeastTouchSlop(mLastEvent);
send(mLastEvent);
// Wait for the finger lifting from the second view.
@@ -402,7 +443,6 @@
// Manually construct the next move event. Using moveEachPointers() will batch the move
// event onto the pointer up event which will mean that the move event still has a pointer
// count of 3.
- // Todo: refactor to avoid using batching as there is no special reason to do it that way.
float[] x = new float[2];
float[] y = new float[2];
x[0] = mLastEvent.getX(0) + 100;
@@ -734,6 +774,9 @@
}
}
+ private void moveAtLeastTouchSlop(MotionEvent event) {
+ moveEachPointers(event, p(2 * mTouchSlop, 0));
+ }
/**
* A {@link android.os.Handler} that doesn't process messages until {@link #fastForward(int)} is
* invoked.
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
index 70527ce..44d6760 100644
--- a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
@@ -238,7 +238,7 @@
}
@Override
- Handler getHandler(Handler.Callback callback) {
+ Handler newHandler(Handler.Callback callback) {
if (mTestHandler == null) {
mTestHandler = new TestHandler(mHandler.getLooper(), callback, mImmediate);
}
@@ -250,14 +250,18 @@
return mTestHandler;
}
+ /**
+ * This override returns the tracker supplied in the constructor. It does not create a
+ * new one.
+ */
@Override
- AnrTimer.CpuTracker getTracker() {
+ AnrTimer.CpuTracker newTracker() {
return mTracker;
}
/** For test purposes, always enable the feature. */
@Override
- boolean getFeatureEnabled() {
+ boolean isFeatureEnabled() {
return true;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
index 988cd81..feb6bd9 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertTrue;
import android.content.Context;
@@ -34,6 +36,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -49,8 +52,9 @@
final Context context = InstrumentationRegistry.getContext();
mBgThread = new HandlerThread("bg thread");
mBgThread.start();
- mBatteryStatsService = new BatteryStatsService(context,
- context.getCacheDir(), new Handler(mBgThread.getLooper()));
+ File systemDir = context.getCacheDir();
+ Handler handler = new Handler(mBgThread.getLooper());
+ mBatteryStatsService = new BatteryStatsService(context, systemDir, handler);
}
@After
@@ -121,4 +125,14 @@
waitThread.join(1000);
}
}
+
+ @Test
+ public void testSavingStatsdAtomPullTimestamp() {
+ mBatteryStatsService.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234);
+ assertThat(mBatteryStatsService.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
+ .isEqualTo(1234);
+ mBatteryStatsService.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(5478);
+ assertThat(mBatteryStatsService.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
+ .isEqualTo(5478);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 9e5a047..3a3dd6e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -36,6 +36,7 @@
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.content.ComponentName;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.common.AuthenticateReason;
import android.hardware.biometrics.common.ICancellationSignal;
@@ -59,6 +60,7 @@
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.face.UsageStats;
import org.junit.Before;
@@ -103,7 +105,7 @@
@Mock
private ClientMonitorCallback mCallback;
@Mock
- private Sensor.HalSessionCallback mHalSessionCallback;
+ private AidlResponseHandler mAidlResponseHandler;
@Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
@@ -112,6 +114,8 @@
private AuthSessionCoordinator mAuthSessionCoordinator;
@Mock
private BiometricManager mBiometricManager;
+ @Mock
+ private LockoutTracker mLockoutTracker;
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
@Captor
@@ -236,27 +240,63 @@
verify(mCallback).onClientFinished(client, true);
}
+ @Test
+ public void authWithNoLockout() throws RemoteException {
+ when(mLockoutTracker.getLockoutModeForUser(anyInt())).thenReturn(
+ LockoutTracker.LOCKOUT_NONE);
+
+ final FaceAuthenticationClient client = createClientWithLockoutTracker(mLockoutTracker);
+ client.start(mCallback);
+
+ verify(mHal).authenticate(OP_ID);
+ }
+
+ @Test
+ public void authWithLockout() throws RemoteException {
+ when(mLockoutTracker.getLockoutModeForUser(anyInt())).thenReturn(
+ LockoutTracker.LOCKOUT_PERMANENT);
+
+ final FaceAuthenticationClient client = createClientWithLockoutTracker(mLockoutTracker);
+ client.start(mCallback);
+
+ verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
+ eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT), anyInt());
+ verify(mHal, never()).authenticate(anyInt());
+ }
+
private FaceAuthenticationClient createClient() throws RemoteException {
return createClient(2 /* version */, mClientMonitorCallbackConverter,
- false /* allowBackgroundAuthentication */);
+ false /* allowBackgroundAuthentication */,
+ null /* lockoutTracker */);
}
private FaceAuthenticationClient createClientWithNullListener() throws RemoteException {
return createClient(2 /* version */, null /* listener */,
- true /* allowBackgroundAuthentication */);
+ true /* allowBackgroundAuthentication */,
+ null /* lockoutTracker */);
}
private FaceAuthenticationClient createClient(int version) throws RemoteException {
return createClient(version, mClientMonitorCallbackConverter,
- false /* allowBackgroundAuthentication */);
+ false /* allowBackgroundAuthentication */,
+ null /* lockoutTracker */);
+ }
+
+ private FaceAuthenticationClient createClientWithLockoutTracker(LockoutTracker lockoutTracker)
+ throws RemoteException {
+ return createClient(0 /* version */,
+ mClientMonitorCallbackConverter,
+ true /* allowBackgroundAuthentication */,
+ lockoutTracker);
}
private FaceAuthenticationClient createClient(int version,
ClientMonitorCallbackConverter listener,
- boolean allowBackgroundAuthentication) throws RemoteException {
+ boolean allowBackgroundAuthentication,
+ LockoutTracker lockoutTracker) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
- final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
final FaceAuthenticateOptions options = new FaceAuthenticateOptions.Builder()
.setOpPackageName("test-owner")
.setUserId(USER_ID)
@@ -270,7 +310,7 @@
false /* restricted */, options, 4 /* cookie */,
false /* requireConfirmation */,
mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
- mUsageStats, null /* mLockoutCache */, allowBackgroundAuthentication,
+ mUsageStats, lockoutTracker, allowBackgroundAuthentication,
null /* sensorPrivacyManager */, 0 /* biometricStrength */) {
@Override
protected ActivityTaskManager getActivityTaskManager() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
index ade3e82..fbf0e13 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -87,7 +87,7 @@
@Mock
private ClientMonitorCallback mCallback;
@Mock
- private Sensor.HalSessionCallback mHalSessionCallback;
+ private AidlResponseHandler mAidlResponseHandler;
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
@Captor
@@ -170,7 +170,7 @@
private FaceDetectClient createClient(int version) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
- final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
return new FaceDetectClient(mContext, () -> aidl, mToken,
99 /* requestId */, mClientMonitorCallbackConverter,
new FaceAuthenticateOptions.Builder()
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
index 54d116f..128f314 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
@@ -83,7 +83,7 @@
@Mock
private ClientMonitorCallback mCallback;
@Mock
- private Sensor.HalSessionCallback mHalSessionCallback;
+ private AidlResponseHandler mAidlResponseHandler;
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
@Captor
@@ -150,7 +150,7 @@
private FaceEnrollClient createClient(int version) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
- final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
return new FaceEnrollClient(mContext, () -> aidl, mToken, mClientMonitorCallbackConverter,
USER_ID, HAT, "com.foo.bar", 44 /* requestId */,
mUtils, new int[0] /* disabledFeatures */, 6 /* timeoutSec */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
new file mode 100644
index 0000000..c8bfaa9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceGenerateChallengeClientTest {
+ private static final String TAG = "FaceGenerateChallengeClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+ private static final long CHALLENGE = 200;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mListener;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+
+ private FaceGenerateChallengeClient mClient;
+
+ @Before
+ public void setUp() throws RemoteException {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+ doAnswer(invocation -> {
+ mClient.onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE);
+ return null;
+ }).when(mSession).generateChallenge();
+ }
+
+ @Test
+ public void generateChallenge() throws RemoteException {
+ createClient(mListener);
+ mClient.start(mCallback);
+
+ verify(mListener).onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void generateChallenge_nullListener() {
+ createClient(null);
+ mClient.start(mCallback);
+
+ verify(mCallback).onClientFinished(mClient, false);
+ }
+
+ private void createClient(ClientMonitorCallbackConverter listener) {
+ mClient = new FaceGenerateChallengeClient(mContext, () -> mAidlSession, mToken, listener,
+ USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java
new file mode 100644
index 0000000..9d0c84e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FaceGetFeatureClientTest {
+ private static final String TAG = "FaceGetFeatureClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mListener;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext());
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+
+ private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION;
+ private FaceGetFeatureClient mClient;
+
+ @Before
+ public void setUp() throws RemoteException {
+ mClient = new FaceGetFeatureClient(mContext, () -> mAidlSession, mToken, mListener,
+ USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, mFeature);
+
+ when(mAidlSession.getSession()).thenReturn(mSession);
+ doAnswer(invocation -> {
+ mClient.onFeatureGet(true, new byte[]{
+ AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)});
+ return null;
+ }).when(mSession).getFeatures();
+ }
+
+ @Test
+ public void getFeature() throws RemoteException {
+ ArgumentCaptor<int[]> featuresToSend = ArgumentCaptor.forClass(int[].class);
+ ArgumentCaptor<boolean[]> featureState = ArgumentCaptor.forClass(boolean[].class);
+ mClient.start(mCallback);
+
+ verify(mListener).onFeatureGet(eq(true), featuresToSend.capture(),
+ featureState.capture());
+ assertThat(featuresToSend.getValue()).asList().containsExactlyElementsIn(List.of(mFeature));
+ assertThat(featureState.getValue()).asList().containsExactlyElementsIn(List.of(true));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java
new file mode 100644
index 0000000..1b4c017
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.face.Face;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Presubmit
+@SmallTest
+public class FaceInternalCleanupClientTest {
+ private static final String TAG = "FaceInternalCleanupClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricUtils<Face> mBiometricUtils;
+ @Mock
+ private Map<Integer, Long> mAuthenticatorIds;
+
+ private final List<Face> mEnrolledList = new ArrayList<>();
+ private final int mBiometricId = 1;
+ private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */);
+ private FaceInternalCleanupClient mClient;
+ private List<Integer> mAddedIds;
+
+ @Before
+ public void setUp() throws RemoteException {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ mEnrolledList.add(mFace);
+ mAddedIds = new ArrayList<>();
+ mClient = new FaceInternalCleanupClient(mContext, () -> mAidlSession, USER_ID, TAG,
+ SENSOR_ID, mBiometricLogger, mBiometricContext, mBiometricUtils,
+ mAuthenticatorIds) {
+ @Override
+ protected void onAddUnknownTemplate(int userId,
+ @NonNull BiometricAuthenticator.Identifier identifier) {
+ mAddedIds.add(identifier.getBiometricId());
+ }
+ };
+ }
+
+ @Test
+ public void removesUnknownTemplate() throws Exception {
+ final List<Face> templates = List.of(
+ new Face("one", 1, 1),
+ new Face("two", 2, 1)
+ );
+ mClient.start(mCallback);
+ for (int i = templates.size() - 1; i >= 0; i--) {
+ mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
+ }
+ for (int i = templates.size() - 1; i >= 0; i--) {
+ mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0);
+ }
+
+ assertThat(mAddedIds).isEmpty();
+ final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class);
+
+ verify(mSession, times(2)).removeEnrollments(captor.capture());
+ assertThat(captor.getAllValues().stream()
+ .flatMap(x -> Arrays.stream(x).boxed())
+ .collect(Collectors.toList()))
+ .containsExactly(1, 2);
+ verify(mCallback).onClientFinished(eq(mClient), eq(true));
+ }
+
+ @Test
+ public void addsUnknownTemplateWhenVirtualIsEnabled() throws Exception {
+ mClient.setFavorHalEnrollments();
+ final List<Face> templates = List.of(
+ new Face("one", 1, 1),
+ new Face("two", 2, 1)
+ );
+ mClient.start(mCallback);
+ for (int i = templates.size() - 1; i >= 0; i--) {
+ mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
+ }
+
+ assertThat(mAddedIds).containsExactly(1, 2);
+ verify(mSession, never()).removeEnrollments(any());
+ verify(mCallback).onClientFinished(eq(mClient), eq(true));
+ }
+
+ @Test
+ public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() {
+ final List<Face> templates = List.of(
+ new Face("one", 1, 1),
+ new Face("two", 2, 1),
+ new Face("three", 3, 1)
+ );
+ mClient.start(mCallback);
+ for (int i = templates.size() - 1; i >= 0; i--) {
+ mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
+ }
+
+ // The first template is removed after enumeration
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(2);
+
+ // Simulate finishing the removal of the first template.
+ // |remaining| is 0 because one FaceRemovalClient is associated with only one
+ // biometrics ID.
+ mClient.getCurrentRemoveClient().onRemoved(templates.get(0), 0);
+
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
+
+ // Simulate finishing the removal of the second template.
+ mClient.getCurrentRemoveClient().onRemoved(templates.get(1), 0);
+
+ assertThat(mClient.getUnknownHALTemplates()).isEmpty();
+ }
+
+ @Test
+ public void noUnknownTemplates() throws RemoteException {
+ mClient.start(mCallback);
+ mClient.getCurrentEnumerateClient().onEnumerationResult(null, 0);
+
+ verify(mSession).enumerateEnrollments();
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+ verify(mSession, never()).removeEnrollments(any());
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java
new file mode 100644
index 0000000..8d74fd1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.face.Face;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FaceInternalEnumerateClientTest {
+ private static final String TAG = "FaceInternalEnumerateClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricUtils<Face> mBiometricUtils;
+
+ private final int mBiometricId = 1;
+ private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */);
+ private FaceInternalEnumerateClient mClient;
+
+ @Before
+ public void setUp() {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ final List<Face> enrolled = new ArrayList<>();
+ enrolled.add(mFace);
+ mClient = new FaceInternalEnumerateClient(mContext, () -> mAidlSession, mToken, USER_ID,
+ TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, mBiometricContext);
+ }
+
+ @Test
+ public void internalCleanupClient_noTemplatesRemaining() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onEnumerationResult(mFace, 0);
+ return null;
+ }).when(mSession).enumerateEnrollments();
+
+ mClient.start(mCallback);
+
+ verify(mSession).enumerateEnrollments();
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+ verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void internalCleanupClient_nullIdentifier_remainingOne() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onEnumerationResult(null, 1);
+ return null;
+ }).when(mSession).enumerateEnrollments();
+
+ mClient.start(mCallback);
+
+ verify(mSession).enumerateEnrollments();
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+ verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+ verify(mCallback, never()).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void internalCleanupClient_nullIdentifier_noTemplatesRemaining() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onEnumerationResult(null, 0);
+ return null;
+ }).when(mSession).enumerateEnrollments();
+
+ mClient.start(mCallback);
+
+ verify(mSession).enumerateEnrollments();
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+ verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void internalCleanupClient_templatesRemaining() throws RemoteException {
+ final Face identifier = new Face("face", 2, 1);
+ doAnswer(invocation -> {
+ mClient.onEnumerationResult(identifier, 1);
+ return null;
+ }).when(mSession).enumerateEnrollments();
+
+ mClient.start(mCallback);
+
+ verify(mSession).enumerateEnrollments();
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
+ verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+ verify(mCallback, never()).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void internalCleanupClient_differentIdentifier_noTemplatesRemaining()
+ throws RemoteException {
+ final Face identifier = new Face("face", 2, 1);
+ doAnswer(invocation -> {
+ mClient.onEnumerationResult(identifier, 0);
+ return null;
+ }).when(mSession).enumerateEnrollments();
+
+ mClient.start(mCallback);
+
+ verify(mSession).enumerateEnrollments();
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
+ verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
index 76a5acc..1d9e933 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
@@ -76,7 +76,7 @@
@Mock
private ClientMonitorCallback mCallback;
@Mock
- private Sensor.HalSessionCallback mHalSessionCallback;
+ private AidlResponseHandler mAidlResponseHandler;
@Mock
private BiometricUtils<Face> mUtils;
@Mock
@@ -115,7 +115,7 @@
private FaceRemovalClient createClient(int version, int[] biometricIds) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
- final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
return new FaceRemovalClient(mContext, () -> aidl, mToken,
mClientMonitorCallbackConverter, biometricIds, USER_ID,
"own-it", mUtils /* utils */, 5 /* sensorId */, mBiometricLogger, mBiometricContext,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java
new file mode 100644
index 0000000..dbbd69b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutCache;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceResetLockoutClientTest {
+ private static final String TAG = "FaceResetLockoutClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ private final byte[] mHardwareAuthToken = new byte[69];
+ @Mock
+ private LockoutCache mLockoutTracker;
+ @Mock
+ private LockoutResetDispatcher mLockoutResetDispatcher;
+ @Mock
+ private AuthSessionCoordinator mAuthSessionCoordinator;
+
+ private FaceResetLockoutClient mClient;
+
+ @Before
+ public void setUp() {
+ mClient = new FaceResetLockoutClient(mContext, () -> mAidlSession, USER_ID, TAG, SENSOR_ID,
+ mBiometricLogger, mBiometricContext, mHardwareAuthToken, mLockoutTracker,
+ mLockoutResetDispatcher, BIOMETRIC_STRONG);
+
+ when(mAidlSession.getSession()).thenReturn(mSession);
+ when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+ }
+
+ @Test
+ public void testResetLockout_onLockoutCleared() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onLockoutCleared();
+ return null;
+ }).when(mSession).resetLockout(any());
+ mClient.start(mCallback);
+
+ verify(mSession).resetLockout(any());
+ verify(mAuthSessionCoordinator).resetLockoutFor(USER_ID, BIOMETRIC_STRONG, -1);
+ verify(mLockoutTracker).setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_NONE);
+ verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void testResetLockout_onError() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onError(0, 0);
+ return null;
+ }).when(mSession).resetLockout(any());
+ mClient.start(mCallback);
+
+ verify(mSession).resetLockout(any());
+ verify(mAuthSessionCoordinator, never()).resetLockoutFor(USER_ID,
+ BIOMETRIC_STRONG, -1);
+ verify(mLockoutTracker, never()).setLockoutModeForUser(USER_ID,
+ LockoutTracker.LOCKOUT_NONE);
+ verify(mLockoutResetDispatcher, never()).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mCallback).onClientFinished(mClient, false);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java
new file mode 100644
index 0000000..fb5502a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceRevokeChallengeClientTest {
+ private static final String TAG = "FaceRevokeChallengeClientTest";
+ private static final long CHALLENGE = 200L;
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+
+ private FaceRevokeChallengeClient mClient;
+
+ @Before
+ public void setUp() {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ mClient = new FaceRevokeChallengeClient(mContext, () -> mAidlSession, mToken, USER_ID, TAG,
+ SENSOR_ID, mBiometricLogger, mBiometricContext, CHALLENGE);
+ }
+
+ @Test
+ public void revokeChallenge() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onChallengeRevoked(SENSOR_ID, USER_ID, CHALLENGE);
+ return null;
+ }).when(mSession).revokeChallenge(CHALLENGE);
+ mClient.start(mCallback);
+
+ verify(mSession).revokeChallenge(CHALLENGE);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java
new file mode 100644
index 0000000..eb8cc9c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceSetFeatureClientTest {
+ private static final String TAG = "FaceSetFeatureClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mListener;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+
+ private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION;
+ private final boolean mEnabled = true;
+ private final byte[] mHardwareAuthToken = new byte[69];
+ private FaceSetFeatureClient mClient;
+ TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext());
+
+ @Before
+ public void setUp() {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ mClient = new FaceSetFeatureClient(mContext, () -> mAidlSession, mToken, mListener, USER_ID,
+ TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, mFeature, mEnabled,
+ mHardwareAuthToken);
+ }
+
+ @Test
+ public void setFeature_onFeatureSet() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onFeatureSet(true);
+ return null;
+ }).when(mSession).setFeature(any(),
+ eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), eq(mEnabled));
+ mClient.start(mCallback);
+
+ verify(mSession).setFeature(any(),
+ eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)),
+ eq(mEnabled));
+ verify(mListener).onFeatureSet(true, mFeature);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void setFeature_onError() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onError(0, 0);
+ return null;
+ }).when(mSession).setFeature(any(),
+ eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)),
+ eq(mEnabled));
+ mClient.start(mCallback);
+
+ verify(mSession).setFeature(any(),
+ eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)),
+ eq(mEnabled));
+ verify(mListener).onFeatureSet(false, mFeature);
+ verify(mCallback).onClientFinished(mClient, false);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index be9f52e..7a293e8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -74,7 +74,7 @@
@Mock
private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
@Mock
- private Sensor.HalSessionCallback.Callback mHalSessionCallback;
+ private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
@Mock
private LockoutResetDispatcher mLockoutResetDispatcher;
@Mock
@@ -94,7 +94,7 @@
private final LockoutCache mLockoutCache = new LockoutCache();
private UserAwareBiometricScheduler mScheduler;
- private Sensor.HalSessionCallback mHalCallback;
+ private AidlResponseHandler mHalCallback;
@Before
public void setUp() {
@@ -111,10 +111,9 @@
mBiometricService,
() -> USER_ID,
mUserSwitchCallback);
- mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
- TAG, mScheduler, SENSOR_ID,
- USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- mHalSessionCallback);
+ mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
+ mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
+ mHardwareUnavailableCallback);
}
@Test
@@ -153,11 +152,11 @@
sensorProps.commonProps.sensorId = 1;
final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
- sensorProps.commonProps.maxEnrollmentsPerUser, null,
+ sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */,
sensorProps.sensorType, sensorProps.supportsDetectInteraction,
sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
- final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
- internalProp, mLockoutResetDispatcher, mBiometricContext);
+ final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext,
+ null /* handler */, internalProp, mLockoutResetDispatcher, mBiometricContext);
mScheduler.reset();
@@ -181,7 +180,7 @@
sensorProps.commonProps.sensorId = 1;
final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
- sensorProps.commonProps.maxEnrollmentsPerUser, null,
+ sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */,
sensorProps.sensorType, sensorProps.supportsDetectInteraction,
sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
new file mode 100644
index 0000000..9a40e8a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
@@ -0,0 +1,342 @@
+/*
+ * 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.biometrics.sensors.face.hidl;
+
+import static com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter.ENROLL_TIMEOUT_SEC;
+import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.face.EnrollmentType;
+import android.hardware.biometrics.face.Feature;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.OptionalBool;
+import android.hardware.biometrics.face.V1_0.OptionalUint64;
+import android.hardware.biometrics.face.V1_0.Status;
+import android.hardware.face.Face;
+import android.hardware.face.FaceManager;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.hardware.keymaster.Timestamp;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlConversionUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class AidlToHidlAdapterTest {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private IBiometricsFace mSession;
+ @Mock
+ FaceManager mFaceManager;
+ @Mock
+ private AidlResponseHandler mAidlResponseHandler;
+ @Mock
+ private HardwareAuthToken mHardwareAuthToken;
+ @Mock
+ private Clock mClock;
+
+ private final long mChallenge = 100L;
+ private AidlToHidlAdapter mAidlToHidlAdapter;
+ private final Face mFace = new Face("face" /* name */, 1 /* faceId */, 0 /* deviceId */);
+ private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_REQUIRE_DIVERSITY;
+ private final byte[] mFeatures = new byte[]{Feature.REQUIRE_ATTENTION};
+
+ @Before
+ public void setUp() throws RemoteException {
+ TestableContext testableContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext());
+ testableContext.addMockSystemService(FaceManager.class, mFaceManager);
+ mAidlToHidlAdapter = new AidlToHidlAdapter(testableContext, () -> mSession, 0 /* userId */,
+ mAidlResponseHandler, mClock);
+ mHardwareAuthToken.timestamp = new Timestamp();
+ mHardwareAuthToken.mac = new byte[10];
+ final OptionalUint64 result = new OptionalUint64();
+ result.status = Status.OK;
+ result.value = mChallenge;
+
+ when(mSession.generateChallenge(anyInt())).thenReturn(result);
+ when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(List.of(mFace));
+ }
+
+ @Test
+ public void testGenerateChallengeCache() throws RemoteException {
+ verify(mSession).setCallback(any());
+
+ final ArgumentCaptor<Long> challengeCaptor = ArgumentCaptor.forClass(Long.class);
+
+ mAidlToHidlAdapter.generateChallenge();
+
+ verify(mSession).generateChallenge(CHALLENGE_TIMEOUT_SEC);
+ verify(mAidlResponseHandler).onChallengeGenerated(challengeCaptor.capture());
+ assertThat(challengeCaptor.getValue()).isEqualTo(mChallenge);
+
+ forwardTime(10 /* seconds */);
+ mAidlToHidlAdapter.generateChallenge();
+ forwardTime(20 /* seconds */);
+ mAidlToHidlAdapter.generateChallenge();
+
+ //Confirms that the challenge is cached and the hal method is not called again
+ verifyNoMoreInteractions(mSession);
+ verify(mAidlResponseHandler, times(3))
+ .onChallengeGenerated(mChallenge);
+
+ forwardTime(60 /* seconds */);
+ mAidlToHidlAdapter.generateChallenge();
+
+ //HAL method called after challenge has timed out
+ verify(mSession, times(2)).generateChallenge(CHALLENGE_TIMEOUT_SEC);
+ }
+
+ @Test
+ public void testRevokeChallenge_waitsUntilEmpty() throws RemoteException {
+ for (int i = 0; i < 3; i++) {
+ mAidlToHidlAdapter.generateChallenge();
+ forwardTime(10 /* seconds */);
+ }
+ for (int i = 0; i < 3; i++) {
+ mAidlToHidlAdapter.revokeChallenge(0);
+ forwardTime((i + 1) * 10 /* seconds */);
+ }
+
+ verify(mSession).revokeChallenge();
+ }
+
+ @Test
+ public void testRevokeChallenge_timeout() throws RemoteException {
+ mAidlToHidlAdapter.generateChallenge();
+ mAidlToHidlAdapter.generateChallenge();
+ forwardTime(700);
+ mAidlToHidlAdapter.generateChallenge();
+ mAidlToHidlAdapter.revokeChallenge(0);
+
+ verify(mSession).revokeChallenge();
+ }
+
+ @Test
+ public void testEnroll() throws RemoteException {
+ ICancellationSignal cancellationSignal = mAidlToHidlAdapter.enroll(mHardwareAuthToken,
+ EnrollmentType.DEFAULT, mFeatures,
+ null /* previewSurface */);
+ ArgumentCaptor<ArrayList<Integer>> featureCaptor = ArgumentCaptor.forClass(ArrayList.class);
+
+ verify(mSession).enroll(any(), eq(ENROLL_TIMEOUT_SEC), featureCaptor.capture());
+
+ ArrayList<Integer> features = featureCaptor.getValue();
+
+ assertThat(features).containsExactly(
+ AidlConversionUtils.convertAidlToFrameworkFeature(mFeatures[0]));
+
+ cancellationSignal.cancel();
+
+ verify(mSession).cancel();
+ }
+
+ @Test
+ public void testAuthenticate() throws RemoteException {
+ final int operationId = 2;
+ ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+
+ verify(mSession).authenticate(operationId);
+
+ cancellationSignal.cancel();
+
+ verify(mSession).cancel();
+ }
+
+ @Test
+ public void testDetectInteraction() throws RemoteException {
+ ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+
+ verify(mSession).authenticate(0);
+
+ cancellationSignal.cancel();
+
+ verify(mSession).cancel();
+ }
+
+ @Test
+ public void testEnumerateEnrollments() throws RemoteException {
+ mAidlToHidlAdapter.enumerateEnrollments();
+
+ verify(mSession).enumerate();
+ }
+
+ @Test
+ public void testRemoveEnrollment() throws RemoteException {
+ final int[] enrollments = new int[]{1};
+ mAidlToHidlAdapter.removeEnrollments(enrollments);
+
+ verify(mSession).remove(enrollments[0]);
+ }
+
+ @Test
+ public void testGetFeatures_onResultSuccess() throws RemoteException {
+ final OptionalBool result = new OptionalBool();
+ result.status = Status.OK;
+ result.value = true;
+ ArgumentCaptor<byte[]> featureRetrieved = ArgumentCaptor.forClass(byte[].class);
+
+ when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
+
+ mAidlToHidlAdapter.setFeature(mFeature);
+ mAidlToHidlAdapter.getFeatures();
+
+ verify(mSession).getFeature(eq(mFeature), anyInt());
+ verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
+ assertThat(featureRetrieved.getValue()[0]).isEqualTo(
+ AidlConversionUtils.convertFrameworkToAidlFeature(mFeature));
+ }
+
+ @Test
+ public void testGetFeatures_onResultFailed() throws RemoteException {
+ final OptionalBool result = new OptionalBool();
+ result.status = Status.OK;
+ result.value = false;
+ ArgumentCaptor<byte[]> featureRetrieved = ArgumentCaptor.forClass(byte[].class);
+
+ when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
+
+ mAidlToHidlAdapter.setFeature(mFeature);
+ mAidlToHidlAdapter.getFeatures();
+
+ verify(mSession).getFeature(eq(mFeature), anyInt());
+ verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
+ assertThat(featureRetrieved.getValue().length).isEqualTo(0);
+ }
+
+ @Test
+ public void testGetFeatures_onStatusFailed() throws RemoteException {
+ final OptionalBool result = new OptionalBool();
+ result.status = Status.INTERNAL_ERROR;
+ result.value = false;
+
+ when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
+
+ mAidlToHidlAdapter.setFeature(mFeature);
+ mAidlToHidlAdapter.getFeatures();
+
+ verify(mSession).getFeature(eq(mFeature), anyInt());
+ verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
+ verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0);
+ }
+
+ @Test
+ public void testGetFeatures_featureNotSet() throws RemoteException {
+ mAidlToHidlAdapter.getFeatures();
+
+ verify(mSession, never()).getFeature(eq(mFeature), anyInt());
+ verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
+ }
+
+ @Test
+ public void testSetFeatureSuccessful() throws RemoteException {
+ byte feature = Feature.REQUIRE_ATTENTION;
+ boolean enabled = true;
+
+ when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())).thenReturn(Status.OK);
+
+ mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+
+ verify(mAidlResponseHandler).onFeatureSet(feature);
+ }
+
+ @Test
+ public void testSetFeatureFailed() throws RemoteException {
+ byte feature = Feature.REQUIRE_ATTENTION;
+ boolean enabled = true;
+
+ when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt()))
+ .thenReturn(Status.INTERNAL_ERROR);
+
+ mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+
+ verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN,
+ 0 /* vendorCode */);
+ }
+
+ @Test
+ public void testGetAuthenticatorId() throws RemoteException {
+ final long authenticatorId = 2L;
+ final OptionalUint64 result = new OptionalUint64();
+ result.status = Status.OK;
+ result.value = authenticatorId;
+
+ when(mSession.getAuthenticatorId()).thenReturn(result);
+
+ mAidlToHidlAdapter.getAuthenticatorId();
+
+ verify(mSession).getAuthenticatorId();
+ verify(mAidlResponseHandler).onAuthenticatorIdRetrieved(authenticatorId);
+ }
+
+ @Test
+ public void testResetLockout() throws RemoteException {
+ mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+
+ ArgumentCaptor<ArrayList> hatCaptor = ArgumentCaptor.forClass(ArrayList.class);
+
+ verify(mSession).resetLockout(hatCaptor.capture());
+
+ assertThat(hatCaptor.getValue()).containsExactlyElementsIn(processHAT(mHardwareAuthToken));
+ }
+
+ private ArrayList<Byte> processHAT(HardwareAuthToken hat) {
+ ArrayList<Byte> hardwareAuthToken = new ArrayList<>();
+ for (byte b : HardwareAuthTokenUtils.toByteArray(hat)) {
+ hardwareAuthToken.add(b);
+ }
+ return hardwareAuthToken;
+ }
+
+ private void forwardTime(long seconds) {
+ when(mClock.millis()).thenReturn(seconds * 1000);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 8a11e31..79a528c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -53,11 +53,15 @@
import android.os.RemoteException;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.TestableContext;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.CallbackWithProbe;
@@ -66,6 +70,7 @@
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutTracker;
import org.junit.Before;
import org.junit.Rule;
@@ -102,6 +107,9 @@
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock
private ISession mHal;
@@ -124,7 +132,7 @@
@Mock
private ClientMonitorCallback mCallback;
@Mock
- private Sensor.HalSessionCallback mHalSessionCallback;
+ private AidlResponseHandler mAidlResponseHandler;
@Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
@@ -135,6 +143,8 @@
private AuthSessionCoordinator mAuthSessionCoordinator;
@Mock
private Clock mClock;
+ @Mock
+ private LockoutTracker mLockoutTracker;
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
@Captor
@@ -425,37 +435,65 @@
verify(mCallback).onClientFinished(client, true);
}
+ @Test
+ public void testLockoutTracker_authSuccess() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient(1 /* version */,
+ true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter,
+ mLockoutTracker);
+ client.start(mCallback);
+ client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+ 2 /* deviceId */), true /* authenticated */, new ArrayList<>());
+
+ verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID);
+ verify(mLockoutTracker, never()).addFailedAttemptForUser(anyInt());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void testLockoutTracker_authFailed() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient(1 /* version */,
+ true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter,
+ mLockoutTracker);
+ client.start(mCallback);
+ client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+ 2 /* deviceId */), false /* authenticated */, new ArrayList<>());
+
+ verify(mLockoutTracker, never()).resetFailedAttemptsForUser(anyBoolean(), anyInt());
+ verify(mLockoutTracker).addFailedAttemptForUser(USER_ID);
+ }
+
private FingerprintAuthenticationClient createClient() throws RemoteException {
return createClient(100 /* version */, true /* allowBackgroundAuthentication */,
- mClientMonitorCallbackConverter);
+ mClientMonitorCallbackConverter, null);
}
private FingerprintAuthenticationClient createClientWithoutBackgroundAuth()
throws RemoteException {
return createClient(100 /* version */, false /* allowBackgroundAuthentication */,
- mClientMonitorCallbackConverter);
+ mClientMonitorCallbackConverter, null);
}
private FingerprintAuthenticationClient createClient(int version) throws RemoteException {
return createClient(version, true /* allowBackgroundAuthentication */,
- mClientMonitorCallbackConverter);
+ mClientMonitorCallbackConverter, null);
}
private FingerprintAuthenticationClient createClientWithNullListener() throws RemoteException {
return createClient(100 /* version */, true /* allowBackgroundAuthentication */,
- null /* listener */);
+ null, /* listener */null);
}
private FingerprintAuthenticationClient createClient(int version,
- boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener)
+ boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener,
+ LockoutTracker lockoutTracker)
throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
- final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
final FingerprintAuthenticateOptions options = new FingerprintAuthenticateOptions.Builder()
.setOpPackageName("test-owner")
- .setUserId(5)
- .setSensorId(9)
+ .setUserId(USER_ID)
+ .setSensorId(SENSOR_ID)
.build();
return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken,
REQUEST_ID, listener, OP_ID,
@@ -463,10 +501,11 @@
false /* requireConfirmation */,
mBiometricLogger, mBiometricContext,
true /* isStrongBiometric */,
- null /* taskStackListener */, null /* lockoutCache */,
+ null /* taskStackListener */,
mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,
mSensorProps,
- new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) {
+ new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock,
+ lockoutTracker) {
@Override
protected ActivityTaskManager getActivityTaskManager() {
return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 78d3a9d..a467c84 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -83,7 +83,7 @@
@Mock
private ClientMonitorCallback mCallback;
@Mock
- private Sensor.HalSessionCallback mHalSessionCallback;
+ private AidlResponseHandler mAidlResponseHandler;
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
@Captor
@@ -150,7 +150,7 @@
@Test
public void testWhenListenerIsNull() {
- final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mHalSessionCallback);
+ final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mAidlResponseHandler);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext, () -> aidl,
mToken, 6 /* requestId */, null /* listener */,
new FingerprintAuthenticateOptions.Builder()
@@ -173,7 +173,7 @@
private FingerprintDetectClient createClient(int version) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
- final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
return new FingerprintDetectClient(mContext, () -> aidl, mToken,
6 /* requestId */, mClientMonitorCallbackConverter,
new FingerprintAuthenticateOptions.Builder()
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index ef25380..c7eb1db 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -102,7 +102,7 @@
@Mock
private ClientMonitorCallback mCallback;
@Mock
- private Sensor.HalSessionCallback mHalSessionCallback;
+ private AidlResponseHandler mAidlResponseHandler;
@Mock
private Probe mLuxProbe;
@Captor
@@ -291,7 +291,7 @@
private FingerprintEnrollClient createClient(int version) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
- final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
return new FingerprintEnrollClient(mContext, () -> aidl, mToken, REQUEST_ID,
mClientMonitorCallbackConverter, 0 /* userId */,
HAT, "owner", mBiometricUtils, 8 /* sensorId */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java
new file mode 100644
index 0000000..8409619
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FingerprintGenerateChallengeClientTest {
+ private static final String TAG = "FingerprintGenerateChallengeClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+ private static final long CHALLENGE = 200;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mListener;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+
+ private FingerprintGenerateChallengeClient mClient;
+
+ @Before
+ public void setUp() {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ mClient = new FingerprintGenerateChallengeClient(mContext, () -> mAidlSession, mToken,
+ mListener, USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext);
+ }
+
+ @Test
+ public void generateChallenge() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE);
+ return null;
+ }).when(mSession).generateChallenge();
+ mClient.start(mCallback);
+
+ verify(mSession).generateChallenge();
+ verify(mListener).onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
index 5806443..c9482ce 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
@@ -28,6 +28,7 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.fingerprint.Fingerprint;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.testing.TestableContext;
@@ -41,7 +42,6 @@
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -70,9 +70,9 @@
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
@Mock
- private AidlSession mAidlSession;
+ ISession mSession;
@Mock
- private ISession mSession;
+ private AidlSession mAidlSession;
@Mock
private BiometricLogger mLogger;
@Mock
@@ -87,15 +87,16 @@
@Before
public void setup() {
- when(mAidlSession.getSession()).thenReturn(mSession);
mAddedIds = new ArrayList<>();
+
+ when(mAidlSession.getSession()).thenReturn(mSession);
}
- @Ignore("TODO(b/229015801): verify cleanup behavior")
@Test
public void removesUnknownTemplate() throws Exception {
mClient = createClient();
+ final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class);
final List<Fingerprint> templates = List.of(
new Fingerprint("one", 1, 1),
new Fingerprint("two", 2, 1)
@@ -108,8 +109,8 @@
mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0);
}
+ verify(mSession).enumerateEnrollments();
assertThat(mAddedIds).isEmpty();
- final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class);
verify(mSession, times(2)).removeEnrollments(captor.capture());
assertThat(captor.getAllValues().stream()
.flatMap(x -> Arrays.stream(x).boxed())
@@ -132,13 +133,15 @@
mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
}
+ verify(mSession).enumerateEnrollments();
assertThat(mAddedIds).containsExactly(1, 2);
verify(mSession, never()).removeEnrollments(any());
verify(mCallback).onClientFinished(eq(mClient), eq(true));
}
@Test
- public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() {
+ public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled()
+ throws RemoteException {
mClient = createClient();
final List<Fingerprint> templates = List.of(
@@ -150,6 +153,8 @@
for (int i = templates.size() - 1; i >= 0; i--) {
mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
}
+
+ verify(mSession).enumerateEnrollments();
// The first template is removed after enumeration
assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(2);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java
new file mode 100644
index 0000000..723f916
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.Fingerprint;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@Presubmit
+@SmallTest
+public class FingerprintInternalEnumerateClientTest {
+ private static final String TAG = "FingerprintInternalEnumerateClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricUtils<Fingerprint> mBiometricUtils;
+
+ private FingerprintInternalEnumerateClient mClient;
+
+ @Before
+ public void setUp() {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ List<Fingerprint> enrolled = new ArrayList<>();
+ enrolled.add(new Fingerprint("one", 1, 1));
+ mClient = new FingerprintInternalEnumerateClient(mContext, () -> mAidlSession, mToken,
+ USER_ID, TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger,
+ mBiometricContext);
+ }
+
+ @Test
+ public void internalEnumerate_unknownTemplates() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onEnumerationResult(new Fingerprint("two", 2, 1), 1);
+ mClient.onEnumerationResult(new Fingerprint("three", 3, 1), 0);
+ return null;
+ }).when(mSession).enumerateEnrollments();
+ mClient.start(mCallback);
+
+ verify(mSession).enumerateEnrollments();
+ assertThat(mClient.getUnknownHALTemplates().stream()
+ .flatMap(x -> Stream.of(x.getBiometricId()))
+ .collect(Collectors.toList())).containsExactly(2, 3);
+ verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, 1);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void internalEnumerate_noUnknownTemplates() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onEnumerationResult(new Fingerprint("one", 1, 1), 0);
+ return null;
+ }).when(mSession).enumerateEnrollments();
+ mClient.start(mCallback);
+
+ verify(mSession).enumerateEnrollments();
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+ verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java
new file mode 100644
index 0000000..64f07e2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.Fingerprint;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@Presubmit
+@SmallTest
+public class FingerprintRemovalClientTest {
+ private static final String TAG = "FingerprintRemovalClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mListener;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricUtils<Fingerprint> mBiometricUtils;
+ @Mock
+ private Map<Integer, Long> mAuthenticatorIds;
+
+ private FingerprintRemovalClient mClient;
+ private int[] mBiometricIds = new int[]{1, 2};
+
+ @Before
+ public void setUp() {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ mClient = new FingerprintRemovalClient(mContext, () -> mAidlSession, mToken, mListener,
+ mBiometricIds, USER_ID, TAG, mBiometricUtils, SENSOR_ID,
+ mBiometricLogger, mBiometricContext, mAuthenticatorIds);
+ }
+
+ @Test
+ public void removalMultipleFingerprints() throws RemoteException {
+ when(mBiometricUtils.getBiometricsForUser(any(), anyInt())).thenReturn(
+ List.of(new Fingerprint("three", 3, 1)));
+ doAnswer(invocation -> {
+ mClient.onRemoved(new Fingerprint("one", 1, 1), 1);
+ mClient.onRemoved(new Fingerprint("two", 2, 1), 0);
+ return null;
+ }).when(mSession).removeEnrollments(mBiometricIds);
+ mClient.start(mCallback);
+
+ verify(mSession).removeEnrollments(mBiometricIds);
+ verify(mBiometricUtils, times(2)).removeBiometricForUser(eq(mContext),
+ eq(USER_ID), anyInt());
+ verifyNoMoreInteractions(mAuthenticatorIds);
+ verify(mListener, times(2)).onRemoved(any(), anyInt());
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void removeFingerprint_nullIdentifier() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onRemoved(null, 0);
+ return null;
+ }).when(mSession).removeEnrollments(mBiometricIds);
+ mClient.start(mCallback);
+
+ verify(mSession).removeEnrollments(mBiometricIds);
+ verify(mListener).onError(anyInt(), anyInt(), anyInt(), anyInt());
+ verify(mCallback).onClientFinished(mClient, false);
+ }
+
+ @Test
+ public void removeFingerprints_noFingerprintEnrolled() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onRemoved(new Fingerprint("one", 1, 1), 1);
+ mClient.onRemoved(new Fingerprint("two", 2, 1), 0);
+ return null;
+ }).when(mSession).removeEnrollments(mBiometricIds);
+ when(mBiometricUtils.getBiometricsForUser(any(), anyInt())).thenReturn(new ArrayList<>());
+
+ mClient.start(mCallback);
+
+ verify(mSession).removeEnrollments(mBiometricIds);
+ verify(mBiometricUtils, times(2)).removeBiometricForUser(eq(mContext),
+ eq(USER_ID), anyInt());
+ verify(mAuthenticatorIds).put(USER_ID, 0L);
+ verify(mListener, times(2)).onRemoved(any(), anyInt());
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java
new file mode 100644
index 0000000..a4746de
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FingerprintResetLockoutClientTest {
+ private static final String TAG = "FingerprintResetLockoutClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private LockoutTracker mLockoutTracker;
+ @Mock
+ private LockoutResetDispatcher mLockoutResetDispatcher;
+ @Mock
+ private AuthSessionCoordinator mAuthSessionCoordinator;
+
+ private FingerprintResetLockoutClient mClient;
+
+ @Before
+ public void setUp() {
+ when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ mClient = new FingerprintResetLockoutClient(mContext, () -> mAidlSession, USER_ID, TAG,
+ SENSOR_ID, mBiometricLogger, mBiometricContext, new byte[69],
+ mLockoutTracker, mLockoutResetDispatcher,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ }
+
+ @Test
+ public void resetLockout_onLockoutCleared() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onLockoutCleared();
+ return null;
+ }).when(mSession).resetLockout(any());
+ mClient.start(mCallback);
+
+ verify(mSession).resetLockout(any());
+ verify(mLockoutTracker).setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_NONE);
+ verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID);
+ verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID),
+ eq(BiometricManager.Authenticators.BIOMETRIC_STRONG), anyLong());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java
new file mode 100644
index 0000000..f19b2f7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FingerprintRevokeChallengeClientTest {
+ private static final String TAG = "FingerprintRevokeChallengeClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+ private static final long CHALLENGE = 200;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+
+ private FingerprintRevokeChallengeClient mClient;
+
+ @Before
+ public void setUp() {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ mClient = new FingerprintRevokeChallengeClient(mContext, () -> mAidlSession, mToken,
+ USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, CHALLENGE);
+ }
+
+ @Test
+ public void revokeChallenge_sameChallenge() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onChallengeRevoked(CHALLENGE);
+ return null;
+ }).when(mSession).revokeChallenge(CHALLENGE);
+ mClient.start(mCallback);
+
+ verify(mSession).revokeChallenge(CHALLENGE);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void revokeChallenge_differentChallenge() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onChallengeRevoked(CHALLENGE + 1);
+ return null;
+ }).when(mSession).revokeChallenge(CHALLENGE);
+ mClient.start(mCallback);
+
+ verify(mSession).revokeChallenge(CHALLENGE);
+ verify(mCallback).onClientFinished(mClient, false);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 15d7601..4102600 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -75,7 +75,7 @@
@Mock
private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
@Mock
- private Sensor.HalSessionCallback.Callback mHalSessionCallback;
+ private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
@Mock
private LockoutResetDispatcher mLockoutResetDispatcher;
@Mock
@@ -97,7 +97,7 @@
private final LockoutCache mLockoutCache = new LockoutCache();
private UserAwareBiometricScheduler mScheduler;
- private Sensor.HalSessionCallback mHalCallback;
+ private AidlResponseHandler mHalCallback;
@Before
public void setUp() {
@@ -113,10 +113,9 @@
mBiometricService,
() -> USER_ID,
mUserSwitchCallback);
- mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
- TAG, mScheduler, SENSOR_ID,
- USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- mHalSessionCallback);
+ mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
+ mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
+ mHardwareUnavailableCallback);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
new file mode 100644
index 0000000..b78ba82
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.biometrics.sensors.fingerprint.hidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.hardware.keymaster.Timestamp;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class AidlToHidlAdapterTest {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private IBiometricsFingerprint mSession;
+ @Mock
+ private AidlResponseHandler mAidlResponseHandler;
+ @Mock
+ private HardwareAuthToken mHardwareAuthToken;
+
+ private final long mChallenge = 100L;
+ private final int mUserId = 0;
+ private AidlToHidlAdapter mAidlToHidlAdapter;
+
+ @Before
+ public void setUp() {
+ mAidlToHidlAdapter = new AidlToHidlAdapter(() -> mSession, mUserId,
+ mAidlResponseHandler);
+ mHardwareAuthToken.timestamp = new Timestamp();
+ mHardwareAuthToken.mac = new byte[10];
+ }
+
+ @Test
+ public void testGenerateChallenge() throws RemoteException {
+ when(mSession.preEnroll()).thenReturn(mChallenge);
+ mAidlToHidlAdapter.generateChallenge();
+
+ verify(mSession).preEnroll();
+ verify(mAidlResponseHandler).onChallengeGenerated(mChallenge);
+ }
+
+ @Test
+ public void testRevokeChallenge() throws RemoteException {
+ mAidlToHidlAdapter.revokeChallenge(mChallenge);
+
+ verify(mSession).postEnroll();
+ verify(mAidlResponseHandler).onChallengeRevoked(0L);
+ }
+
+ @Test
+ public void testEnroll() throws RemoteException {
+ final ICancellationSignal cancellationSignal =
+ mAidlToHidlAdapter.enroll(mHardwareAuthToken);
+
+ verify(mSession).enroll(any(), anyInt(), eq(AidlToHidlAdapter.ENROLL_TIMEOUT_SEC));
+
+ cancellationSignal.cancel();
+
+ verify(mSession).cancel();
+ }
+
+ @Test
+ public void testAuthenticate() throws RemoteException {
+ final int operationId = 2;
+ final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+
+ verify(mSession).authenticate(operationId, mUserId);
+
+ cancellationSignal.cancel();
+
+ verify(mSession).cancel();
+ }
+
+ @Test
+ public void testDetectInteraction() throws RemoteException {
+ final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+
+ verify(mSession).authenticate(0 /* operationId */, mUserId);
+
+ cancellationSignal.cancel();
+
+ verify(mSession).cancel();
+ }
+
+ @Test
+ public void testEnumerateEnrollments() throws RemoteException {
+ mAidlToHidlAdapter.enumerateEnrollments();
+
+ verify(mSession).enumerate();
+ }
+
+ @Test
+ public void testRemoveEnrollment() throws RemoteException {
+ final int[] enrollmentIds = new int[]{1};
+ mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+
+ verify(mSession).remove(mUserId, enrollmentIds[0]);
+ }
+
+ @Test
+ public void testRemoveMultipleEnrollments() throws RemoteException {
+ final int[] enrollmentIds = new int[]{1, 2};
+ mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+
+ verify(mSession).remove(mUserId, 0);
+ }
+
+ @Test
+ public void testResetLockout() throws RemoteException {
+ mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+
+ verify(mAidlResponseHandler).onLockoutCleared();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index e8cbcf9..a3d415e 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -337,11 +337,7 @@
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
- mSetFlagsRule.disableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
- mSetFlagsRule.disableFlags(Flags.FLAG_DYNAMIC_POLICY);
- mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS);
- mSetFlagsRule.disableFlags(Flags.FLAG_VDM_CUSTOM_HOME);
- mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_NATIVE_VDM);
+ mSetFlagsRule.initAllFlagsToReleaseConfigDefault();
doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index 67b70684..5cc84b1 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.contentcapture;
+import static android.view.contentprotection.flags.Flags.FLAG_PARSE_GROUPS_CONFIG_ENABLED;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -33,6 +35,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.contentcapture.ContentCaptureServiceInfo;
import android.view.contentcapture.ContentCaptureEvent;
@@ -56,6 +59,9 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Collections;
+import java.util.List;
+
/**
* Test for {@link ContentCaptureManagerService}.
*
@@ -84,6 +90,8 @@
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock private UserManagerInternal mMockUserManagerInternal;
@Mock private ContentProtectionBlocklistManager mMockContentProtectionBlocklistManager;
@@ -96,6 +104,10 @@
private boolean mDevCfgEnableContentProtectionReceiver;
+ private List<List<String>> mDevCfgContentProtectionRequiredGroups = List.of(List.of("a"));
+
+ private List<List<String>> mDevCfgContentProtectionOptionalGroups = Collections.emptyList();
+
private int mContentProtectionBlocklistManagersCreated;
private int mContentProtectionServiceInfosCreated;
@@ -367,7 +379,21 @@
}
@Test
- public void isContentProtectionReceiverEnabled_withoutManagers() {
+ public void isContentProtectionReceiverEnabled_true() {
+ when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
+ when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isTrue();
+ }
+
+ @Test
+ public void isContentProtectionReceiverEnabled_false_withoutManagers() {
boolean actual =
mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
USER_ID, PACKAGE_NAME);
@@ -378,7 +404,7 @@
}
@Test
- public void isContentProtectionReceiverEnabled_disabledWithFlag() {
+ public void isContentProtectionReceiverEnabled_false_disabledWithFlag() {
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false;
@@ -393,6 +419,22 @@
}
@Test
+ public void isContentProtectionReceiverEnabled_false_emptyGroups() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mDevCfgContentProtectionRequiredGroups = Collections.emptyList();
+ mDevCfgContentProtectionOptionalGroups = Collections.emptyList();
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
+ verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ }
+
+ @Test
public void onLoginDetected_disabledAfterConstructor() {
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -437,29 +479,91 @@
}
@Test
- public void parseContentProtectionGroupsConfig_null() {
+ public void parseContentProtectionGroupsConfig_disabled_null() {
+ mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
assertThat(service.parseContentProtectionGroupsConfig(null)).isEmpty();
}
@Test
- public void parseContentProtectionGroupsConfig_empty() {
+ public void parseContentProtectionGroupsConfig_disabled_empty() {
+ mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty();
}
@Test
- public void parseContentProtectionGroupsConfig_notEmpty() {
+ public void parseContentProtectionGroupsConfig_disabled_notEmpty() {
+ mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
assertThat(service.parseContentProtectionGroupsConfig("a")).isEmpty();
}
+ @Test
+ public void parseContentProtectionGroupsConfig_enabled_null() {
+ mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+ ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+ assertThat(service.parseContentProtectionGroupsConfig(null)).isEmpty();
+ }
+
+ @Test
+ public void parseContentProtectionGroupsConfig_enabled_empty() {
+ mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+ ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+ assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty();
+ }
+
+ @Test
+ public void parseContentProtectionGroupsConfig_enabled_singleValue() {
+ mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+ ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+ assertThat(service.parseContentProtectionGroupsConfig("a"))
+ .isEqualTo(List.of(List.of("a")));
+ }
+
+ @Test
+ public void parseContentProtectionGroupsConfig_enabled_multipleValues() {
+ mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+ ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+ assertThat(service.parseContentProtectionGroupsConfig("a,b"))
+ .isEqualTo(List.of(List.of("a", "b")));
+ }
+
+ @Test
+ public void parseContentProtectionGroupsConfig_enabled_multipleGroups() {
+ mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+ ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+ assertThat(service.parseContentProtectionGroupsConfig("a,b;c;d,e"))
+ .isEqualTo(List.of(List.of("a", "b"), List.of("c"), List.of("d", "e")));
+ }
+
+ @Test
+ public void parseContentProtectionGroupsConfig_enabled_emptyValues() {
+ mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+ ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+ assertThat(service.parseContentProtectionGroupsConfig("a;b;;;c;,d,,e,;,;"))
+ .isEqualTo(List.of(List.of("a"), List.of("b"), List.of("c"), List.of("d", "e")));
+ }
+
private class TestContentCaptureManagerService extends ContentCaptureManagerService {
TestContentCaptureManagerService() {
super(sContext);
this.mDevCfgEnableContentProtectionReceiver =
ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
+ this.mDevCfgContentProtectionRequiredGroups =
+ ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionRequiredGroups;
+ this.mDevCfgContentProtectionOptionalGroups =
+ ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionOptionalGroups;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 9b32a80..de3cfbf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -54,8 +54,8 @@
import com.android.internal.widget.LockSettingsInternal;
import com.android.server.AlarmManagerInternal;
import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 16fdfb1..76aa40c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -77,7 +77,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
import com.android.server.AlarmManagerInternal;
-import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.UserManagerInternal;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index a029db9..fa3c7a4c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -22,7 +22,7 @@
import android.content.Context;
-import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 23f14f8..02b86db 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -53,8 +53,8 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import org.junit.After;
import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 37a6d22..eca19c8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -255,7 +255,7 @@
public void testUnlockUserKeyIfUnsecuredPassesPrimaryUserAuthSecret() throws RemoteException {
initSpAndSetCredential(PRIMARY_USER_ID, newPassword(null));
reset(mAuthSecretService);
- mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+ mService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
}
@@ -267,7 +267,7 @@
mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
reset(mAuthSecretService);
- mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+ mService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
}
@@ -285,39 +285,39 @@
@Test
public void testHeadlessSystemUserDoesNotPassAuthSecret() throws RemoteException {
setupHeadlessTest();
- mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+ mService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
}
@Test
public void testHeadlessSecondaryUserPassesAuthSecret() throws RemoteException {
setupHeadlessTest();
- mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+ mService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
}
@Test
public void testHeadlessTertiaryUserPassesSameAuthSecret() throws RemoteException {
setupHeadlessTest();
- mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+ mService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
var captor = ArgumentCaptor.forClass(byte[].class);
verify(mAuthSecretService).setPrimaryUserCredential(captor.capture());
var value = captor.getValue();
reset(mAuthSecretService);
- mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
+ mService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
verify(mAuthSecretService).setPrimaryUserCredential(eq(value));
}
@Test
public void testHeadlessTertiaryUserPassesSameAuthSecretAfterReset() throws RemoteException {
setupHeadlessTest();
- mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+ mService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
var captor = ArgumentCaptor.forClass(byte[].class);
verify(mAuthSecretService).setPrimaryUserCredential(captor.capture());
var value = captor.getValue();
mService.clearAuthSecret();
reset(mAuthSecretService);
- mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
+ mService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
verify(mAuthSecretService).setPrimaryUserCredential(eq(value));
}
diff --git a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
index 75d71da..06f117b 100644
--- a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
@@ -38,9 +38,10 @@
public class BluetoothRouteControllerTest {
private final BluetoothRouteController.BluetoothRoutesUpdatedListener
- mBluetoothRoutesUpdatedListener = routes -> {
- // Empty on purpose.
- };
+ mBluetoothRoutesUpdatedListener =
+ () -> {
+ // Empty on purpose.
+ };
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
index ec4b8a8..14b121d 100644
--- a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
@@ -38,7 +38,7 @@
public class DeviceRouteControllerTest {
private final DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener =
- deviceRoute -> {
+ () -> {
// Empty on purpose.
};
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index d85768d..f94aff7 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -26,6 +26,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -66,6 +67,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.testutils.OffsettableClock;
import com.android.server.wm.WindowManagerInternal;
@@ -128,6 +130,14 @@
}
};
+ private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector =
+ new MediaProjectionManagerService.Injector() {
+ @Override
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger() {
+ return mMediaProjectionMetricsLogger;
+ }
+ };
+
private Context mContext;
private MediaProjectionManagerService mService;
private OffsettableClock mClock;
@@ -142,6 +152,8 @@
private PackageManager mPackageManager;
@Mock
private IMediaProjectionWatcherCallback mWatcherCallback;
+ @Mock
+ private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
@Captor
private ArgumentCaptor<ContentRecordingSession> mSessionCaptor;
@@ -734,6 +746,25 @@
}
@Test
+ public void setContentRecordingSession_success_logsCaptureInProgress()
+ throws Exception {
+ mService.addCallback(mWatcherCallback);
+ MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+ projection.start(mIMediaProjectionCallback);
+ doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+ any(ContentRecordingSession.class));
+
+ service.setContentRecordingSession(DISPLAY_SESSION);
+
+ verify(mMediaProjectionMetricsLogger).notifyProjectionStateChange(
+ projection.uid,
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
+ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN
+ );
+ }
+
+ @Test
public void setContentRecordingSession_notifiesListenersOnCallbackLooper()
throws Exception {
mService = new MediaProjectionManagerService(mContext, mTestLooperInjector);
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java
new file mode 100644
index 0000000..07cdf4d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.media.projection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+/**
+ * Tests for the {@link MediaProjectionSessionIdGenerator} class.
+ *
+ * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionSessionIdGeneratorTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionSessionIdGeneratorTest {
+
+ private static final String TEST_PREFS_FILE = "media-projection-session-id-test";
+
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private final File mSharedPreferencesFile = new File(mContext.getCacheDir(), TEST_PREFS_FILE);
+ private final SharedPreferences mSharedPreferences = createSharePreferences();
+ private final MediaProjectionSessionIdGenerator mGenerator =
+ createGenerator(mSharedPreferences);
+
+ @Before
+ public void setUp() {
+ mSharedPreferences.edit().clear().commit();
+ }
+
+ @After
+ public void tearDown() {
+ mSharedPreferences.edit().clear().commit();
+ mSharedPreferencesFile.delete();
+ }
+
+ @Test
+ public void getCurrentSessionId_byDefault_returns0() {
+ assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+ }
+
+ @Test
+ public void getCurrentSessionId_multipleTimes_returnsSameValue() {
+ assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+ assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+ assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+ }
+
+ @Test
+ public void createAndGetNewSessionId_returnsIncrementedId() {
+ int previousValue = mGenerator.getCurrentSessionId();
+
+ int newValue = mGenerator.createAndGetNewSessionId();
+
+ assertThat(newValue).isEqualTo(previousValue + 1);
+ }
+
+ @Test
+ public void createAndGetNewSessionId_persistsNewValue() {
+ int newValue = mGenerator.createAndGetNewSessionId();
+
+ MediaProjectionSessionIdGenerator newInstance = createGenerator(createSharePreferences());
+
+ assertThat(newInstance.getCurrentSessionId()).isEqualTo(newValue);
+ }
+
+ private SharedPreferences createSharePreferences() {
+ return mContext.getSharedPreferences(mSharedPreferencesFile, Context.MODE_PRIVATE);
+ }
+
+ private MediaProjectionSessionIdGenerator createGenerator(SharedPreferences sharedPreferences) {
+ return new MediaProjectionSessionIdGenerator(sharedPreferences);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionTimestampStoreTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionTimestampStoreTest.java
new file mode 100644
index 0000000..7723541
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionTimestampStoreTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.media.projection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.InstantSource;
+
+/**
+ * Tests for the {@link MediaProjectionTimestampStore} class.
+ *
+ * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionTimestampStoreTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionTimestampStoreTest {
+
+ private static final String TEST_PREFS_FILE = "media-projection-timestamp-test";
+
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private final File mSharedPreferencesFile = new File(mContext.getCacheDir(), TEST_PREFS_FILE);
+ private final SharedPreferences mSharedPreferences = createSharePreferences();
+
+ private Instant mCurrentInstant = Instant.ofEpochMilli(0);
+
+ private final InstantSource mInstantSource = () -> mCurrentInstant;
+ private final MediaProjectionTimestampStore mStore =
+ new MediaProjectionTimestampStore(mSharedPreferences, mInstantSource);
+
+ @Before
+ public void setUp() {
+ mSharedPreferences.edit().clear().commit();
+ }
+
+ @After
+ public void tearDown() {
+ mSharedPreferences.edit().clear().commit();
+ mSharedPreferencesFile.delete();
+ }
+
+ @Test
+ public void timeSinceLastActiveSession_byDefault_returnsNull() {
+ assertThat(mStore.timeSinceLastActiveSession()).isNull();
+ }
+
+ @Test
+ public void timeSinceLastActiveSession_returnsBasedOnLastActiveSessionEnded() {
+ mCurrentInstant = Instant.ofEpochMilli(0);
+ mStore.registerActiveSessionEnded();
+
+ mCurrentInstant = mCurrentInstant.plusSeconds(60);
+
+ assertThat(mStore.timeSinceLastActiveSession()).isEqualTo(Duration.ofSeconds(60));
+ }
+
+ @Test
+ public void timeSinceLastActiveSession_valueIsPersisted() {
+ mCurrentInstant = Instant.ofEpochMilli(0);
+ mStore.registerActiveSessionEnded();
+
+ MediaProjectionTimestampStore newStoreInstance =
+ new MediaProjectionTimestampStore(createSharePreferences(), mInstantSource);
+ mCurrentInstant = mCurrentInstant.plusSeconds(123);
+
+ assertThat(newStoreInstance.timeSinceLastActiveSession())
+ .isEqualTo(Duration.ofSeconds(123));
+ }
+
+ private SharedPreferences createSharePreferences() {
+ return mContext.getSharedPreferences(mSharedPreferencesFile, Context.MODE_PRIVATE);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pdb/OWNERS b/services/tests/servicestests/src/com/android/server/pdb/OWNERS
new file mode 100644
index 0000000..6dfb888
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pdb/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/pdb/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index 42be3d3..5d6f36c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -19,7 +19,6 @@
import android.content.pm.SigningDetails;
import android.util.SparseArray;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageUserStateImpl;
@@ -159,17 +158,19 @@
public PackageSetting build() {
final PackageSetting packageSetting = new PackageSetting(mName, mRealName,
- new File(mCodePath), mLegacyNativeLibraryPathString, mPrimaryCpuAbiString,
- mSecondaryCpuAbiString, mCpuAbiOverrideString, mPVersionCode, mPkgFlags,
- mPrivateFlags, mSharedUserId, null /* usesSdkLibraries */,
- null /* usesSdkLibrariesVersions */, null /* usesStaticLibraries */,
- null /* usesStaticLibrariesVersions */, mMimeGroups, mDomainSetId);
- packageSetting.setSignatures(mSigningDetails != null
- ? new PackageSignatures(mSigningDetails)
- : new PackageSignatures());
- packageSetting.setPkg((ParsedPackage) mPkg);
- packageSetting.setAppId(mAppId);
- packageSetting.setVolumeUuid(this.mVolumeUuid);
+ new File(mCodePath), mPkgFlags, mPrivateFlags, mDomainSetId)
+ .setLegacyNativeLibraryPath(mLegacyNativeLibraryPathString)
+ .setPrimaryCpuAbi(mPrimaryCpuAbiString)
+ .setSecondaryCpuAbi(mSecondaryCpuAbiString)
+ .setCpuAbiOverride(mCpuAbiOverrideString)
+ .setLongVersionCode(mPVersionCode)
+ .setSharedUserAppId(mSharedUserId)
+ .setMimeGroups(mMimeGroups)
+ .setSignatures(mSigningDetails != null
+ ? new PackageSignatures(mSigningDetails) : new PackageSignatures())
+ .setPkg(mPkg)
+ .setAppId(mAppId)
+ .setVolumeUuid(this.mVolumeUuid);
if (mInstallSource != null) {
packageSetting.setInstallSource(mInstallSource);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 3d4b4a6..75d012a 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -7769,6 +7769,74 @@
assertEquals(NotificationManagerService.MAX_PACKAGE_TOASTS, mService.mToastQueue.size());
}
+ @Test
+ public void testPrioritizeSystemToasts() throws Exception {
+ // Insert non-system toasts
+ final String testPackage = "testPackageName";
+ assertEquals(0, mService.mToastQueue.size());
+ mService.isSystemUid = false;
+ mService.isSystemAppId = false;
+ setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
+
+ // package is not suspended
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
+ .thenReturn(false);
+
+ INotificationManager nmService = (INotificationManager) mService.mService;
+
+ // Enqueue maximum number of toasts for test package
+ for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_TOASTS; i++) {
+ enqueueTextToast(testPackage, "Text");
+ }
+
+ // Enqueue system toast
+ final String testPackageSystem = "testPackageNameSystem";
+ mService.isSystemUid = true;
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem, false);
+ when(mPackageManager.isPackageSuspendedForUser(testPackageSystem, mUserId))
+ .thenReturn(false);
+
+ enqueueToast(testPackageSystem, new TestableToastCallback());
+
+ // System toast is inserted at the front of the queue, behind current showing toast
+ assertEquals(testPackageSystem, mService.mToastQueue.get(1).pkg);
+ }
+
+ @Test
+ public void testPrioritizeSystemToasts_enqueueAfterExistingSystemToast() throws Exception {
+ // Insert system toasts
+ final String testPackageSystem1 = "testPackageNameSystem1";
+ assertEquals(0, mService.mToastQueue.size());
+ mService.isSystemUid = true;
+ setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem1, false);
+
+ // package is not suspended
+ when(mPackageManager.isPackageSuspendedForUser(testPackageSystem1, mUserId))
+ .thenReturn(false);
+
+ INotificationManager nmService = (INotificationManager) mService.mService;
+
+ // Enqueue maximum number of toasts for test package
+ for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_TOASTS; i++) {
+ enqueueTextToast(testPackageSystem1, "Text");
+ }
+
+ // Enqueue another system toast
+ final String testPackageSystem2 = "testPackageNameSystem2";
+ mService.isSystemUid = true;
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem2, false);
+ when(mPackageManager.isPackageSuspendedForUser(testPackageSystem2, mUserId))
+ .thenReturn(false);
+
+ enqueueToast(testPackageSystem2, new TestableToastCallback());
+
+ // System toast is inserted at the back of the queue, after the other system toasts
+ assertEquals(testPackageSystem2,
+ mService.mToastQueue.get(mService.mToastQueue.size() - 1).pkg);
+ }
+
private void setAppInForegroundForToasts(int uid, boolean inForeground) {
int importance = (inForeground) ? IMPORTANCE_FOREGROUND : IMPORTANCE_NONE;
when(mActivityManager.getUidImportance(mUid)).thenReturn(importance);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index 121e296..337dd22 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -91,7 +91,6 @@
private static final Multimap<Class<?>, String> KNOWN_BAD =
ImmutableMultimap.<Class<?>, String>builder()
.put(Person.Builder.class, "setUri") // TODO: b/281044385
- .put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385
.build();
// Types that we can't really produce. No methods receiving these parameters will be invoked.
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index ca5cfa5..6f37967 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -27,6 +27,7 @@
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
+ "flag-junit",
"frameworks-base-testutils",
"frameworks-services-vibrator-testutils",
"junit",
@@ -34,6 +35,7 @@
"platform-test-annotations",
"service-permission.stubs.system_server",
"services.core",
+ "flag-junit",
],
platform_apis: true,
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
index bc826a3..04158c4 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
@@ -31,6 +31,8 @@
import android.content.res.Resources;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.AtomicFile;
import android.util.SparseArray;
@@ -49,6 +51,8 @@
import java.io.FileOutputStream;
public class HapticFeedbackCustomizationTest {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Rule public MockitoRule rule = MockitoJUnit.rule();
// Pairs of valid vibration XML along with their equivalent VibrationEffect.
@@ -77,6 +81,7 @@
@Before
public void setUp() {
when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true);
+ mSetFlagsRule.enableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
}
@Test
@@ -87,6 +92,21 @@
}
@Test
+ public void testParseCustomizations_featureFlagDisabled_returnsNull() throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
+ // Valid customization XML.
+ String xml = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ setupCustomizationFile(xml);
+
+ assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
+ .isNull();
+ }
+
+ @Test
public void testParseCustomizations_oneVibrationCustomization_success() throws Exception {
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index 0003555..3d0dca0 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -16,20 +16,24 @@
package com.android.server.vibrator;
+import static android.os.VibrationAttributes.CATEGORY_KEYBOARD;
import static android.os.VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
+import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
+import static android.os.VibrationEffect.EFFECT_CLICK;
import static android.os.VibrationEffect.EFFECT_TEXTURE_TICK;
import static android.os.VibrationEffect.EFFECT_TICK;
import static android.view.HapticFeedbackConstants.CLOCK_TICK;
import static android.view.HapticFeedbackConstants.CONTEXT_CLICK;
+import static android.view.HapticFeedbackConstants.KEYBOARD_RELEASE;
+import static android.view.HapticFeedbackConstants.KEYBOARD_TAP;
import static android.view.HapticFeedbackConstants.SAFE_MODE_ENABLED;
-import static android.view.HapticFeedbackConstants.TEXT_HANDLE_MOVE;
import static android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS;
import static android.view.HapticFeedbackConstants.SCROLL_LIMIT;
import static android.view.HapticFeedbackConstants.SCROLL_TICK;
-
+import static android.view.HapticFeedbackConstants.TEXT_HANDLE_MOVE;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -42,9 +46,10 @@
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.AtomicFile;
import android.util.SparseArray;
-import android.view.flags.FeatureFlags;
import androidx.test.InstrumentationRegistry;
@@ -62,6 +67,8 @@
public class HapticFeedbackVibrationProviderTest {
@Rule public MockitoRule rule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final VibrationEffect PRIMITIVE_TICK_EFFECT =
VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK, 0.2497f).compose();
private static final VibrationEffect PRIMITIVE_CLICK_EFFECT =
@@ -69,11 +76,15 @@
private static final int[] SCROLL_FEEDBACK_CONSTANTS =
new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK};
+ private static final int[] KEYBOARD_FEEDBACK_CONSTANTS =
+ new int[] {KEYBOARD_TAP, KEYBOARD_RELEASE};
+
+ private static final float KEYBOARD_VIBRATION_FIXED_AMPLITUDE = 0.62f;
+
private Context mContext = InstrumentationRegistry.getContext();
private VibratorInfo mVibratorInfo = VibratorInfo.EMPTY_VIBRATOR_INFO;
@Mock private Resources mResourcesMock;
- @Mock private FeatureFlags mViewFeatureFlags;
@Test
public void testNonExistentCustomization_useDefault() throws Exception {
@@ -214,6 +225,62 @@
}
@Test
+ public void testKeyboardHaptic_noFixedAmplitude_defaultVibrationReturned() {
+ mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
+ SparseArray<VibrationEffect> customizations = new SparseArray<>();
+ customizations.put(KEYBOARD_TAP, PRIMITIVE_CLICK_EFFECT);
+ customizations.put(KEYBOARD_RELEASE, PRIMITIVE_TICK_EFFECT);
+
+ // Test with a customization available for `KEYBOARD_TAP` & `KEYBOARD_RELEASE`.
+ HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);
+
+ assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
+ .isEqualTo(PRIMITIVE_CLICK_EFFECT);
+ assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
+ .isEqualTo(PRIMITIVE_TICK_EFFECT);
+
+ // Test with no customization available for `KEYBOARD_TAP` & `KEYBOARD_RELEASE`.
+ hapticProvider = createProviderWithDefaultCustomizations();
+
+ assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
+ .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
+ assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
+ .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
+ }
+
+ @Test
+ public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOff_defaultVibrationReturned() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+ mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
+ mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
+
+ HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+ assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
+ .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
+ assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
+ .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
+ }
+
+ @Test
+ public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOn_keyboardVibrationReturned() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+ mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
+ mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
+
+ HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+ assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
+ .isEqualTo(VibrationEffect.startComposition()
+ .addPrimitive(PRIMITIVE_CLICK, KEYBOARD_VIBRATION_FIXED_AMPLITUDE)
+ .compose());
+ assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
+ .isEqualTo(VibrationEffect.startComposition()
+ .addPrimitive(PRIMITIVE_TICK, KEYBOARD_VIBRATION_FIXED_AMPLITUDE)
+ .compose());
+ }
+
+ @Test
public void testVibrationAttribute_forNotBypassingIntensitySettings() {
HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
@@ -235,7 +302,7 @@
@Test
public void testVibrationAttribute_scrollFeedback_scrollApiFlagOn_bypassInterruptPolicy() {
- when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(true);
+ mSetFlagsRule.enableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API);
HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
@@ -248,7 +315,7 @@
@Test
public void testVibrationAttribute_scrollFeedback_scrollApiFlagOff_noBypassInterruptPolicy() {
- when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(false);
+ mSetFlagsRule.disableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API);
HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
@@ -259,14 +326,71 @@
}
}
+ @Test
+ public void testVibrationAttribute_keyboardCategoryOff_notUseKeyboardCategory() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+ HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+ for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
+ VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ effectId, /* bypassVibrationIntensitySetting= */ false);
+ assertWithMessage("Expected no CATEGORY_KEYBOARD for effect " + effectId)
+ .that(attrs.getCategory()).isEqualTo(0);
+ }
+ }
+
+ @Test
+ public void testVibrationAttribute_keyboardCategoryOn_useKeyboardCategory() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+ HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+ for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
+ VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ effectId, /* bypassVibrationIntensitySetting= */ false);
+ assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId)
+ .that(attrs.getCategory()).isEqualTo(CATEGORY_KEYBOARD);
+ }
+ }
+
+ @Test
+ public void testVibrationAttribute_noFixAmplitude_keyboardCategoryOn_noBypassIntensityScale() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+ mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
+ mockKeyboardVibrationFixedAmplitude(-1);
+ HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+ for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
+ VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ effectId, /* bypassVibrationIntensitySetting= */ false);
+ assertWithMessage("Expected no FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
+ + effectId)
+ .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isFalse();
+ }
+ }
+
+ @Test
+ public void testVibrationAttribute_fixAmplitude_keyboardCategoryOn_bypassIntensityScale() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+ mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
+ mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
+ HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
+
+ for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
+ VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ effectId, /* bypassVibrationIntensitySetting= */ false);
+ assertWithMessage("Expected FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
+ + effectId)
+ .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isTrue();
+ }
+ }
+
private HapticFeedbackVibrationProvider createProviderWithDefaultCustomizations() {
return createProvider(/* customizations= */ null);
}
private HapticFeedbackVibrationProvider createProvider(
SparseArray<VibrationEffect> customizations) {
- return new HapticFeedbackVibrationProvider(
- mResourcesMock, mVibratorInfo, customizations, mViewFeatureFlags);
+ return new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
}
private void mockVibratorPrimitiveSupport(int... supportedPrimitives) {
@@ -287,6 +411,11 @@
.thenReturn(vibrationPattern);
}
+ private void mockKeyboardVibrationFixedAmplitude(float amplitude) {
+ when(mResourcesMock.getFloat(R.dimen.config_keyboardHapticFeedbackFixedAmplitude))
+ .thenReturn(amplitude);
+ }
+
private void setupCustomizationFile(String xml) throws Exception {
File file = new File(mContext.getCacheDir(), "test.xml");
file.createNewFile();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index 1ae0966..7a2bb5a 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -69,7 +69,11 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.test.TestLooper;
+import android.os.vibrator.Flags;
import android.os.vibrator.VibrationConfig;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.util.ArraySet;
import android.view.Display;
@@ -95,6 +99,9 @@
public class VibrationSettingsTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final int UID = 1;
private static final int VIRTUAL_DISPLAY_ID = 1;
private static final String SYSUI_PACKAGE_NAME = "sysui";
@@ -606,6 +613,47 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
+ public void shouldIgnoreVibration_withKeyboardSettingsOff_shouldIgnoreKeyboardVibration() {
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
+ setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0);
+
+ // Keyboard touch ignored.
+ assertVibrationIgnoredForAttributes(
+ new VibrationAttributes.Builder()
+ .setUsage(USAGE_TOUCH)
+ .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+ .build(),
+ Vibration.Status.IGNORED_FOR_SETTINGS);
+
+ // General touch and keyboard touch with bypass flag not ignored.
+ assertVibrationNotIgnoredForUsage(USAGE_TOUCH);
+ assertVibrationNotIgnoredForAttributes(
+ new VibrationAttributes.Builder()
+ .setUsage(USAGE_TOUCH)
+ .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+ .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
+ .build());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
+ public void shouldIgnoreVibration_withKeyboardSettingsOn_shouldNotIgnoreKeyboardVibration() {
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+ setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1);
+
+ // General touch ignored.
+ assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
+
+ // Keyboard touch not ignored.
+ assertVibrationNotIgnoredForAttributes(
+ new VibrationAttributes.Builder()
+ .setUsage(USAGE_TOUCH)
+ .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+ .build());
+ }
+
+ @Test
public void shouldIgnoreVibrationFromVirtualDisplays_displayNonVirtual_neverIgnored() {
// Vibrations from the primary display is never ignored regardless of the creation and
// removal of virtual displays and of the changes of apps running on virtual displays.
@@ -895,6 +943,14 @@
mVibrationSettings.shouldIgnoreVibration(callerInfo));
}
+ private void assertVibrationIgnoredForAttributes(VibrationAttributes attrs,
+ Vibration.Status expectedStatus) {
+ Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID,
+ Display.DEFAULT_DISPLAY, null, null);
+ assertEquals(errorMessageForAttributes(attrs), expectedStatus,
+ mVibrationSettings.shouldIgnoreVibration(callerInfo));
+ }
+
private void assertVibrationNotIgnoredForUsage(@VibrationAttributes.Usage int usage) {
assertVibrationNotIgnoredForUsageAndFlags(usage, /* flags= */ 0);
}
@@ -919,10 +975,21 @@
mVibrationSettings.shouldIgnoreVibration(callerInfo));
}
+ private void assertVibrationNotIgnoredForAttributes(VibrationAttributes attrs) {
+ Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID,
+ Display.DEFAULT_DISPLAY, null, null);
+ assertNull(errorMessageForAttributes(attrs),
+ mVibrationSettings.shouldIgnoreVibration(callerInfo));
+ }
+
private String errorMessageForUsage(int usage) {
return "Error for usage " + VibrationAttributes.usageToString(usage);
}
+ private String errorMessageForAttributes(VibrationAttributes attrs) {
+ return "Error for attributes " + attrs;
+ }
+
private void setDefaultIntensity(@Vibrator.VibrationIntensity int intensity) {
when(mVibrationConfigMock.getDefaultVibrationIntensity(anyInt())).thenReturn(intensity);
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 40e0e84..3dfaed6 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -82,6 +82,7 @@
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.util.ArraySet;
import android.util.SparseArray;
@@ -89,7 +90,7 @@
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
-import android.view.flags.FeatureFlags;
+import android.view.flags.Flags;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
@@ -155,6 +156,8 @@
@Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private VibratorManagerService.NativeWrapper mNativeWrapperMock;
@Mock
@@ -175,8 +178,6 @@
private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock;
@Mock
private AudioManager mAudioManagerMock;
- @Mock
- private FeatureFlags mViewFeatureFlags;
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
@@ -326,8 +327,7 @@
HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
Resources resources, VibratorInfo vibratorInfo) {
return new HapticFeedbackVibrationProvider(
- resources, vibratorInfo, mHapticFeedbackVibrationMap,
- mViewFeatureFlags);
+ resources, vibratorInfo, mHapticFeedbackVibrationMap);
}
});
return mService;
@@ -1354,7 +1354,7 @@
denyPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
denyPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING);
// Flag override to enable the scroll feedack constants to bypass interruption policies.
- when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(true);
+ mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API);
mHapticFeedbackVibrationMap.put(
HapticFeedbackConstants.SCROLL_TICK,
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 8e7ba70..dd7dec0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -130,12 +130,22 @@
// verify if back animation would start.
assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation());
- // reset drawing status
+ // reset drawing status to test translucent activity
backNavigationInfo.onBackNavigationFinished(false);
mBackNavigationController.clearBackAnimations();
- topTask.forAllWindows(w -> {
- makeWindowVisibleAndDrawn(w);
- }, true);
+ final ActivityRecord topActivity = topTask.getTopMostActivity();
+ makeWindowVisibleAndDrawn(topActivity.findMainWindow());
+ // simulate translucent
+ topActivity.setOccludesParent(false);
+ backNavigationInfo = startBackNavigation();
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
+
+ // reset drawing status to test keyguard occludes
+ topActivity.setOccludesParent(true);
+ backNavigationInfo.onBackNavigationFinished(false);
+ mBackNavigationController.clearBackAnimations();
+ makeWindowVisibleAndDrawn(topActivity.findMainWindow());
setupKeyguardOccluded();
backNavigationInfo = startBackNavigation();
assertThat(typeToString(backNavigationInfo.getType()))
@@ -201,9 +211,7 @@
// reset drawing status
backNavigationInfo.onBackNavigationFinished(false);
mBackNavigationController.clearBackAnimations();
- testCase.recordFront.forAllWindows(w -> {
- makeWindowVisibleAndDrawn(w);
- }, true);
+ makeWindowVisibleAndDrawn(testCase.recordFront.findMainWindow());
setupKeyguardOccluded();
backNavigationInfo = startBackNavigation();
assertThat(typeToString(backNavigationInfo.getType()))
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 233a207..6a738be 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -18,16 +18,20 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.utils.LastCallVerifier.lastCall;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
import android.graphics.Rect;
@@ -35,8 +39,9 @@
import android.view.SurfaceControl;
import android.view.SurfaceSession;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.testutils.StubTransaction;
+import com.android.server.wm.utils.MockAnimationAdapter;
+import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -118,102 +123,147 @@
}
}
- private MockSurfaceBuildingContainer mHost;
- private Dimmer mDimmer;
- private SurfaceControl.Transaction mTransaction;
- private Dimmer.SurfaceAnimatorStarter mSurfaceAnimatorStarter;
+ static class MockAnimationAdapterFactory extends SmoothDimmer.AnimationAdapterFactory {
+ public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec,
+ SurfaceAnimationRunner runner) {
+ return sTestAnimation;
+ }
+ }
- private static class SurfaceAnimatorStarterImpl implements Dimmer.SurfaceAnimatorStarter {
+ private static class SurfaceAnimatorStarterImpl implements LegacyDimmer.SurfaceAnimatorStarter {
@Override
public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
- AnimationAdapter anim, boolean hidden, @AnimationType int type) {
+ AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type) {
surfaceAnimator.mStaticAnimationFinishedCallback.onAnimationFinished(type, anim);
}
}
+ private MockSurfaceBuildingContainer mHost;
+ private Dimmer mDimmer;
+ private SurfaceControl.Transaction mTransaction;
+ private TestWindowContainer mChild;
+ private static AnimationAdapter sTestAnimation;
+ private static LegacyDimmer.SurfaceAnimatorStarter sSurfaceAnimatorStarter;
+
@Before
public void setUp() throws Exception {
mHost = new MockSurfaceBuildingContainer(mWm);
- mSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl());
mTransaction = spy(StubTransaction.class);
- mDimmer = new Dimmer(mHost, mSurfaceAnimatorStarter);
+ mChild = new TestWindowContainer(mWm);
+ if (Flags.dimmerRefactor()) {
+ sTestAnimation = spy(new MockAnimationAdapter());
+ mDimmer = new SmoothDimmer(mHost, new MockAnimationAdapterFactory());
+ } else {
+ sSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl());
+ mDimmer = new LegacyDimmer(mHost, sSurfaceAnimatorStarter);
+ }
}
@Test
public void testUpdateDimsAppliesCrop() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
+ mHost.addChild(mChild, 0);
- final float alpha = 0.8f;
- mDimmer.dimAbove(child, alpha);
+ mDimmer.adjustAppearance(mChild, 1, 1);
+ mDimmer.adjustRelativeLayer(mChild, -1);
int width = 100;
int height = 300;
- mDimmer.mDimState.mDimBounds.set(0, 0, width, height);
+ mDimmer.getDimBounds().set(0, 0, width, height);
mDimmer.updateDims(mTransaction);
- verify(mTransaction).setWindowCrop(getDimLayer(), width, height);
- verify(mTransaction).show(getDimLayer());
+ verify(mTransaction).setWindowCrop(mDimmer.getDimLayer(), width, height);
+ verify(mTransaction).show(mDimmer.getDimLayer());
}
@Test
- public void testDimAboveWithChildCreatesSurfaceAboveChild() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
+ public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() {
+ assumeTrue(Flags.dimmerRefactor());
+ final float alpha = 0.7f;
+ final int blur = 50;
+ mHost.addChild(mChild, 0);
+ mDimmer.adjustAppearance(mChild, alpha, blur);
+ mDimmer.adjustRelativeLayer(mChild, -1);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
- final float alpha = 0.8f;
- mDimmer.dimAbove(child, alpha);
- SurfaceControl dimLayer = getDimLayer();
+ assertNotNull("Dimmer should have created a surface", dimLayer);
+
+ mDimmer.updateDims(mTransaction);
+ verify(sTestAnimation).startAnimation(eq(dimLayer), eq(mTransaction),
+ anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+ verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, -1);
+ verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha);
+ verify(mTransaction).setBackgroundBlurRadius(dimLayer, blur);
+ }
+
+ @Test
+ public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Legacy() {
+ assumeFalse(Flags.dimmerRefactor());
+ final float alpha = 0.7f;
+ mHost.addChild(mChild, 0);
+ mDimmer.adjustAppearance(mChild, alpha, 20);
+ mDimmer.adjustRelativeLayer(mChild, -1);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
assertNotNull("Dimmer should have created a surface", dimLayer);
verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha);
- verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, 1);
+ verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1);
}
@Test
- public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
+ public void testDimBelowWithChildSurfaceDestroyedWhenReset_Smooth() {
+ assumeTrue(Flags.dimmerRefactor());
+ mHost.addChild(mChild, 0);
final float alpha = 0.8f;
- mDimmer.dimBelow(child, alpha, 0);
- SurfaceControl dimLayer = getDimLayer();
-
- assertNotNull("Dimmer should have created a surface", dimLayer);
-
- verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha);
- verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1);
+ final int blur = 50;
+ // Dim once
+ mDimmer.adjustAppearance(mChild, alpha, blur);
+ mDimmer.adjustRelativeLayer(mChild, -1);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
+ mDimmer.updateDims(mTransaction);
+ // Reset, and don't dim
+ mDimmer.resetDimStates();
+ mDimmer.adjustRelativeLayer(mChild, -1);
+ mDimmer.updateDims(mTransaction);
+ verify(mTransaction).show(dimLayer);
+ verify(mTransaction).remove(dimLayer);
}
@Test
- public void testDimBelowWithChildSurfaceDestroyedWhenReset() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
+ public void testDimBelowWithChildSurfaceDestroyedWhenReset_Legacy() {
+ assumeFalse(Flags.dimmerRefactor());
+ mHost.addChild(mChild, 0);
final float alpha = 0.8f;
- mDimmer.dimAbove(child, alpha);
- SurfaceControl dimLayer = getDimLayer();
+ mDimmer.adjustAppearance(mChild, alpha, 20);
+ mDimmer.adjustRelativeLayer(mChild, -1);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
mDimmer.resetDimStates();
mDimmer.updateDims(mTransaction);
- verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any(
- SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
+ verify(sSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class),
+ any(SurfaceControl.Transaction.class), any(AnimationAdapter.class),
+ anyBoolean(),
eq(ANIMATION_TYPE_DIMMER));
verify(mHost.getPendingTransaction()).remove(dimLayer);
}
@Test
public void testDimBelowWithChildSurfaceNotDestroyedWhenPersisted() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
+ mHost.addChild(mChild, 0);
final float alpha = 0.8f;
- mDimmer.dimAbove(child, alpha);
- SurfaceControl dimLayer = getDimLayer();
+ final int blur = 20;
+ // Dim once
+ mDimmer.adjustAppearance(mChild, alpha, blur);
+ mDimmer.adjustRelativeLayer(mChild, -1);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
+ mDimmer.updateDims(mTransaction);
+ // Reset and dim again
mDimmer.resetDimStates();
- mDimmer.dimAbove(child, alpha);
-
+ mDimmer.adjustAppearance(mChild, alpha, blur);
+ mDimmer.adjustRelativeLayer(mChild, -1);
mDimmer.updateDims(mTransaction);
verify(mTransaction).show(dimLayer);
verify(mTransaction, never()).remove(dimLayer);
@@ -221,14 +271,13 @@
@Test
public void testDimUpdateWhileDimming() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
-
+ mHost.addChild(mChild, 0);
final float alpha = 0.8f;
- mDimmer.dimAbove(child, alpha);
- final Rect bounds = mDimmer.mDimState.mDimBounds;
+ mDimmer.adjustAppearance(mChild, alpha, 20);
+ mDimmer.adjustRelativeLayer(mChild, -1);
+ final Rect bounds = mDimmer.getDimBounds();
- SurfaceControl dimLayer = getDimLayer();
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
bounds.set(0, 0, 10, 10);
mDimmer.updateDims(mTransaction);
verify(mTransaction).setWindowCrop(dimLayer, bounds.width(), bounds.height());
@@ -242,41 +291,58 @@
}
@Test
- public void testRemoveDimImmediately() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
-
- mDimmer.dimAbove(child, 1);
- SurfaceControl dimLayer = getDimLayer();
+ public void testRemoveDimImmediately_Smooth() {
+ assumeTrue(Flags.dimmerRefactor());
+ mHost.addChild(mChild, 0);
+ mDimmer.adjustAppearance(mChild, 1, 2);
+ mDimmer.adjustRelativeLayer(mChild, -1);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
mDimmer.updateDims(mTransaction);
verify(mTransaction, times(1)).show(dimLayer);
- reset(mSurfaceAnimatorStarter);
+ reset(sTestAnimation);
mDimmer.dontAnimateExit();
mDimmer.resetDimStates();
mDimmer.updateDims(mTransaction);
- verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any(
- SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
+ verify(sTestAnimation, never()).startAnimation(
+ any(SurfaceControl.class), any(SurfaceControl.Transaction.class),
+ anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+ verify(mTransaction).remove(dimLayer);
+ }
+
+ @Test
+ public void testRemoveDimImmediately_Legacy() {
+ assumeFalse(Flags.dimmerRefactor());
+ mHost.addChild(mChild, 0);
+ mDimmer.adjustAppearance(mChild, 1, 0);
+ mDimmer.adjustRelativeLayer(mChild, -1);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
+ mDimmer.updateDims(mTransaction);
+ verify(mTransaction, times(1)).show(dimLayer);
+
+ reset(sSurfaceAnimatorStarter);
+ mDimmer.dontAnimateExit();
+ mDimmer.resetDimStates();
+ mDimmer.updateDims(mTransaction);
+ verify(sSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class),
+ any(SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
eq(ANIMATION_TYPE_DIMMER));
verify(mTransaction).remove(dimLayer);
}
@Test
- public void testDimmerWithBlurUpdatesTransaction() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
+ public void testDimmerWithBlurUpdatesTransaction_Legacy() {
+ assumeFalse(Flags.dimmerRefactor());
+ mHost.addChild(mChild, 0);
final int blurRadius = 50;
- mDimmer.dimBelow(child, 0, blurRadius);
- SurfaceControl dimLayer = getDimLayer();
+ mDimmer.adjustAppearance(mChild, 1, blurRadius);
+ mDimmer.adjustRelativeLayer(mChild, -1);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
assertNotNull("Dimmer should have created a surface", dimLayer);
verify(mHost.getPendingTransaction()).setBackgroundBlurRadius(dimLayer, blurRadius);
- verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1);
- }
-
- private SurfaceControl getDimLayer() {
- return mDimmer.mDimState.mDimLayer;
+ verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
index 6c48a69..9f43a17 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
@@ -94,6 +94,16 @@
}
@Test
+ public void test_selectiveCloneLunchRemoteTransition() {
+ final RemoteTransition transition = mock(RemoteTransition.class);
+ final SafeActivityOptions clone = new SafeActivityOptions(
+ ActivityOptions.makeRemoteTransition(transition))
+ .selectiveCloneLaunchOptions();
+
+ assertSame(clone.getOriginalOptions().getRemoteTransition(), transition);
+ }
+
+ @Test
public void test_getOptions() {
// Mock everything necessary
MockitoSession mockingSession = mockitoSession()
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 2bf1385..6235b3b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -26,7 +26,9 @@
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
@@ -1655,6 +1657,127 @@
assertEquals(frontMostTaskFragment, tf0);
}
+ @Test
+ public void testApplyTransaction_reorderToBottomOfTask() {
+ mController.unregisterOrganizer(mIOrganizer);
+ mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */);
+ final Task task = createTask(mDisplayContent);
+ // Create a non-embedded Activity at the bottom.
+ final ActivityRecord bottomActivity = new ActivityBuilder(mAtm)
+ .setTask(task)
+ .build();
+ final TaskFragment tf0 = createTaskFragment(task);
+ final TaskFragment tf1 = createTaskFragment(task);
+ // Create a non-embedded Activity at the top.
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+ .setTask(task)
+ .build();
+
+ // Ensure correct order of the children before the operation
+ assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+ assertEquals(tf1, task.getChildAt(2).asTaskFragment());
+ assertEquals(tf0, task.getChildAt(1).asTaskFragment());
+ assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+
+ // Reorder TaskFragment to bottom
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_REORDER_TO_BOTTOM_OF_TASK).build();
+ mTransaction.addTaskFragmentOperation(tf1.getFragmentToken(), operation);
+ assertApplyTransactionAllowed(mTransaction);
+
+ // Ensure correct order of the children after the operation
+ assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+ assertEquals(tf0, task.getChildAt(2).asTaskFragment());
+ assertEquals(bottomActivity, task.getChildAt(1).asActivityRecord());
+ assertEquals(tf1, task.getChildAt(0).asTaskFragment());
+ }
+
+ @Test
+ public void testApplyTransaction_reorderToTopOfTask() {
+ mController.unregisterOrganizer(mIOrganizer);
+ mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */);
+ final Task task = createTask(mDisplayContent);
+ // Create a non-embedded Activity at the bottom.
+ final ActivityRecord bottomActivity = new ActivityBuilder(mAtm)
+ .setTask(task)
+ .build();
+ final TaskFragment tf0 = createTaskFragment(task);
+ final TaskFragment tf1 = createTaskFragment(task);
+ // Create a non-embedded Activity at the top.
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+ .setTask(task)
+ .build();
+
+ // Ensure correct order of the children before the operation
+ assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+ assertEquals(tf1, task.getChildAt(2).asTaskFragment());
+ assertEquals(tf0, task.getChildAt(1).asTaskFragment());
+ assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+
+ // Reorder TaskFragment to top
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_REORDER_TO_TOP_OF_TASK).build();
+ mTransaction.addTaskFragmentOperation(tf0.getFragmentToken(), operation);
+ assertApplyTransactionAllowed(mTransaction);
+
+ // Ensure correct order of the children after the operation
+ assertEquals(tf0, task.getChildAt(3).asTaskFragment());
+ assertEquals(topActivity, task.getChildAt(2).asActivityRecord());
+ assertEquals(tf1, task.getChildAt(1).asTaskFragment());
+ assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+ }
+
+ @Test
+ public void testApplyTransaction_reorderToBottomOfTask_failsIfNotSystemOrganizer() {
+ testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
+ OP_TYPE_REORDER_TO_BOTTOM_OF_TASK);
+ }
+
+ @Test
+ public void testApplyTransaction_reorderToTopOfTask_failsIfNotSystemOrganizer() {
+ testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
+ OP_TYPE_REORDER_TO_TOP_OF_TASK);
+ }
+
+ private void testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
+ @TaskFragmentOperation.OperationType int opType) {
+ final Task task = createTask(mDisplayContent);
+ // Create a non-embedded Activity at the bottom.
+ final ActivityRecord bottomActivity = new ActivityBuilder(mAtm)
+ .setTask(task)
+ .build();
+ final TaskFragment tf0 = createTaskFragment(task);
+ final TaskFragment tf1 = createTaskFragment(task);
+ // Create a non-embedded Activity at the top.
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+ .setTask(task)
+ .build();
+
+ // Ensure correct order of the children before the operation
+ assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+ assertEquals(tf1, task.getChildAt(2).asTaskFragment());
+ assertEquals(tf0, task.getChildAt(1).asTaskFragment());
+ assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+
+ // Apply reorder transaction, which is expected to fail for non-system organizer.
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ opType).build();
+ mTransaction
+ .addTaskFragmentOperation(tf0.getFragmentToken(), operation)
+ .setErrorCallbackToken(mErrorToken);
+ assertApplyTransactionAllowed(mTransaction);
+ // The pending event will be dispatched on the handler (from requestTraversal).
+ waitHandlerIdle(mWm.mAnimationHandler);
+
+ assertTaskFragmentErrorTransaction(opType, SecurityException.class);
+
+ // Ensure no change to the order of the children after the operation
+ assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+ assertEquals(tf1, task.getChildAt(2).asTaskFragment());
+ assertEquals(tf0, task.getChildAt(1).asTaskFragment());
+ assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+ }
+
/**
* Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls
* {@link WindowOrganizerController#applyTransaction(WindowContainerTransaction)} to apply the
@@ -1782,6 +1905,19 @@
assertEquals(activityToken, change.getActivityToken());
}
+ /** Setups an embedded TaskFragment. */
+ private TaskFragment createTaskFragment(Task task) {
+ final IBinder token = new Binder();
+ TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setFragmentToken(token)
+ .setOrganizer(mOrganizer)
+ .createActivityCount(1)
+ .build();
+ mWindowOrganizerController.mLaunchTaskFragments.put(token, taskFragment);
+ return taskFragment;
+ }
+
/** Setups an embedded TaskFragment in a PIP Task. */
private void setupTaskFragmentInPip() {
mTaskFragment = new TaskFragmentBuilder(mAtm)
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 5205bb0..7822071 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -37,6 +37,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_PARENT_TASK;
+import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_TASK_FRAGMENT;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -687,4 +689,24 @@
tf0.setIsolatedNav(true);
assertTrue(tf0.isIsolatedNav());
}
+
+ @Test
+ public void testGetDimBounds() {
+ final Task task = mTaskFragment.getTask();
+ final Rect taskBounds = task.getBounds();
+ mTaskFragment.setBounds(taskBounds.left, taskBounds.top, taskBounds.left + 10,
+ taskBounds.top + 10);
+ final Rect taskFragmentBounds = mTaskFragment.getBounds();
+
+ // Return Task bounds if dimming on parent Task.
+ final Rect dimBounds = new Rect();
+ mTaskFragment.setEmbeddedDimArea(EMBEDDED_DIM_AREA_PARENT_TASK);
+ mTaskFragment.getDimBounds(dimBounds);
+ assertEquals(taskBounds, dimBounds);
+
+ // Return TF bounds by default.
+ mTaskFragment.setEmbeddedDimArea(EMBEDDED_DIM_AREA_TASK_FRAGMENT);
+ mTaskFragment.getDimBounds(dimBounds);
+ assertEquals(taskFragmentBounds, dimBounds);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
index 75a8dd8..085eddd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.platform.test.annotations.Presubmit;
@@ -94,10 +95,14 @@
public void testRemoveFinishingInvisibleActivityFromUnknown() {
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
- activity.finishing = true;
- activity.setVisibleRequested(true);
- activity.setVisibility(false);
+ assertFalse(mDisplayContent.mUnknownAppVisibilityController.allResolved());
+ activity.makeFinishingLocked();
assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved());
+
+ mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
+ assertTrue(mDisplayContent.mUnknownAppVisibilityController.isVisibilityUnknown(activity));
+ activity.setState(ActivityRecord.State.STOPPED, "test");
+ assertFalse(mDisplayContent.mUnknownAppVisibilityController.isVisibilityUnknown(activity));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index e86fc36..eaeb804 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -100,6 +100,9 @@
import com.android.compatibility.common.util.AdoptShellPermissionsRule;
import com.android.internal.os.IResultReceiver;
import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerService.WindowContainerInfo;
+
+import com.google.common.truth.Expect;
import org.junit.Rule;
import org.junit.Test;
@@ -125,6 +128,9 @@
InstrumentationRegistry.getInstrumentation().getUiAutomation(),
ADD_TRUSTED_DISPLAY);
+ @Rule
+ public Expect mExpect = Expect.create();
+
@Test
public void testIsRequestedOrientationMapped() {
mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ true,
@@ -674,64 +680,68 @@
@Test
public void testGetTaskWindowContainerTokenForLaunchCookie_nullCookie() {
- WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(null);
- assertThat(wct).isNull();
+ WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(null);
+ assertThat(wci).isNull();
}
@Test
public void testGetTaskWindowContainerTokenForLaunchCookie_invalidCookie() {
Binder cookie = new Binder("test cookie");
- WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
- assertThat(wct).isNull();
+ WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+ assertThat(wci).isNull();
final ActivityRecord testActivity = new ActivityBuilder(mAtm)
.setCreateTask(true)
.build();
- wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
- assertThat(wct).isNull();
+ wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+ assertThat(wci).isNull();
}
@Test
public void testGetTaskWindowContainerTokenForLaunchCookie_validCookie() {
final Binder cookie = new Binder("ginger cookie");
final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
- setupActivityWithLaunchCookie(cookie, launchRootTask);
+ final int uid = 123;
+ setupActivityWithLaunchCookie(cookie, launchRootTask, uid);
- WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
- assertThat(wct).isEqualTo(launchRootTask);
+ WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+ mExpect.that(wci.getToken()).isEqualTo(launchRootTask);
+ mExpect.that(wci.getUid()).isEqualTo(uid);
}
@Test
public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies() {
final Binder cookie1 = new Binder("ginger cookie");
final WindowContainerToken launchRootTask1 = mock(WindowContainerToken.class);
- setupActivityWithLaunchCookie(cookie1, launchRootTask1);
+ final int uid1 = 123;
+ setupActivityWithLaunchCookie(cookie1, launchRootTask1, uid1);
setupActivityWithLaunchCookie(new Binder("choc chip cookie"),
- mock(WindowContainerToken.class));
+ mock(WindowContainerToken.class), /* uid= */ 456);
setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
- mock(WindowContainerToken.class));
+ mock(WindowContainerToken.class), /* uid= */ 789);
- WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie1);
- assertThat(wct).isEqualTo(launchRootTask1);
+ WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie1);
+ mExpect.that(wci.getToken()).isEqualTo(launchRootTask1);
+ mExpect.that(wci.getUid()).isEqualTo(uid1);
}
@Test
public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies_noneValid() {
setupActivityWithLaunchCookie(new Binder("ginger cookie"),
- mock(WindowContainerToken.class));
+ mock(WindowContainerToken.class), /* uid= */ 123);
setupActivityWithLaunchCookie(new Binder("choc chip cookie"),
- mock(WindowContainerToken.class));
+ mock(WindowContainerToken.class), /* uid= */ 456);
setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
- mock(WindowContainerToken.class));
+ mock(WindowContainerToken.class), /* uid= */ 789);
- WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(
+ WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(
new Binder("some other cookie"));
- assertThat(wct).isNull();
+ assertThat(wci).isNull();
}
@Test
@@ -778,17 +788,18 @@
}
@Test
- public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() {
+ public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerInfo() {
WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
Task task = createTask(mDefaultDisplay);
ActivityRecord activityRecord = createActivityRecord(task);
- ContentRecordingSession session = ContentRecordingSession.createTaskSession(
- activityRecord.mLaunchCookie);
+ ContentRecordingSession session =
+ ContentRecordingSession.createTaskSession(activityRecord.mLaunchCookie);
wmInternal.setContentRecordingSession(session);
- assertThat(session.getTokenToRecord()).isEqualTo(
- task.mRemoteToken.toWindowContainerToken().asBinder());
+ mExpect.that(session.getTokenToRecord())
+ .isEqualTo(task.mRemoteToken.toWindowContainerToken().asBinder());
+ mExpect.that(session.getTargetUid()).isEqualTo(activityRecord.getUid());
}
@Test
@@ -1010,12 +1021,12 @@
}
}
- private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
+ private void setupActivityWithLaunchCookie(
+ IBinder launchCookie, WindowContainerToken wct, int uid) {
final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
when(remoteToken.toWindowContainerToken()).thenReturn(wct);
- final ActivityRecord testActivity = new ActivityBuilder(mAtm)
- .setCreateTask(true)
- .build();
+ final ActivityRecord testActivity =
+ new ActivityBuilder(mAtm).setCreateTask(true).setUid(uid).build();
testActivity.mLaunchCookie = launchCookie;
testActivity.getTask().mRemoteToken = remoteToken;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 7168670..0b77fd8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -40,6 +40,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.wm.testing.Assert.assertThrows;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowContainer.SYNC_STATE_READY;
@@ -58,6 +59,7 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
@@ -77,11 +79,13 @@
import android.view.Display;
import android.view.SurfaceControl;
import android.view.WindowInsets;
+import android.window.ITaskFragmentOrganizer;
import android.window.ITaskOrganizer;
import android.window.IWindowContainerTransactionCallback;
import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
+import android.window.TaskFragmentOrganizer;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -579,6 +583,87 @@
}
@Test
+ public void testTaskFragmentHiddenAndFocusableChanges() {
+ removeGlobalMinSizeRestriction();
+ final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+
+ final WindowContainerTransaction t = new WindowContainerTransaction();
+ final TaskFragmentOrganizer organizer =
+ createTaskFragmentOrganizer(t, true /* isSystemOrganizer */);
+
+ final IBinder token = new Binder();
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(rootTask)
+ .setFragmentToken(token)
+ .setOrganizer(organizer)
+ .createActivityCount(1)
+ .build();
+
+ // Should be visible and focusable initially.
+ assertTrue(rootTask.shouldBeVisible(null));
+ assertTrue(taskFragment.shouldBeVisible(null));
+ assertTrue(taskFragment.isFocusable());
+ assertTrue(taskFragment.isTopActivityFocusable());
+
+ // Apply transaction to the TaskFragment hidden and not focusable.
+ t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true);
+ t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false);
+ mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
+ t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
+ false /* shouldApplyIndependently */);
+
+ // Should be not visible and not focusable after the transaction.
+ assertFalse(taskFragment.shouldBeVisible(null));
+ assertFalse(taskFragment.isFocusable());
+ assertFalse(taskFragment.isTopActivityFocusable());
+
+ // Apply transaction to the TaskFragment not hidden and focusable.
+ t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), false);
+ t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), true);
+ mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
+ t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
+ false /* shouldApplyIndependently */);
+
+ // Should be visible and focusable after the transaction.
+ assertTrue(taskFragment.shouldBeVisible(null));
+ assertTrue(taskFragment.isFocusable());
+ assertTrue(taskFragment.isTopActivityFocusable());
+ }
+
+ @Test
+ public void testTaskFragmentHiddenAndFocusableChanges_throwsWhenNotSystemOrganizer() {
+ removeGlobalMinSizeRestriction();
+ final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+
+ final WindowContainerTransaction t = new WindowContainerTransaction();
+ final TaskFragmentOrganizer organizer =
+ createTaskFragmentOrganizer(t, false /* isSystemOrganizer */);
+
+ final IBinder token = new Binder();
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(rootTask)
+ .setFragmentToken(token)
+ .setOrganizer(organizer)
+ .createActivityCount(1)
+ .build();
+
+ assertTrue(rootTask.shouldBeVisible(null));
+ assertTrue(taskFragment.shouldBeVisible(null));
+
+ t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true);
+ t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false);
+
+ // Non-system organizers are not allow to update the hidden and focusable states.
+ assertThrows(SecurityException.class, () ->
+ mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
+ t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
+ false /* shouldApplyIndependently */)
+ );
+ }
+
+ @Test
public void testContainerTranslucentChanges() {
removeGlobalMinSizeRestriction();
final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
@@ -1600,4 +1685,20 @@
assertTrue(taskIds.contains(expectedTasks[i].mTaskId));
}
}
+
+ @NonNull
+ private TaskFragmentOrganizer createTaskFragmentOrganizer(
+ @NonNull WindowContainerTransaction t, boolean isSystemOrganizer) {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final ITaskFragmentOrganizer organizerInterface =
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
+ mWm.mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController
+ .registerOrganizerInternal(
+ ITaskFragmentOrganizer.Stub.asInterface(
+ organizer.getOrganizerToken().asBinder()),
+ isSystemOrganizer);
+ t.setTaskFragmentOrganizer(organizerInterface);
+
+ return organizer;
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java b/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java
new file mode 100644
index 0000000..320d094
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java
@@ -0,0 +1,68 @@
+/*
+ * 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.utils;
+
+import org.mockito.internal.verification.VerificationModeFactory;
+import org.mockito.internal.verification.api.VerificationData;
+import org.mockito.invocation.Invocation;
+import org.mockito.invocation.MatchableInvocation;
+import org.mockito.verification.VerificationMode;
+
+import java.util.List;
+
+/**
+ * Verifier to check that the last call of a method received the expected argument
+ */
+public class LastCallVerifier implements VerificationMode {
+
+ /**
+ * Allows comparing the expected invocation with the last invocation on the same method
+ */
+ public static LastCallVerifier lastCall() {
+ return new LastCallVerifier();
+ }
+
+ @Override
+ public void verify(VerificationData data) {
+ List<Invocation> invocations = data.getAllInvocations();
+ MatchableInvocation target = data.getTarget();
+ for (int i = invocations.size() - 1; i >= 0; i--) {
+ final Invocation invocation = invocations.get(i);
+ if (target.hasSameMethod(invocation)) {
+ if (target.matches(invocation)) {
+ return;
+ } else {
+ throw new LastCallMismatch(target.getInvocation(), invocation, invocations);
+ }
+ }
+ }
+ throw new RuntimeException(target + " never invoked");
+ }
+
+ @Override
+ public VerificationMode description(String description) {
+ return VerificationModeFactory.description(this, description);
+ }
+
+ static class LastCallMismatch extends RuntimeException {
+ LastCallMismatch(
+ Invocation expected, Invocation received, List<Invocation> allInvocations) {
+ super("Expected invocation " + expected + " but received " + received
+ + " as the last invocation.\nAll registered invocations:\n" + allInvocations);
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java b/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java
new file mode 100644
index 0000000..1a66970a
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java
@@ -0,0 +1,64 @@
+/*
+ * 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.utils;
+
+import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.wm.AnimationAdapter;
+import com.android.server.wm.SurfaceAnimator;
+
+import java.io.PrintWriter;
+
+/**
+ * An empty animation adapter which just executes the finish callback
+ */
+public class MockAnimationAdapter implements AnimationAdapter {
+
+ @Override
+ public boolean getShowWallpaper() {
+ return false;
+ }
+
+ @Override
+ public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
+ int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+ // As the animation won't run, finish it immediately
+ finishCallback.onAnimationFinished(0, null);
+ }
+
+ @Override
+ public void onAnimationCancelled(SurfaceControl animationLeash) {}
+
+ @Override
+ public long getDurationHint() {
+ return 0;
+ }
+
+ @Override
+ public long getStatusBarTransitionsStartTime() {
+ return 0;
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {}
+
+ @Override
+ public void dumpDebug(ProtoOutputStream proto) {}
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index f3bf026..55b5d11 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -53,6 +53,7 @@
import android.app.usage.BroadcastResponseStatsList;
import android.app.usage.ConfigurationStats;
import android.app.usage.EventStats;
+import android.app.usage.Flags;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageEvents.Event;
@@ -200,6 +201,7 @@
static final int MSG_ON_START = 7;
static final int MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK = 8;
static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED = 9;
+ static final int MSG_UID_REMOVED = 10;
private final Object mLock = new Object();
private Handler mHandler;
@@ -378,11 +380,10 @@
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_STARTED);
- getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
- null, mHandler);
-
+ getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL,
+ filter, null, /* Handler scheduler */ null);
getContext().registerReceiverAsUser(new UidRemovedReceiver(), UserHandle.ALL,
- new IntentFilter(ACTION_UID_REMOVED), null, mHandler);
+ new IntentFilter(ACTION_UID_REMOVED), null, /* Handler scheduler */ null);
mRealTimeSnapshot = SystemClock.elapsedRealtime();
mSystemTimeSnapshot = System.currentTimeMillis();
@@ -614,7 +615,6 @@
if (Intent.ACTION_USER_REMOVED.equals(action)) {
if (userId >= 0) {
mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget();
- mResponseStatsTracker.onUserRemoved(userId);
}
} else if (Intent.ACTION_USER_STARTED.equals(action)) {
if (userId >= 0) {
@@ -632,9 +632,7 @@
return;
}
- synchronized (mLock) {
- mResponseStatsTracker.onUidRemoved(uid);
- }
+ mHandler.obtainMessage(MSG_UID_REMOVED, uid, 0).sendToTarget();
}
}
@@ -1301,6 +1299,8 @@
mPendingLaunchTimeChangePackages.remove(userId);
}
mAppStandby.onUserRemoved(userId);
+ mResponseStatsTracker.onUserRemoved(userId);
+
// Cancel any scheduled jobs for this user since the user is being removed.
UsageStatsIdleService.cancelPruneJob(getContext(), userId);
UsageStatsIdleService.cancelUpdateMappingsJob(getContext(), userId);
@@ -2037,6 +2037,9 @@
case MSG_REMOVE_USER:
onUserRemoved(msg.arg1);
break;
+ case MSG_UID_REMOVED:
+ mResponseStatsTracker.onUidRemoved(msg.arg1);
+ break;
case MSG_PACKAGE_REMOVED:
onPackageRemoved(msg.arg1, (String) msg.obj);
break;
@@ -2125,7 +2128,8 @@
private boolean canReportUsageStats() {
if (isCallingUidSystem()) {
- return true; // System UID can always report UsageStats
+ // System UID can always report UsageStats
+ return true;
}
return getContext().checkCallingPermission(Manifest.permission.REPORT_USAGE_STATS)
@@ -2621,9 +2625,12 @@
return;
}
- if (!canReportUsageStats()) {
- throw new SecurityException("Only the system or holders of the REPORT_USAGE_STATS"
- + " permission are allowed to call reportChooserSelection");
+ if (Flags.reportUsageStatsPermission()) {
+ if (!canReportUsageStats()) {
+ throw new SecurityException(
+ "Only the system or holders of the REPORT_USAGE_STATS"
+ + " permission are allowed to call reportChooserSelection");
+ }
}
// Verify if this package exists before reporting an event for it.
@@ -2643,9 +2650,17 @@
@Override
public void reportUserInteraction(String packageName, int userId) {
Objects.requireNonNull(packageName);
- if (!canReportUsageStats()) {
- throw new SecurityException("Only the system or holders of the REPORT_USAGE_STATS"
- + " permission are allowed to call reportUserInteraction");
+ if (Flags.reportUsageStatsPermission()) {
+ if (!canReportUsageStats()) {
+ throw new SecurityException(
+ "Only the system or holders of the REPORT_USAGE_STATS"
+ + " permission are allowed to call reportUserInteraction");
+ }
+ } else {
+ if (!isCallingUidSystem()) {
+ throw new SecurityException("Only system is allowed to call"
+ + " reportUserInteraction");
+ }
}
final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime());
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 5d2f27d..35e2fcf 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -37,8 +37,8 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.hardware.usb.IUsbManager;
import android.hardware.usb.IDisplayPortAltModeInfoListener;
+import android.hardware.usb.IUsbManager;
import android.hardware.usb.IUsbOperationInternal;
import android.hardware.usb.ParcelableUsbPort;
import android.hardware.usb.UsbAccessory;
@@ -46,7 +46,6 @@
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
-import android.hardware.usb.DisplayPortAltModeInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
@@ -1215,6 +1214,20 @@
mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
"", 0);
}
+ } else if ("enable-usb-data".equals(args[0]) && args.length == 3) {
+ final String portId = args[1];
+ final boolean enable = Boolean.parseBoolean(args[2]);
+
+ if (mPortManager != null) {
+ for (UsbPort p : mPortManager.getPorts()) {
+ if (p.getId().equals(portId)) {
+ int res = p.enableUsbData(enable);
+ Slog.i(TAG, "enableUsbData " + portId + " status " + res);
+ break;
+ }
+ }
+ }
+
} else if ("ports".equals(args[0]) && args.length == 1) {
if (mPortManager != null) {
mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
@@ -1293,6 +1306,11 @@
pw.println("reset-displayport-status can also be used in order to set");
pw.println("the DisplayPortInfo to default values.");
pw.println();
+ pw.println("Example enableUsbData");
+ pw.println("This dumpsys command functions for both simulated and real ports.");
+ pw.println(" dumpsys usb enable-usb-data \"matrix\" true");
+ pw.println(" dumpsys usb enable-usb-data \"matrix\" false");
+ pw.println();
pw.println("Example USB device descriptors:");
pw.println(" dumpsys usb dump-descriptors -dump-short");
pw.println(" dumpsys usb dump-descriptors -dump-tree");
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index 42b08e3..93b5a40 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
@@ -20,6 +20,8 @@
import static android.Manifest.permission.LOG_COMPAT_CHANGE;
import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
import static android.Manifest.permission.RECORD_AUDIO;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
@@ -753,11 +755,21 @@
"Failed to obtain permission RECORD_AUDIO for identity "
+ mVoiceInteractorIdentity);
}
- mAppOpsManager.noteOpNoThrow(
- AppOpsPolicy.getVoiceActivationOp(),
- mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
- mVoiceInteractorIdentity.attributionTag,
- HOTWORD_DETECTION_OP_MESSAGE);
+ int opMode = mAppOpsManager.unsafeCheckOpNoThrow(
+ AppOpsManager.opToPublicName(AppOpsPolicy.getVoiceActivationOp()),
+ mVoiceInteractorIdentity.uid,
+ mVoiceInteractorIdentity.packageName);
+ if (opMode == MODE_DEFAULT || opMode == MODE_ALLOWED) {
+ mAppOpsManager.noteOpNoThrow(
+ AppOpsPolicy.getVoiceActivationOp(),
+ mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+ mVoiceInteractorIdentity.attributionTag,
+ HOTWORD_DETECTION_OP_MESSAGE);
+ } else {
+ throw new SecurityException(
+ "The app op OP_RECEIVE_SANDBOX_TRIGGER_AUDIO is denied for "
+ + "identity" + mVoiceInteractorIdentity);
+ }
} else {
enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
RECORD_AUDIO, HOTWORD_DETECTION_OP_MESSAGE);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 138e575..1c689d0 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -58,6 +58,7 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import android.os.PermissionEnforcer;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
@@ -67,6 +68,7 @@
import android.os.ShellCallback;
import android.os.Trace;
import android.os.UserHandle;
+import android.permission.flags.Flags;
import android.provider.Settings;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
@@ -1286,6 +1288,17 @@
}
}
+ // Enforce permissions that are flag controlled. The flag value decides if the permission
+ // should be enforced.
+ private void initAndVerifyDetector_enforcePermissionWithFlags() {
+ PermissionEnforcer enforcer = mContext.getSystemService(PermissionEnforcer.class);
+ if (Flags.voiceActivationPermissionApis()) {
+ enforcer.enforcePermission(
+ android.Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO,
+ getCallingPid(), getCallingUid());
+ }
+ }
+
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION)
@Override
public void initAndVerifyDetector(
@@ -1295,7 +1308,13 @@
@NonNull IBinder token,
IHotwordRecognitionStatusCallback callback,
int detectorType) {
+ // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+ // {@link #initAndVerifyDetector(Identity, PersistableBundle, ShareMemory, IBinder,
+ // IHotwordRecognitionStatusCallback, int)}
+ // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully
+ // launched.
super.initAndVerifyDetector_enforcePermission();
+ initAndVerifyDetector_enforcePermissionWithFlags();
synchronized (this) {
enforceIsCurrentVoiceInteractionService();
diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java
index 971fc78..e20e4d2 100644
--- a/telephony/java/android/telephony/BarringInfo.java
+++ b/telephony/java/android/telephony/BarringInfo.java
@@ -202,6 +202,24 @@
&& mConditionalBarringTimeSeconds == other.mConditionalBarringTimeSeconds;
}
+ private static String barringTypeToString(@BarringType int barringType) {
+ return switch (barringType) {
+ case BARRING_TYPE_NONE -> "NONE";
+ case BARRING_TYPE_CONDITIONAL -> "CONDITIONAL";
+ case BARRING_TYPE_UNCONDITIONAL -> "UNCONDITIONAL";
+ case BARRING_TYPE_UNKNOWN -> "UNKNOWN";
+ default -> "UNKNOWN(" + barringType + ")";
+ };
+ }
+
+ @Override
+ public String toString() {
+ return "BarringServiceInfo {mBarringType=" + barringTypeToString(mBarringType)
+ + ", mIsConditionallyBarred=" + mIsConditionallyBarred
+ + ", mConditionalBarringFactor=" + mConditionalBarringFactor
+ + ", mConditionalBarringTimeSeconds=" + mConditionalBarringTimeSeconds + "}";
+ }
+
/** @hide */
public BarringServiceInfo(Parcel p) {
mBarringType = p.readInt();
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 63e91ad..7a0bf90 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -10523,6 +10523,8 @@
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
"LTE", new int[]{3731, 5965, 8618, 11179, 13384});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+ "LTE_CA", new int[]{3831, 6065, 8718, 11379, 13484});
+ auto_data_switch_rat_signal_score_string_bundle.putIntArray(
"NR_SA", new int[]{5288, 6795, 6955, 7562, 9713});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
"NR_NSA", new int[]{5463, 6827, 8029, 9007, 9428});
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 90fa69f..73220c3 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -15033,15 +15033,6 @@
@TestApi
public static final int HAL_SERVICE_IMS = 7;
- /**
- * HAL service type that supports the HAL APIs implementation of IRadioSatellite
- * {@link RadioSatelliteProxy}
- * @hide
- */
- @TestApi
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
- public static final int HAL_SERVICE_SATELLITE = 8;
-
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"HAL_SERVICE_"},
@@ -15054,7 +15045,6 @@
HAL_SERVICE_SIM,
HAL_SERVICE_VOICE,
HAL_SERVICE_IMS,
- HAL_SERVICE_SATELLITE
})
public @interface HalService {}
diff --git a/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl b/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl
new file mode 100644
index 0000000..54cab48
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.telephony.satellite;
+
+import android.telephony.satellite.NtnSignalStrength;
+
+/**
+ * Interface for non-terrestrial signal strength notify callback.
+ * @hide
+ */
+oneway interface INtnSignalStrengthCallback {
+ /**
+ * Called when NTN signal strength changes.
+ * @param ntnSignalStrength The new NTN signal strength.
+ */
+ void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength);
+}
diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl b/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl
new file mode 100644
index 0000000..a79cb69
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.telephony.satellite;
+
+parcelable NtnSignalStrength;
diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.java b/telephony/java/android/telephony/satellite/NtnSignalStrength.java
new file mode 100644
index 0000000..16d7654
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.java
@@ -0,0 +1,149 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.telephony.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * NTN signal strength related information.
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public final class NtnSignalStrength implements Parcelable {
+ /** Non-terrestrial network signal strength is not available. */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final int NTN_SIGNAL_STRENGTH_NONE = 0;
+ /** Non-terrestrial network signal strength is poor. */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final int NTN_SIGNAL_STRENGTH_POOR = 1;
+ /** Non-terrestrial network signal strength is moderate. */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final int NTN_SIGNAL_STRENGTH_MODERATE = 2;
+ /** Non-terrestrial network signal strength is good. */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final int NTN_SIGNAL_STRENGTH_GOOD = 3;
+ /** Non-terrestrial network signal strength is great. */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final int NTN_SIGNAL_STRENGTH_GREAT = 4;
+ @NtnSignalStrengthLevel private int mLevel;
+
+ /** @hide */
+ @IntDef(prefix = "NTN_SIGNAL_STRENGTH_", value = {
+ NTN_SIGNAL_STRENGTH_NONE,
+ NTN_SIGNAL_STRENGTH_POOR,
+ NTN_SIGNAL_STRENGTH_MODERATE,
+ NTN_SIGNAL_STRENGTH_GOOD,
+ NTN_SIGNAL_STRENGTH_GREAT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NtnSignalStrengthLevel {}
+
+ /**
+ * Create a parcelable object to inform the current non-terrestrial signal strength
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public NtnSignalStrength(@NtnSignalStrengthLevel int level) {
+ this.mLevel = level;
+ }
+
+ /**
+ * This constructor is used to create a copy of an existing NtnSignalStrength object.
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public NtnSignalStrength(@Nullable NtnSignalStrength source) {
+ this.mLevel = (source == null) ? NTN_SIGNAL_STRENGTH_NONE : source.getLevel();
+ }
+
+ private NtnSignalStrength(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @NtnSignalStrengthLevel public int getLevel() {
+ return mLevel;
+ }
+
+ /**
+ * @return 0
+ */
+ @Override
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @param out The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+ */
+ @Override
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mLevel);
+ }
+
+ private void readFromParcel(Parcel in) {
+ mLevel = in.readInt();
+ }
+
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @NonNull public static final Creator<NtnSignalStrength> CREATOR =
+ new Creator<NtnSignalStrength>() {
+ @Override public NtnSignalStrength createFromParcel(Parcel in) {
+ return new NtnSignalStrength(in);
+ }
+
+ @Override public NtnSignalStrength[] newArray(int size) {
+ return new NtnSignalStrength[size];
+ }
+ };
+
+ @Override
+ public int hashCode() {
+ return mLevel;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+
+ NtnSignalStrength that = (NtnSignalStrength) obj;
+ return mLevel == that.mLevel;
+ }
+
+ @Override public String toString() {
+ return "NtnSignalStrength{"
+ + "mLevel=" + mLevel
+ + '}';
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java b/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java
new file mode 100644
index 0000000..4b79590
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java
@@ -0,0 +1,39 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.telephony.flags.Flags;
+
+/**
+ * A callback class for notifying satellite signal strength change.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public interface NtnSignalStrengthCallback {
+ /**
+ * Called when non-terrestrial network signal strength changes.
+ * @param ntnSignalStrength The new non-terrestrial network signal strength.
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ void onNtnSignalStrengthChanged(@NonNull NtnSignalStrength ntnSignalStrength);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 7322aeb..21d93bd 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -35,7 +35,9 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
import android.telephony.TelephonyFrameworkInitializer;
+import android.telephony.TelephonyManager;
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.ITelephony;
@@ -77,6 +79,8 @@
private static final ConcurrentHashMap<SatelliteTransmissionUpdateCallback,
ISatelliteTransmissionUpdateCallback> sSatelliteTransmissionUpdateCallbackMap =
new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap<NtnSignalStrengthCallback, INtnSignalStrengthCallback>
+ sNtnSignalStrengthCallbackMap = new ConcurrentHashMap<>();
private final int mSubId;
@@ -192,6 +196,14 @@
public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility";
/**
+ * Bundle key to get the response from
+ * {@link #requestNtnSignalStrength(Executor, OutcomeReceiver)}.
+ * @hide
+ */
+
+ public static final String KEY_NTN_SIGNAL_STRENGTH = "ntn_signal_strength";
+
+ /**
* The request was successfully processed.
*/
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1866,6 +1878,165 @@
return new HashSet<>();
}
+ /**
+ * Request to get the signal strength of the satellite connection.
+ *
+ * <p>
+ * Note: This API is specifically designed for OEM enabled satellite connectivity only.
+ * For satellite connectivity enabled using carrier roaming, please refer to
+ * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and
+ * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
+ * </p>
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback object to which the result will be delivered. If the request is
+ * successful, {@link OutcomeReceiver#onResult(Object)} will return an instance of
+ * {@link NtnSignalStrength} with a value of {@link NtnSignalStrength.NtnSignalStrengthLevel}
+ * The {@link NtnSignalStrength#NTN_SIGNAL_STRENGTH_NONE} will be returned if there is no
+ * signal strength data available.
+ * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} will return a
+ * {@link SatelliteException} with the {@link SatelliteResult}.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available or
+ * satellite is not supported.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @NonNull
+ public void requestNtnSignalStrength(@NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<NtnSignalStrength, SatelliteException> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData.containsKey(KEY_NTN_SIGNAL_STRENGTH)) {
+ NtnSignalStrength ntnSignalStrength =
+ resultData.getParcelable(KEY_NTN_SIGNAL_STRENGTH,
+ NtnSignalStrength.class);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onResult(ntnSignalStrength)));
+ } else {
+ loge("KEY_NTN_SIGNAL_STRENGTH does not exist.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(
+ SATELLITE_RESULT_REQUEST_FAILED))));
+ }
+ } else {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(resultCode))));
+ }
+ }
+ };
+ telephony.requestNtnSignalStrength(mSubId, receiver);
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("requestNtnSignalStrength() RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers for NTN signal strength changed from satellite modem.
+ *
+ * <p>
+ * Note: This API is specifically designed for OEM enabled satellite connectivity only.
+ * For satellite connectivity enabled using carrier roaming, please refer to
+ * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and
+ * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
+ * </p>
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback to handle the NTN signal strength changed event.
+ *
+ * @return The {@link SatelliteResult} result of the operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @SatelliteResult public int registerForNtnSignalStrengthChanged(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull NtnSignalStrengthCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ INtnSignalStrengthCallback internalCallback =
+ new INtnSignalStrengthCallback.Stub() {
+ @Override
+ public void onNtnSignalStrengthChanged(
+ NtnSignalStrength ntnSignalStrength) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onNtnSignalStrengthChanged(
+ ntnSignalStrength)));
+ }
+ };
+ sNtnSignalStrengthCallbackMap.put(callback, internalCallback);
+ return telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback);
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("registerForNtnSignalStrengthChanged() RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return SATELLITE_RESULT_REQUEST_FAILED;
+ }
+
+ /**
+ * Unregisters for NTN signal strength changed from satellite modem.
+ * If callback was not registered before, the request will be ignored.
+ *
+ * <p>
+ * Note: This API is specifically designed for OEM enabled satellite connectivity only.
+ * For satellite connectivity enabled using carrier roaming, please refer to
+ * {@link TelephonyManager#unregisterTelephonyCallback(TelephonyCallback)}..
+ * </p>
+ *
+ * @param callback The callback that was passed to
+ * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public void unregisterForNtnSignalStrengthChanged(@NonNull NtnSignalStrengthCallback callback) {
+ Objects.requireNonNull(callback);
+ INtnSignalStrengthCallback internalCallback =
+ sNtnSignalStrengthCallbackMap.remove(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ if (internalCallback != null) {
+ telephony.unregisterForNtnSignalStrengthChanged(mSubId, internalCallback);
+ } else {
+ loge("unregisterForNtnSignalStrengthChanged: No internal callback.");
+ }
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("unregisterForNtnSignalStrengthChanged() RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+
+ }
+
+
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
.getTelephonyServiceManager()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
index 0803a01..b7712bd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
@@ -12,15 +12,17 @@
* 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.communal.shared
+package android.telephony.satellite.stub;
-import android.appwidget.AppWidgetProviderInfo
+import android.telephony.satellite.NtnSignalStrength;
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
- val providerInfo: AppWidgetProviderInfo,
- val appWidgetId: Int,
-)
+/**
+ * Consumer pattern for a request that receives the signal strength of non-terrestrial network from
+ * the SatelliteService.
+ * @hide
+ */
+oneway interface INtnSignalStrengthConsumer {
+ void accept(in NtnSignalStrength result);
+}
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 7fda550..6b47db1 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -16,6 +16,7 @@
package android.telephony.satellite.stub;
+import android.telephony.satellite.stub.INtnSignalStrengthConsumer;
import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer;
import android.telephony.satellite.stub.ISatelliteListener;
import android.telephony.satellite.stub.SatelliteDatagram;
@@ -454,4 +455,44 @@
*/
void requestIsSatelliteEnabledForCarrier(int simSlot, in IIntegerConsumer resultCallback,
in IBooleanConsumer callback);
+
+ /**
+ * Request to get the signal strength of the satellite connection.
+ *
+ * @param resultCallback The {@link SatelliteError} result of the operation.
+ * @param callback The callback to handle the NTN signal strength changed event.
+ */
+ void requestSignalStrength(in IIntegerConsumer resultCallback,
+ in INtnSignalStrengthConsumer callback);
+
+ /**
+ * The satellite service should report the NTN signal strength via
+ * ISatelliteListener#onNtnSignalStrengthChanged when the NTN signal strength changes.
+ *
+ * @param resultCallback The callback to receive the error code result of the operation.
+ *
+ * Valid result codes returned:
+ * SatelliteResult:SATELLITE_RESULT_SUCCESS
+ * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+ * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+ * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+ * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+ */
+ void startSendingNtnSignalStrength(in IIntegerConsumer resultCallback);
+
+ /**
+ * The satellite service should stop reporting NTN signal strength to the framework. This will
+ * be called when device is screen off to save power by not letting signal strength updates to
+ * wake up application processor.
+ *
+ * @param resultCallback The callback to receive the error code result of the operation.
+ *
+ * Valid result codes returned:
+ * SatelliteResult:SATELLITE_RESULT_SUCCESS
+ * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+ * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+ * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+ * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+ */
+ void stopSendingNtnSignalStrength(in IIntegerConsumer resultCallback);
}
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index d687162..d44ddfa 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -16,6 +16,7 @@
package android.telephony.satellite.stub;
+import android.telephony.satellite.stub.NtnSignalStrength;
import android.telephony.satellite.stub.NTRadioTechnology;
import android.telephony.satellite.stub.PointingInfo;
import android.telephony.satellite.stub.SatelliteDatagram;
@@ -58,4 +59,10 @@
* @param state The current satellite modem state.
*/
void onSatelliteModemStateChanged(in SatelliteModemState state);
+
+ /**
+ * Called when NTN signal strength changes.
+ * @param ntnSignalStrength The new NTN signal strength.
+ */
+ void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength);
}
diff --git a/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl b/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl
new file mode 100644
index 0000000..f489005
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.telephony.satellite.stub;
+
+import android.telephony.satellite.stub.NtnSignalStrengthLevel;
+
+/**
+ * @hide
+ */
+parcelable NtnSignalStrength {
+ /**
+ * Non-terrestrial signal strength. The value represents the level of signal strength which can
+ * be translated to the number of signal bars.
+ */
+ NtnSignalStrengthLevel signalStrengthLevel;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl
similarity index 67%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl
index 0803a01..53b1373 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl
@@ -12,15 +12,18 @@
* 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.communal.shared
+package android.telephony.satellite.stub;
-import android.appwidget.AppWidgetProviderInfo
-
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
- val providerInfo: AppWidgetProviderInfo,
- val appWidgetId: Int,
-)
+/**
+ * {@hide}
+ */
+@Backing(type="int")
+enum NtnSignalStrengthLevel {
+ NTN_SIGNAL_STRENGTH_NONE = 0,
+ NTN_SIGNAL_STRENGTH_POOR = 1,
+ NTN_SIGNAL_STRENGTH_MODERATE = 2,
+ NTN_SIGNAL_STRENGTH_GOOD = 3,
+ NTN_SIGNAL_STRENGTH_GREAT = 4
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index 4cee01e..a636a61 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -241,6 +241,30 @@
"requestIsSatelliteEnabledForCarrier");
}
+ @Override
+ public void requestSignalStrength(IIntegerConsumer resultCallback,
+ INtnSignalStrengthConsumer callback) throws RemoteException {
+ executeMethodAsync(
+ () -> SatelliteImplBase.this.requestSignalStrength(resultCallback, callback),
+ "requestSignalStrength");
+ }
+
+ @Override
+ public void startSendingNtnSignalStrength(IIntegerConsumer resultCallback)
+ throws RemoteException {
+ executeMethodAsync(
+ () -> SatelliteImplBase.this.startSendingNtnSignalStrength(resultCallback),
+ "startSendingNtnSignalStrength");
+ }
+
+ @Override
+ public void stopSendingNtnSignalStrength(IIntegerConsumer resultCallback)
+ throws RemoteException {
+ executeMethodAsync(
+ () -> SatelliteImplBase.this.stopSendingNtnSignalStrength(resultCallback),
+ "stopSendingNtnSignalStrength");
+ }
+
// Call the methods with a clean calling identity on the executor and wait indefinitely for
// the future to return.
private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
@@ -728,4 +752,35 @@
@NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) {
// stub implementation
}
+
+ /**
+ * Request to get the signal strength of the satellite connection.
+ *
+ * @param resultCallback The {@link SatelliteError} result of the operation.
+ * @param callback The callback to handle the NTN signal strength changed event.
+ */
+ public void requestSignalStrength(@NonNull IIntegerConsumer resultCallback,
+ INtnSignalStrengthConsumer callback) {
+ // stub implementation
+ }
+
+ /**
+ * Requests to deliver signal strength changed events through the
+ * {@link ISatelliteListener#onNtnSignalStrengthChanged(NtnSignalStrength ntnSignalStrength)}
+ * callback.
+ *
+ * @param resultCallback The {@link SatelliteError} result of the operation.
+ */
+ public void startSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback) {
+ // stub implementation
+ }
+
+ /**
+ * Requests to stop signal strength changed events
+ *
+ * @param resultCallback The {@link SatelliteError} result of the operation.
+ */
+ public void stopSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback){
+ // stub implementation
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 3aa5a5a..58e7026 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -67,10 +67,12 @@
import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.telephony.ims.aidl.IRcsConfigCallback;
+import android.telephony.satellite.INtnSignalStrengthCallback;
import android.telephony.satellite.ISatelliteDatagramCallback;
import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
import android.telephony.satellite.ISatelliteProvisionStateCallback;
import android.telephony.satellite.ISatelliteStateCallback;
+import android.telephony.satellite.NtnSignalStrength;
import android.telephony.satellite.SatelliteCapabilities;
import android.telephony.satellite.SatelliteDatagram;
import com.android.ims.internal.IImsServiceFeatureCallback;
@@ -2837,7 +2839,6 @@
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
void deprovisionSatelliteService(int subId, in String token, in IIntegerConsumer callback);
-
/**
* Registers for provision state changed from satellite modem.
*
@@ -3071,4 +3072,40 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
int[] getSatelliteAttachRestrictionReasonsForCarrier(int subId);
+
+ /**
+ * Request to get the signal strength of the satellite connection.
+ *
+ * @param subId The subId of the subscription to request for.
+ * @param receiver Result receiver to get the error code of the request and the current signal
+ * strength of the satellite connection.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void requestNtnSignalStrength(int subId, in ResultReceiver receiver);
+
+ /**
+ * Registers for NTN signal strength changed from satellite modem.
+ *
+ * @param subId The subId of the subscription to request for.
+ * @param callback The callback to handle the NTN signal strength changed event.
+ *
+ * @return The {@link SatelliteResult} result of the operation.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ int registerForNtnSignalStrengthChanged(int subId, in INtnSignalStrengthCallback callback);
+
+ /**
+ * Unregisters for NTN signal strength changed from satellite modem.
+ * If callback was not registered before, the request will be ignored.
+ *
+ * @param subId The subId of the subscription to unregister for provision state changed.
+ * @param callback The callback that was passed to
+ * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void unregisterForNtnSignalStrengthChanged(int subId,
+ in INtnSignalStrengthCallback callback);
}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 72e4389..a20f26c 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -120,31 +120,6 @@
int BLOCKED_DUE_TO_CALL = 69; /* SMS is blocked due to call control */
int RF_HARDWARE_ISSUE = 70; /* RF HW issue is detected */
int NO_RF_CALIBRATION_INFO = 71; /* No RF calibration in device */
- int ENCODING_NOT_SUPPORTED = 72; /* The encoding scheme is not supported by
- either the network or the MS. */
- int FEATURE_NOT_SUPPORTED = 73; /* The requesting feature is not supported by
- the service provider. */
- int INVALID_CONTACT = 74; /* The contact to be added is either not
- existing or not valid. */
- int MODEM_INCOMPATIBLE = 75; /* The modem of the MS is not compatible with
- the service provider. */
- int NETWORK_TIMEOUT = 76; /* Modem timeout to receive ACK or response from
- network after sending a request to it. */
- int NO_SATELLITE_SIGNAL = 77; /* Modem fails to communicate with the satellite
- network since there is no satellite signal.*/
- int NOT_SUFFICIENT_ACCOUNT_BALANCE = 78; /* The request cannot be performed since the
- subscriber's account balance is not
- sufficient. */
- int RADIO_TECHNOLOGY_NOT_SUPPORTED = 79; /* The radio technology is not supported by the
- service provider. */
- int SUBSCRIBER_NOT_AUTHORIZED = 80; /* The subscription is not authorized to
- register with the service provider. */
- int SWITCHED_FROM_SATELLITE_TO_TERRESTRIAL = 81; /* While processing a request from the
- Framework the satellite modem detects
- terrestrial signal, aborts the request, and
- switches to the terrestrial network. */
- int UNIDENTIFIED_SUBSCRIBER = 82; /* The subscriber is not registered with the
- service provider */
// Below is list of OEM specific error codes which can by used by OEMs in case they don't want to
// reveal particular replacement for Generic failure
@@ -568,22 +543,7 @@
int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;
int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243;
int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244;
- int RIL_REQUEST_GET_SATELLITE_CAPABILITIES = 245;
- int RIL_REQUEST_SET_SATELLITE_POWER = 246;
- int RIL_REQUEST_GET_SATELLITE_POWER = 247;
- int RIL_REQUEST_PROVISION_SATELLITE_SERVICE = 248;
- int RIL_REQUEST_ADD_ALLOWED_SATELLITE_CONTACTS = 249;
- int RIL_REQUEST_REMOVE_ALLOWED_SATELLITE_CONTACTS = 250;
- int RIL_REQUEST_SEND_SATELLITE_MESSAGES = 251;
- int RIL_REQUEST_GET_PENDING_SATELLITE_MESSAGES = 252;
- int RIL_REQUEST_GET_SATELLITE_MODE = 253;
- int RIL_REQUEST_SET_SATELLITE_INDICATION_FILTER = 254;
- int RIL_REQUEST_START_SENDING_SATELLITE_POINTING_INFO = 255;
- int RIL_REQUEST_STOP_SENDING_SATELLITE_POINTING_INFO = 256;
- int RIL_REQUEST_GET_MAX_CHARACTERS_PER_SATELLITE_TEXT_MESSAGE = 257;
- int RIL_REQUEST_GET_TIME_FOR_NEXT_SATELLITE_VISIBILITY = 258;
- int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 259;
- int RIL_REQUEST_SET_SATELLITE_PLMN = 260;
+ int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 245;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -645,13 +605,6 @@
int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED = 1053;
int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054;
int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055;
- int RIL_UNSOL_PENDING_SATELLITE_MESSAGE_COUNT = 1056;
- int RIL_UNSOL_NEW_SATELLITE_MESSAGES = 1057;
- int RIL_UNSOL_SATELLITE_MESSAGES_TRANSFER_COMPLETE = 1058;
- int RIL_UNSOL_SATELLITE_POINTING_INFO_CHANGED = 1059;
- int RIL_UNSOL_SATELLITE_MODE_CHANGED = 1060;
- int RIL_UNSOL_SATELLITE_RADIO_TECHNOLOGY_CHANGED = 1061;
- int RIL_UNSOL_SATELLITE_PROVISION_STATE_CHANGED = 1062;
/* The following unsols are not defined in RIL.h */
int RIL_UNSOL_HAL_NON_RIL_BASE = 1100;
diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt
index 241e691..f61cce6 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.test.mock {
@Deprecated public class MockAccountManager {
@@ -202,7 +200,7 @@
method @Deprecated public int checkPermission(String, String);
method @Deprecated public int checkSignatures(String, String);
method @Deprecated public int checkSignatures(int, int);
- method public void clearInstantAppCookie();
+ method @Deprecated public void clearInstantAppCookie();
method @Deprecated public void clearPackagePreferredActivities(String);
method @Deprecated public String[] currentToCanonicalPackageNames(String[]);
method @Deprecated public void extendVerificationTimeout(int, int, long);
@@ -224,15 +222,15 @@
method @Deprecated public CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
method @Deprecated public android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
method @Deprecated public android.graphics.drawable.Drawable getApplicationLogo(String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.pm.ChangedPackages getChangedPackages(int);
+ method @Deprecated public android.content.pm.ChangedPackages getChangedPackages(int);
method @Deprecated public int getComponentEnabledSetting(android.content.ComponentName);
method @Deprecated public android.graphics.drawable.Drawable getDefaultActivityIcon();
method @Deprecated public android.graphics.drawable.Drawable getDrawable(String, int, android.content.pm.ApplicationInfo);
method @Deprecated public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method @Deprecated public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method @Deprecated public String getInstallerPackageName(String);
- method public byte[] getInstantAppCookie();
- method public int getInstantAppCookieMaxBytes();
+ method @Deprecated public byte[] getInstantAppCookie();
+ method @Deprecated public int getInstantAppCookieMaxBytes();
method @Deprecated public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated public android.content.Intent getLaunchIntentForPackage(String);
method @Deprecated public android.content.Intent getLeanbackLaunchIntentForPackage(String);
@@ -241,7 +239,7 @@
method @Deprecated public int[] getPackageGids(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated public android.content.pm.PackageInfo getPackageInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated public android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.pm.PackageInstaller getPackageInstaller();
+ method @Deprecated public android.content.pm.PackageInstaller getPackageInstaller();
method @Deprecated public int getPackageUid(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated public String[] getPackagesForUid(int);
method @Deprecated public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(String[], int);
@@ -265,8 +263,8 @@
method @Deprecated public android.content.res.XmlResourceParser getXml(String, int, android.content.pm.ApplicationInfo);
method @Deprecated public boolean hasSystemFeature(String);
method @Deprecated public boolean hasSystemFeature(String, int);
- method public boolean isInstantApp();
- method public boolean isInstantApp(String);
+ method @Deprecated public boolean isInstantApp();
+ method @Deprecated public boolean isInstantApp(String);
method @Deprecated public boolean isPermissionRevokedByPolicy(String, String);
method @Deprecated public boolean isSafeMode();
method @Deprecated public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -283,11 +281,12 @@
method @Deprecated public android.content.pm.ProviderInfo resolveContentProvider(String, int);
method @Deprecated public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
method @Deprecated public android.content.pm.ResolveInfo resolveServiceAsUser(android.content.Intent, int, int);
- method public void setApplicationCategoryHint(String, int);
+ method @Deprecated public void setApplicationCategoryHint(String, int);
method @Deprecated public void setApplicationEnabledSetting(String, int, int);
method @Deprecated public void setComponentEnabledSetting(android.content.ComponentName, int, int);
method @Deprecated public void setInstallerPackageName(String, String);
- method public void updateInstantAppCookie(@NonNull byte[]);
+ method @Deprecated public boolean setInstantAppCookie(@NonNull byte[]);
+ method @Deprecated public void updateInstantAppCookie(@NonNull byte[]);
method @Deprecated public void verifyPendingInstall(int, int);
}
diff --git a/test-mock/api/removed.txt b/test-mock/api/removed.txt
index fa2fbd2..0c800b6 100644
--- a/test-mock/api/removed.txt
+++ b/test-mock/api/removed.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.test.mock {
public class MockContext extends android.content.Context {
@@ -11,7 +9,7 @@
@Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
method @Deprecated public String getDefaultBrowserPackageName(int);
method @Deprecated public boolean setDefaultBrowserPackageName(String, int);
- method public boolean setInstantAppCookie(@NonNull byte[]);
+ method @Deprecated public boolean setInstantAppCookie(@NonNull byte[]);
}
}
diff --git a/test-mock/api/system-current.txt b/test-mock/api/system-current.txt
index 2b5132e..f350957 100644
--- a/test-mock/api/system-current.txt
+++ b/test-mock/api/system-current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.test.mock {
public class MockContext extends android.content.Context {
@@ -11,30 +9,30 @@
}
@Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
- method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
- method public boolean arePermissionsIndividuallyControlled();
+ method @Deprecated public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+ method @Deprecated public boolean arePermissionsIndividuallyControlled();
method @Deprecated public java.util.List<android.content.IntentFilter> getAllIntentFilters(String);
- method public String getDefaultBrowserPackageNameAsUser(int);
- method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
- method public android.graphics.drawable.Drawable getInstantAppIcon(String);
- method public android.content.ComponentName getInstantAppInstallerComponent();
- method public android.content.ComponentName getInstantAppResolverSettingsComponent();
- method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
- method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String);
- method public int getIntentVerificationStatusAsUser(String, int);
- method public int getPermissionFlags(String, String, android.os.UserHandle);
- method public void grantRuntimePermission(String, String, android.os.UserHandle);
- method public int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback);
- method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
- method public void revokeRuntimePermission(String, String, android.os.UserHandle);
- method public boolean setDefaultBrowserPackageNameAsUser(String, int);
+ method @Deprecated public String getDefaultBrowserPackageNameAsUser(int);
+ method @Deprecated public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
+ method @Deprecated public android.graphics.drawable.Drawable getInstantAppIcon(String);
+ method @Deprecated public android.content.ComponentName getInstantAppInstallerComponent();
+ method @Deprecated public android.content.ComponentName getInstantAppResolverSettingsComponent();
+ method @Deprecated public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
+ method @Deprecated public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String);
+ method @Deprecated public int getIntentVerificationStatusAsUser(String, int);
+ method @Deprecated public int getPermissionFlags(String, String, android.os.UserHandle);
+ method @Deprecated public void grantRuntimePermission(String, String, android.os.UserHandle);
+ method @Deprecated public int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated public int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated public void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback);
+ method @Deprecated public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+ method @Deprecated public void revokeRuntimePermission(String, String, android.os.UserHandle);
+ method @Deprecated public boolean setDefaultBrowserPackageNameAsUser(String, int);
method public String[] setPackagesSuspended(String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, String);
- method public void setUpdateAvailable(String, boolean);
- method public boolean updateIntentVerificationStatusAsUser(String, int, int);
- method public void updatePermissionFlags(String, String, int, int, android.os.UserHandle);
- method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
+ method @Deprecated public void setUpdateAvailable(String, boolean);
+ method @Deprecated public boolean updateIntentVerificationStatusAsUser(String, int, int);
+ method @Deprecated public void updatePermissionFlags(String, String, int, int, android.os.UserHandle);
+ method @Deprecated public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
}
}
diff --git a/test-mock/api/system-removed.txt b/test-mock/api/system-removed.txt
index 14191eb..d802177 100644
--- a/test-mock/api/system-removed.txt
+++ b/test-mock/api/system-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt
index 1752edc..9ed0108 100644
--- a/test-mock/api/test-current.txt
+++ b/test-mock/api/test-current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.test.mock {
public class MockContext extends android.content.Context {
@@ -8,13 +6,13 @@
}
@Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
- method public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
- method public void clearCrossProfileIntentFilters(int);
- method public int getInstallReason(String, android.os.UserHandle);
- method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
- method public String[] getNamesForUids(int[]);
- method @NonNull public String getServicesSystemSharedLibraryPackageName();
- method @NonNull public String getSharedSystemSharedLibraryPackageName();
+ method @Deprecated public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
+ method @Deprecated public void clearCrossProfileIntentFilters(int);
+ method @Deprecated public int getInstallReason(String, android.os.UserHandle);
+ method @Deprecated public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
+ method @Deprecated public String[] getNamesForUids(int[]);
+ method @Deprecated @NonNull public String getServicesSystemSharedLibraryPackageName();
+ method @Deprecated @NonNull public String getSharedSystemSharedLibraryPackageName();
}
}
diff --git a/test-mock/api/test-removed.txt b/test-mock/api/test-removed.txt
index 14191eb..d802177 100644
--- a/test-mock/api/test-removed.txt
+++ b/test-mock/api/test-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
index 79348d1..ae7c2a9 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
@@ -247,7 +247,7 @@
int rc = 0;
try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
- transaction.setFrameRateCategory(mSurfaceControl, category);
+ transaction.setFrameRateCategory(mSurfaceControl, category, false);
transaction.apply();
}
return rc;
diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml
index 63acddf..0f47980 100644
--- a/tests/FlickerTests/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AndroidTestTemplate.xml
@@ -61,9 +61,7 @@
<option name="shell-timeout" value="6600s"/>
<option name="test-timeout" value="6600s"/>
<option name="hidden-api-checks" value="false"/>
- <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed
<option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
- -->
<!-- PerfettoListener related arguments -->
<option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
<option name="instrumentation-arg"
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
index 359845d..47d6d23 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -36,7 +36,8 @@
/**
* Test launching a secondary Activity into Picture-In-Picture mode.
*
- * Setup: Start from a split A|B. Transition: B enters PIP, observe the window shrink to the bottom
+ * Setup: Start from a split A|B.
+ * Transition: B enters PIP, observe the window first goes fullscreen then shrink to the bottom
* right corner on screen.
*
* To run this test: `atest FlickerTestsOther:SecondaryActivityEnterPipTest`
@@ -63,7 +64,16 @@
}
}
- /** Main and secondary activity start from a split each taking half of the screen. */
+ /**
+ * We expect the background layer to be visible during this transition.
+ */
+ @Presubmit
+ @Test
+ override fun backgroundLayerNeverVisible(): Unit {}
+
+ /**
+ * Main and secondary activity start from a split each taking half of the screen.
+ */
@Presubmit
@Test
fun layersStartFromEqualSplit() {
@@ -109,7 +119,7 @@
.isVisible(TRANSITION_SNAPSHOT)
.isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
.then()
- .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT, isOptional = true)
}
flicker.assertLayersEnd {
visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
diff --git a/tests/FsVerityTest/AndroidTest.xml b/tests/FsVerityTest/AndroidTest.xml
index 49cbde0..d2537f6 100644
--- a/tests/FsVerityTest/AndroidTest.xml
+++ b/tests/FsVerityTest/AndroidTest.xml
@@ -24,7 +24,7 @@
<!-- This test requires root to write against block device. -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
- <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="FsVerityTestApp.apk"/>
<option name="cleanup-apks" value="true"/>
</target_preparer>
diff --git a/tools/aapt2/integration-tests/AutoVersionTest/Android.bp b/tools/aapt2/integration-tests/AutoVersionTest/Android.bp
index bfd3508..c901efa 100644
--- a/tools/aapt2/integration-tests/AutoVersionTest/Android.bp
+++ b/tools/aapt2/integration-tests/AutoVersionTest/Android.bp
@@ -26,4 +26,5 @@
android_test {
name: "AaptAutoVersionTest",
sdk_version: "current",
+ use_resource_processor: false,
}
diff --git a/tools/aapt2/integration-tests/BasicTest/Android.bp b/tools/aapt2/integration-tests/BasicTest/Android.bp
index 7db9d26..d0649ea 100644
--- a/tools/aapt2/integration-tests/BasicTest/Android.bp
+++ b/tools/aapt2/integration-tests/BasicTest/Android.bp
@@ -26,4 +26,5 @@
android_test {
name: "AaptBasicTest",
sdk_version: "current",
+ use_resource_processor: false,
}
diff --git a/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp
index 80404ee..ebb4e9f 100644
--- a/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp
+++ b/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp
@@ -24,9 +24,9 @@
}
android_test {
-
name: "AaptTestStaticLib_App",
sdk_version: "current",
+ use_resource_processor: false,
srcs: ["src/**/*.java"],
asset_dirs: [
"assets",
diff --git a/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp
index a84da43..ee12a929 100644
--- a/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp
+++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp
@@ -26,6 +26,7 @@
android_library {
name: "AaptTestStaticLib_LibOne",
sdk_version: "current",
+ use_resource_processor: false,
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
}
diff --git a/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp
index d386c3a..83b23624 100644
--- a/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp
+++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp
@@ -26,6 +26,7 @@
android_library {
name: "AaptTestStaticLib_LibTwo",
sdk_version: "current",
+ use_resource_processor: false,
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
libs: ["AaptTestStaticLib_LibOne"],
diff --git a/tools/aapt2/integration-tests/SymlinkTest/Android.bp b/tools/aapt2/integration-tests/SymlinkTest/Android.bp
index 1e8cf86..15a6a20 100644
--- a/tools/aapt2/integration-tests/SymlinkTest/Android.bp
+++ b/tools/aapt2/integration-tests/SymlinkTest/Android.bp
@@ -26,4 +26,5 @@
android_test {
name: "AaptSymlinkTest",
sdk_version: "current",
+ use_resource_processor: false,
}
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index a617876..226e2fad 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -7,9 +7,38 @@
default_applicable_licenses: ["frameworks_base_license"],
}
+// Visibility only for ravenwood prototype uses.
+genrule_defaults {
+ name: "hoststubgen-for-prototype-only-genrule",
+ visibility: [
+ ":__subpackages__",
+ "//frameworks/base/ravenwood:__subpackages__",
+ ],
+}
+
+// Visibility only for ravenwood prototype uses.
+java_defaults {
+ name: "hoststubgen-for-prototype-only-java",
+ visibility: [
+ ":__subpackages__",
+ "//frameworks/base/ravenwood:__subpackages__",
+ ],
+}
+
+// Visibility only for ravenwood prototype uses.
+filegroup_defaults {
+ name: "hoststubgen-for-prototype-only-filegroup",
+ visibility: [
+ ":__subpackages__",
+ "//frameworks/base/ravenwood:__subpackages__",
+ ],
+}
+
// This library contains the standard hoststubgen annotations.
+// This is only for the prototype. The productionized version is "ravenwood-annotations".
java_library {
name: "hoststubgen-annotations",
+ defaults: ["hoststubgen-for-prototype-only-java"],
srcs: [
"annotations-src/**/*.java",
],
@@ -18,7 +47,6 @@
// Seems like we need it to avoid circular deps.
// Copied it from "app-compat-annotations".
sdk_version: "core_current",
- visibility: ["//visibility:public"],
}
// This library contains helper classes used in the host side test environment at runtime.
@@ -30,11 +58,6 @@
],
libs: [
"junit",
- "ow2-asm",
- "ow2-asm-analysis",
- "ow2-asm-commons",
- "ow2-asm-tree",
- "ow2-asm-util",
],
static_libs: [
"guava",
@@ -60,12 +83,13 @@
}
// File that contains the standard command line argumetns to hoststubgen.
+// This is only for the prototype. The productionized version is "ravenwood-standard-options".
filegroup {
name: "hoststubgen-standard-options",
+ defaults: ["hoststubgen-for-prototype-only-filegroup"],
srcs: [
"hoststubgen-standard-options.txt",
],
- visibility: ["//visibility:public"],
}
hoststubgen_common_options = "$(location hoststubgen) " +
@@ -98,7 +122,6 @@
"hoststubgen_keep_all.txt",
"hoststubgen_dump.txt",
],
- // visibility: ["//visibility:public"],
}
// Generate the stub/impl from framework-all, with hidden APIs.
@@ -116,8 +139,10 @@
}
// Extract the stub jar from "framework-all-host" for subsequent build rules.
+// This is only for the prototype. Do not use it in "productionized" build rules.
java_genrule_host {
name: "framework-all-hidden-api-host-stub",
+ defaults: ["hoststubgen-for-prototype-only-genrule"],
cmd: "cp $(in) $(out)",
srcs: [
":framework-all-hidden-api-host{host_stub.jar}",
@@ -125,12 +150,13 @@
out: [
"host_stub.jar",
],
- visibility: ["//visibility:public"],
}
// Extract the impl jar from "framework-all-host" for subsequent build rules.
+// This is only for the prototype. Do not use it in "productionized" build rules.
java_genrule_host {
name: "framework-all-hidden-api-host-impl",
+ defaults: ["hoststubgen-for-prototype-only-genrule"],
cmd: "cp $(in) $(out)",
srcs: [
":framework-all-hidden-api-host{host_impl.jar}",
@@ -138,11 +164,11 @@
out: [
"host_impl.jar",
],
- visibility: ["//visibility:public"],
}
// Generate the stub/impl from framework-all, with only public/system/test APIs, without
// hidden APIs.
+// This is only for the prototype. Do not use it in "productionized" build rules.
java_genrule_host {
name: "framework-all-test-api-host",
defaults: ["hoststubgen-command-defaults"],
@@ -159,8 +185,10 @@
}
// Extract the stub jar from "framework-all-test-api-host" for subsequent build rules.
+// This is only for the prototype. Do not use it in "productionized" build rules.
java_genrule_host {
name: "framework-all-test-api-host-stub",
+ defaults: ["hoststubgen-for-prototype-only-genrule"],
cmd: "cp $(in) $(out)",
srcs: [
":framework-all-test-api-host{host_stub.jar}",
@@ -168,12 +196,13 @@
out: [
"host_stub.jar",
],
- visibility: ["//visibility:public"],
}
// Extract the impl jar from "framework-all-test-api-host" for subsequent build rules.
+// This is only for the prototype. Do not use it in "productionized" build rules.
java_genrule_host {
name: "framework-all-test-api-host-impl",
+ defaults: ["hoststubgen-for-prototype-only-genrule"],
cmd: "cp $(in) $(out)",
srcs: [
":framework-all-test-api-host{host_impl.jar}",
@@ -181,7 +210,6 @@
out: [
"host_impl.jar",
],
- visibility: ["//visibility:public"],
}
// This library contains helper classes to build hostside tests/targets.
@@ -191,6 +219,7 @@
// Ideally this library should be empty.
java_library_host {
name: "hoststubgen-helper-framework-buildtime",
+ defaults: ["hoststubgen-for-prototype-only-java"],
srcs: [
"helper-framework-buildtime-src/**/*.java",
],
@@ -200,13 +229,13 @@
"framework-all-hidden-api-host-impl",
"junit",
],
- visibility: ["//visibility:public"],
}
// This module contains "fake" libcore/dalvik classes, framework native substitution, etc,
// that are needed at runtime.
java_library_host {
name: "hoststubgen-helper-framework-runtime",
+ defaults: ["hoststubgen-for-prototype-only-java"],
srcs: [
"helper-framework-runtime-src/**/*.java",
],
@@ -214,7 +243,6 @@
"hoststubgen-helper-runtime",
"framework-all-hidden-api-host-impl",
],
- visibility: ["//visibility:public"],
}
// Defaults for host side test modules.
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
index 4c37579..7ada961 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
@@ -17,8 +17,6 @@
import static java.lang.annotation.ElementType.TYPE;
-import org.objectweb.asm.Type;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -29,6 +27,6 @@
@Target({TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HostStubGenProcessedKeepClass {
- String CLASS_INTERNAL_NAME = Type.getInternalName(HostStubGenProcessedKeepClass.class);
+ String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenProcessedKeepClass.class);
String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";";
}
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
index 34e0030..e598da0a 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
@@ -17,8 +17,6 @@
import static java.lang.annotation.ElementType.TYPE;
-import org.objectweb.asm.Type;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -29,6 +27,6 @@
@Target({TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HostStubGenProcessedStubClass {
- String CLASS_INTERNAL_NAME = Type.getInternalName(HostStubGenProcessedStubClass.class);
+ String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenProcessedStubClass.class);
String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";";
}
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
index d176b5e..9f83597 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -15,8 +15,6 @@
*/
package com.android.hoststubgen.hosthelper;
-import org.objectweb.asm.Type;
-
import java.io.PrintStream;
import java.lang.StackWalker.Option;
import java.lang.reflect.Method;
@@ -32,7 +30,15 @@
private HostTestUtils() {
}
- public static final String CLASS_INTERNAL_NAME = Type.getInternalName(HostTestUtils.class);
+ /**
+ * Same as ASM's Type.getInternalName(). Copied here, to avoid having a reference to ASM
+ * in this JAR.
+ */
+ public static String getInternalName(final Class<?> clazz) {
+ return clazz.getName().replace('.', '/');
+ }
+
+ public static final String CLASS_INTERNAL_NAME = getInternalName(HostTestUtils.class);
/** If true, we won't print method call log. */
private static final boolean SKIP_METHOD_LOG = "1".equals(System.getenv(
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
index 722905f..523106f 100755
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
@@ -16,14 +16,8 @@
source "${0%/*}"/../../common.sh
-#**********************************************************************************************
-#This script is broken because it relies on soong intermediate files, which seem to have moved.
-#**********************************************************************************************
-
# This scripts run the "tiny-framework" test, but does most stuff from the command line, using
# the native java and javac commands.
-# This is useful to
-
debug=0
while getopts "d" opt; do
@@ -42,7 +36,7 @@
HOSTSTUBGEN=hoststubgen
# Rebuild the tool and the dependencies. These are the only things we build with the build system.
-run m $HOSTSTUBGEN hoststubgen-annotations hoststubgen-helper-runtime truth-prebuilt junit
+run m $HOSTSTUBGEN hoststubgen-annotations hoststubgen-helper-runtime truth junit
# Build tiny-framework
@@ -61,7 +55,7 @@
test_compile_classpaths=(
$SOONG_INT/external/junit/junit/android_common/combined/junit.jar
- $ANDROID_BUILD_TOP/out/target/common/obj/JAVA_LIBRARIES/truth-prebuilt_intermediates/classes.jar
+ $SOONG_INT/external/truth/truth/android_common/combined/truth.jar
)
test_runtime_classpaths=(
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index 246d065..cd604ff 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -23,6 +23,10 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import java.io.FileDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
public class TinyFrameworkClassTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@@ -140,4 +144,42 @@
assertThat(TinyFrameworkClassLoadHook.sLoadedClasses)
.doesNotContain(TinyFrameworkNestedClasses.class);
}
+
+ /**
+ * Test to try accessing JDK private fields using reflections + setAccessible(true),
+ * which is now disallowed due to Java Modules, unless you run the javacommand with.
+ * --add-opens=java.base/java.io=ALL-UNNAMED
+ *
+ * You can try it from the command line, like:
+ * $ JAVA_OPTS="--add-opens=java.base/java.io=ALL-UNNAMED" ./run-test-manually.sh
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testFileDescriptor() throws Exception {
+ var fd = FileDescriptor.out;
+
+ // Get the FD value directly from the private field.
+ // This is now prohibited due to Java Modules.
+ // It throws:
+ // java.lang.reflect.InaccessibleObjectException: Unable to make field private int java.io.FileDescriptor.fd accessible: module java.base does not "opens java.io" to unnamed module @3bb50eaa
+
+ thrown.expect(java.lang.reflect.InaccessibleObjectException.class);
+
+ // Access the private field.
+ final Field f = FileDescriptor.class.getDeclaredField("fd");
+ final Method m = FileDescriptor.class.getDeclaredMethod("set", int.class);
+ f.setAccessible(true);
+ m.setAccessible(true);
+
+ assertThat(f.get(fd)).isEqualTo(1);
+
+ // Set
+ f.set(fd, 2);
+ assertThat(f.get(fd)).isEqualTo(2);
+
+ // Call the package private method, set(int).
+ m.invoke(fd, 0);
+ assertThat(f.get(fd)).isEqualTo(0);
+ }
}
diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
index 2e9cf42..8bc88de 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -34,8 +34,7 @@
run ./hoststubgen/test-framework/run-test-without-atest.sh
-#This script is broken because it relies on soong intermediate files, which seem to have moved.
-#run ./hoststubgen/test-tiny-framework/run-test-manually.sh
+run ./hoststubgen/test-tiny-framework/run-test-manually.sh
run atest tiny-framework-dump-test
run ./scripts/build-framework-hostside-jars-and-extract.sh
diff --git a/tools/lint/global/integration_tests/Android.bp b/tools/lint/global/integration_tests/Android.bp
index ca96559..40281d2 100644
--- a/tools/lint/global/integration_tests/Android.bp
+++ b/tools/lint/global/integration_tests/Android.bp
@@ -12,25 +12,58 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-java_library {
- name: "AndroidGlobalLintTestNoAidl",
- srcs: ["TestNoAidl/**/*.java"],
+// Integration tests for @EnforcePermission linters.
+// Each test defines its own java_library. The XML lint report from this
+// java_library is wrapped under a Python library with a unique pkg_path (this
+// is to avoid a name conflict for the report file). All the tests are
+// referenced and executed by AndroidGlobalLintCheckerIntegrationTest.
+
+java_defaults {
+ name: "AndroidGlobalLintIntegrationTestDefault",
libs: [
"framework-annotations-lib",
],
lint: {
- // It is expected that lint returns an error when processing this
+ // It is expected that lint returns an error when processing the
// library. Silence it here, the lint output is verified in tests.py.
suppress_exit_code: true,
},
}
+java_library {
+ name: "AndroidGlobalLintTestNoAidl",
+ srcs: ["TestNoAidl/**/*.java"],
+ defaults: ["AndroidGlobalLintIntegrationTestDefault"],
+}
+
+python_library_host {
+ name: "AndroidGlobalLintTestNoAidl_py",
+ data: [":AndroidGlobalLintTestNoAidl{.lint}"],
+ pkg_path: "no_aidl",
+}
+
+java_library {
+ name: "AndroidGlobalLintTestMissingAnnotation",
+ srcs: [
+ "TestMissingAnnotation/**/*.java",
+ "TestMissingAnnotation/**/*.aidl",
+ ],
+ defaults: ["AndroidGlobalLintIntegrationTestDefault"],
+}
+
+python_library_host {
+ name: "AndroidGlobalLintTestMissingAnnotation_py",
+ data: [":AndroidGlobalLintTestMissingAnnotation{.lint}"],
+ pkg_path: "missing_annotation",
+}
+
python_test_host {
name: "AndroidGlobalLintCheckerIntegrationTest",
srcs: ["tests.py"],
main: "tests.py",
- data: [
- ":AndroidGlobalLintTestNoAidl{.lint}",
+ libs: [
+ "AndroidGlobalLintTestNoAidl_py",
+ "AndroidGlobalLintTestMissingAnnotation_py",
],
version: {
py3: {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java
similarity index 67%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java
index 0803a01..9e4854c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java
@@ -12,15 +12,17 @@
* 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.communal.shared
+package com.google.android.lint.integration_tests;
-import android.appwidget.AppWidgetProviderInfo
+/**
+ * A class that implements an AIDL interface, but is missing the @EnforcePermission annotation.
+ */
+class TestMissingAnnotation extends IFoo.Stub {
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
- val providerInfo: AppWidgetProviderInfo,
- val appWidgetId: Int,
-)
+ @Override
+ public void Method() {
+ }
+
+}
diff --git a/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl b/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl
new file mode 100644
index 0000000..95ec2c2
--- /dev/null
+++ b/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl
@@ -0,0 +1,7 @@
+package com.google.android.lint.integration_tests;
+
+interface IFoo {
+
+ @EnforcePermission("INTERNET")
+ void Method();
+}
diff --git a/tools/lint/global/integration_tests/tests.py b/tools/lint/global/integration_tests/tests.py
index fc3eeb4..cdb16b8 100644
--- a/tools/lint/global/integration_tests/tests.py
+++ b/tools/lint/global/integration_tests/tests.py
@@ -19,16 +19,28 @@
class TestLinterReports(unittest.TestCase):
"""Integration tests for the linters used by @EnforcePermission."""
- def test_no_aidl(self):
- report = pkgutil.get_data("lint", "lint-report.xml").decode()
+ def _read_report(self, pkg_path):
+ report = pkgutil.get_data(pkg_path, "lint/lint-report.xml").decode()
issues = xml.etree.ElementTree.fromstring(report)
self.assertEqual(issues.tag, "issues")
+ return issues
+
+ def test_no_aidl(self):
+ issues = self._read_report("no_aidl")
self.assertEqual(len(issues), 1)
issue = issues[0]
self.assertEqual(issue.attrib["id"], "MisusingEnforcePermissionAnnotation")
self.assertEqual(issue.attrib["severity"], "Error")
+ def test_missing_annotation(self):
+ issues = self._read_report("missing_annotation")
+ self.assertEqual(len(issues), 1)
+
+ issue = issues[0]
+ self.assertEqual(issue.attrib["id"], "MissingEnforcePermissionAnnotation")
+ self.assertEqual(issue.attrib["severity"], "Error")
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 2a199d2..58638e8 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -113,6 +113,7 @@
private HashMap<String, IPnoScanEvent> mPnoScanEventHandlers = new HashMap<>();
private HashMap<String, IApInterfaceEventCallback> mApInterfaceListeners = new HashMap<>();
private Runnable mDeathEventHandler;
+ private Object mLock = new Object();
/**
* Ensures that no more than one sendMgmtFrame operation runs concurrently.
*/
@@ -625,13 +626,15 @@
@VisibleForTesting
public void binderDied() {
mEventHandler.post(() -> {
- Log.e(TAG, "Wificond died!");
- clearState();
- // Invalidate the global wificond handle on death. Will be refreshed
- // on the next setup call.
- mWificond = null;
- if (mDeathEventHandler != null) {
- mDeathEventHandler.run();
+ synchronized (mLock) {
+ Log.e(TAG, "Wificond died!");
+ clearState();
+ // Invalidate the global wificond handle on death. Will be refreshed
+ // on the next setup call.
+ mWificond = null;
+ if (mDeathEventHandler != null) {
+ mDeathEventHandler.run();
+ }
}
});
}
@@ -867,26 +870,28 @@
* @return Returns true on success.
*/
public boolean tearDownInterfaces() {
- Log.d(TAG, "tearing down interfaces in wificond");
- // Explicitly refresh the wificodn handler because |tearDownInterfaces()|
- // could be used to cleanup before we setup any interfaces.
- if (!retrieveWificondAndRegisterForDeath()) {
+ synchronized (mLock) {
+ Log.d(TAG, "tearing down interfaces in wificond");
+ // Explicitly refresh the wificond handler because |tearDownInterfaces()|
+ // could be used to cleanup before we setup any interfaces.
+ if (!retrieveWificondAndRegisterForDeath()) {
+ return false;
+ }
+
+ try {
+ for (Map.Entry<String, IWifiScannerImpl> entry : mWificondScanners.entrySet()) {
+ entry.getValue().unsubscribeScanEvents();
+ entry.getValue().unsubscribePnoScanEvents();
+ }
+ mWificond.tearDownInterfaces();
+ clearState();
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to tear down interfaces due to remote exception");
+ }
+
return false;
}
-
- try {
- for (Map.Entry<String, IWifiScannerImpl> entry : mWificondScanners.entrySet()) {
- entry.getValue().unsubscribeScanEvents();
- entry.getValue().unsubscribePnoScanEvents();
- }
- mWificond.tearDownInterfaces();
- clearState();
- return true;
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to tear down interfaces due to remote exception");
- }
-
- return false;
}
/** Helper function to look up the interface handle using name */
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index 01f1591..3d5a0f7 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -16,6 +16,7 @@
package android.net.wifi.sharedconnectivity.app;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -169,6 +170,7 @@
* @return Returns the Builder object.
*/
@NonNull
+ @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
public Builder setBatteryCharging(boolean isBatteryCharging) {
mIsBatteryCharging = isBatteryCharging;
return this;
@@ -283,6 +285,7 @@
*
* @return Returns true if the battery of the remote device is charging.
*/
+ @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
public boolean isBatteryCharging() {
return mIsBatteryCharging;
}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index 71ac94b..b0f68f7 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -17,6 +17,7 @@
package android.net.wifi.sharedconnectivity.app;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -296,6 +297,7 @@
*/
@TestApi
@NonNull
+ @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api")
public BroadcastReceiver getBroadcastReceiver() {
return mBroadcastReceiver;
}