Merge "Improve avalanche debug logs" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index eac416a..d3e80ae 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -74,6 +74,7 @@
"android.view.inputmethod.flags-aconfig-java",
"android.webkit.flags-aconfig-java",
"android.widget.flags-aconfig-java",
+ "backstage_power_flags_lib",
"backup_flags_lib",
"camera_platform_flags_core_java_lib",
"com.android.hardware.input-aconfig-java",
@@ -641,6 +642,19 @@
],
}
+java_aconfig_library {
+ name: "android.permission.flags-aconfig-java-host",
+ aconfig_declarations: "android.permission.flags-aconfig",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ min_sdk_version: "30",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.permission",
+ "com.android.nfcservices",
+ ],
+}
+
// SQLite
aconfig_declarations {
name: "android.database.sqlite-aconfig",
@@ -1320,3 +1334,20 @@
aconfig_declarations: "android.systemserver.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// backstage power
+aconfig_declarations {
+ name: "backstage_power_flags",
+ package: "com.android.server.power.optimization",
+ container: "system",
+ exportable: true,
+ srcs: [
+ "services/core/java/com/android/server/power/stats/flags.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "backstage_power_flags_lib",
+ aconfig_declarations: "backstage_power_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index 4f715f8..d6b303f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -97,7 +97,7 @@
// AIDL sources from external directories
":android.frameworks.location.altitude-V2-java-source",
":android.hardware.biometrics.common-V4-java-source",
- ":android.hardware.biometrics.fingerprint-V3-java-source",
+ ":android.hardware.biometrics.fingerprint-V5-java-source",
":android.hardware.biometrics.face-V4-java-source",
":android.hardware.gnss-V2-java-source",
":android.hardware.graphics.common-V3-java-source",
diff --git a/apct-tests/perftests/inputmethod/AndroidManifest.xml b/apct-tests/perftests/inputmethod/AndroidManifest.xml
index 5dd6ccc..34f9692 100644
--- a/apct-tests/perftests/inputmethod/AndroidManifest.xml
+++ b/apct-tests/perftests/inputmethod/AndroidManifest.xml
@@ -22,7 +22,7 @@
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.perftests.utils.PerfTestActivity"
- android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
+ android:theme="@android:style/Theme.DeviceDefault.Light.NoActionBar"
android:exported="true">
<intent-filter>
<action android:name="com.android.perftests.core.PERFTEST" />
diff --git a/apct-tests/perftests/packagemanager/AndroidTest.xml b/apct-tests/perftests/packagemanager/AndroidTest.xml
index c9d45a6..db938e4 100644
--- a/apct-tests/perftests/packagemanager/AndroidTest.xml
+++ b/apct-tests/perftests/packagemanager/AndroidTest.xml
@@ -76,11 +76,6 @@
<option name="test-file-name" value="QueriesAll49.apk"/>
</target_preparer>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="com.android.perftests.packagemanager"/>
- <option name="hidden-api-checks" value="false"/>
- </test>
-
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="directory-keys" value="/data/local/PackageManagerPerfTests"/>
<option name="collect-on-run-ended-only" value="true"/>
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 75e2efd2..e20f525 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -28,3 +28,13 @@
description: "Only relax a prefetch job's connectivity constraint when the device is charging and battery is not low"
bug: "299329948"
}
+
+flag {
+ name: "count_quota_fix"
+ namespace: "backstage_power"
+ description: "Fix job count quota check"
+ bug: "300862949"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 012ede2..3bb395f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1650,6 +1650,16 @@
continue;
}
+ if (Flags.countQuotaFix() && !nextPending.isReady()) {
+ // This could happen when the constraints for the job have been marked
+ // as unsatisfiled but hasn't been removed from the pending queue yet.
+ if (DEBUG) {
+ Slog.w(TAG, "Pending+not ready job: " + nextPending);
+ }
+ pendingJobQueue.remove(nextPending);
+ continue;
+ }
+
if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
Slog.w(TAG, "Already running similar job to: " + nextPending);
}
@@ -1737,6 +1747,16 @@
continue;
}
+ if (Flags.countQuotaFix() && !nextPending.isReady()) {
+ // This could happen when the constraints for the job have been marked
+ // as unsatisfiled but hasn't been removed from the pending queue yet.
+ if (DEBUG) {
+ Slog.w(TAG, "Pending+not ready job: " + nextPending);
+ }
+ pendingJobQueue.remove(nextPending);
+ continue;
+ }
+
if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
Slog.w(TAG, "Already running similar job to: " + nextPending);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 3c9648b..c240b3f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -70,6 +70,7 @@
import com.android.server.LocalServices;
import com.android.server.PowerAllowlistInternal;
import com.android.server.job.ConstantsProto;
+import com.android.server.job.Flags;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
import com.android.server.usage.AppStandbyInternal;
@@ -512,7 +513,7 @@
/** An app has reached its quota. The message should contain a {@link UserPackage} object. */
@VisibleForTesting
- static final int MSG_REACHED_QUOTA = 0;
+ static final int MSG_REACHED_TIME_QUOTA = 0;
/** Drop any old timing sessions. */
private static final int MSG_CLEAN_UP_SESSIONS = 1;
/** Check if a package is now within its quota. */
@@ -524,7 +525,7 @@
* object.
*/
@VisibleForTesting
- static final int MSG_REACHED_EJ_QUOTA = 4;
+ static final int MSG_REACHED_EJ_TIME_QUOTA = 4;
/**
* Process a new {@link UsageEvents.Event}. The event will be the message's object and the
* userId will the first arg.
@@ -533,6 +534,11 @@
/** A UID's free quota grace period has ended. */
@VisibleForTesting
static final int MSG_END_GRACE_PERIOD = 6;
+ /**
+ * An app has reached its job count quota. The message should contain a {@link UserPackage}
+ * object.
+ */
+ static final int MSG_REACHED_COUNT_QUOTA = 7;
public QuotaController(@NonNull JobSchedulerService service,
@NonNull BackgroundJobsController backgroundJobsController,
@@ -874,17 +880,46 @@
}
@VisibleForTesting
+ @GuardedBy("mLock")
boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
final int standbyBucket = jobStatus.getEffectiveStandbyBucket();
// A job is within quota if one of the following is true:
// 1. it was started while the app was in the TOP state
// 2. the app is currently in the foreground
// 3. the app overall is within its quota
- return jobStatus.shouldTreatAsUserInitiatedJob()
+ if (!Flags.countQuotaFix()) {
+ return jobStatus.shouldTreatAsUserInitiatedJob()
+ || isTopStartedJobLocked(jobStatus)
+ || isUidInForeground(jobStatus.getSourceUid())
+ || isWithinQuotaLocked(
+ jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+ }
+
+ if (jobStatus.shouldTreatAsUserInitiatedJob()
|| isTopStartedJobLocked(jobStatus)
- || isUidInForeground(jobStatus.getSourceUid())
- || isWithinQuotaLocked(
- jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+ || isUidInForeground(jobStatus.getSourceUid())) {
+ return true;
+ }
+
+ if (standbyBucket == NEVER_INDEX) return false;
+
+ if (isQuotaFreeLocked(standbyBucket)) return true;
+
+ final ExecutionStats stats = getExecutionStatsLocked(jobStatus.getSourceUserId(),
+ jobStatus.getSourcePackageName(), standbyBucket);
+ if (!(getRemainingExecutionTimeLocked(stats) > 0)) {
+ // Out of execution time quota.
+ return false;
+ }
+
+ if (standbyBucket != RESTRICTED_INDEX && mService.isCurrentlyRunningLocked(jobStatus)) {
+ // Running job is considered as within quota except for the restricted one, which
+ // requires additional constraints.
+ return true;
+ }
+
+ // Check if the app is within job count quota.
+ return isUnderJobCountQuotaLocked(stats) && isUnderSessionCountQuotaLocked(stats);
}
@GuardedBy("mLock")
@@ -909,12 +944,11 @@
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
// TODO: use a higher minimum remaining time for jobs with MINIMUM priority
return getRemainingExecutionTimeLocked(stats) > 0
- && isUnderJobCountQuotaLocked(stats, standbyBucket)
- && isUnderSessionCountQuotaLocked(stats, standbyBucket);
+ && isUnderJobCountQuotaLocked(stats)
+ && isUnderSessionCountQuotaLocked(stats);
}
- private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats,
- final int standbyBucket) {
+ private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats) {
final long now = sElapsedRealtimeClock.millis();
final boolean isUnderAllowedTimeQuota =
(stats.jobRateLimitExpirationTimeElapsed <= now
@@ -923,8 +957,7 @@
&& stats.bgJobCountInWindow < stats.jobCountLimit;
}
- private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats,
- final int standbyBucket) {
+ private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats) {
final long now = sElapsedRealtimeClock.millis();
final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now
|| stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow);
@@ -1449,6 +1482,9 @@
stats.jobCountInRateLimitingWindow = 0;
}
stats.jobCountInRateLimitingWindow += count;
+ if (Flags.countQuotaFix()) {
+ stats.bgJobCountInWindow += count;
+ }
}
}
@@ -1683,10 +1719,11 @@
changedJobs.add(js);
}
} else if (realStandbyBucket != EXEMPTED_INDEX && realStandbyBucket != ACTIVE_INDEX
- && realStandbyBucket == js.getEffectiveStandbyBucket()) {
+ && realStandbyBucket == js.getEffectiveStandbyBucket()
+ && !(Flags.countQuotaFix() && mService.isCurrentlyRunningLocked(js))) {
// An app in the ACTIVE bucket may be out of quota while the job could be in quota
// for some reason. Therefore, avoid setting the real value here and check each job
- // individually.
+ // individually. Running job need to determine its own quota status as well.
if (setConstraintSatisfied(js, nowElapsed, realInQuota, isWithinEJQuota)) {
changedJobs.add(js);
}
@@ -1805,9 +1842,8 @@
}
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
- final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
- final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
- standbyBucket);
+ final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats);
+ final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats);
final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName);
final boolean inRegularQuota =
@@ -2126,6 +2162,13 @@
mBgJobCount++;
if (mRegularJobTimer) {
incrementJobCountLocked(mPkg.userId, mPkg.packageName, 1);
+ if (Flags.countQuotaFix()) {
+ final ExecutionStats stats = getExecutionStatsLocked(mPkg.userId,
+ mPkg.packageName, jobStatus.getEffectiveStandbyBucket(), false);
+ if (!isUnderJobCountQuotaLocked(stats)) {
+ mHandler.obtainMessage(MSG_REACHED_COUNT_QUOTA, mPkg).sendToTarget();
+ }
+ }
}
if (mRunningBgJobs.size() == 1) {
// Started tracking the first job.
@@ -2257,7 +2300,6 @@
// repeatedly plugged in and unplugged, or an app changes foreground state
// very frequently, the job count for a package may be artificially high.
mBgJobCount = mRunningBgJobs.size();
-
if (mRegularJobTimer) {
incrementJobCountLocked(mPkg.userId, mPkg.packageName, mBgJobCount);
// Starting the timer means that all cached execution stats are now
@@ -2284,7 +2326,8 @@
return;
}
Message msg = mHandler.obtainMessage(
- mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
+ mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA,
+ mPkg);
final long timeRemainingMs = mRegularJobTimer
? getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName)
: getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName);
@@ -2301,7 +2344,7 @@
private void cancelCutoff() {
mHandler.removeMessages(
- mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
+ mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA, mPkg);
}
public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
@@ -2557,7 +2600,7 @@
break;
default:
if (DEBUG) {
- Slog.d(TAG, "Dropping event " + event.getEventType());
+ Slog.d(TAG, "Dropping usage event " + event.getEventType());
}
break;
}
@@ -2666,7 +2709,7 @@
public void handleMessage(Message msg) {
synchronized (mLock) {
switch (msg.what) {
- case MSG_REACHED_QUOTA: {
+ case MSG_REACHED_TIME_QUOTA: {
UserPackage pkg = (UserPackage) msg.obj;
if (DEBUG) {
Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
@@ -2685,7 +2728,7 @@
// This could potentially happen if an old session phases out while a
// job is currently running.
// Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg);
+ Message rescheduleMsg = obtainMessage(MSG_REACHED_TIME_QUOTA, pkg);
timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId,
pkg.packageName);
if (DEBUG) {
@@ -2695,7 +2738,7 @@
}
break;
}
- case MSG_REACHED_EJ_QUOTA: {
+ case MSG_REACHED_EJ_TIME_QUOTA: {
UserPackage pkg = (UserPackage) msg.obj;
if (DEBUG) {
Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota.");
@@ -2713,7 +2756,7 @@
// This could potentially happen if an old session phases out while a
// job is currently running.
// Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_QUOTA, pkg);
+ Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_TIME_QUOTA, pkg);
timeRemainingMs = getTimeUntilEJQuotaConsumedLocked(
pkg.userId, pkg.packageName);
if (DEBUG) {
@@ -2723,6 +2766,18 @@
}
break;
}
+ case MSG_REACHED_COUNT_QUOTA: {
+ UserPackage pkg = (UserPackage) msg.obj;
+ if (DEBUG) {
+ Slog.d(TAG, pkg + " has reached its count quota.");
+ }
+
+ mStateChangedListener.onControllerStateChanged(
+ maybeUpdateConstraintForPkgLocked(
+ sElapsedRealtimeClock.millis(),
+ pkg.userId, pkg.packageName));
+ break;
+ }
case MSG_CLEAN_UP_SESSIONS:
if (DEBUG) {
Slog.d(TAG, "Cleaning up timing sessions.");
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 613678b..410074e 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1989,6 +1989,9 @@
mAdminProtectedPackages.put(userId, packageNames);
}
}
+ if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) {
+ postCheckIdleStates(userId);
+ }
}
@Override
diff --git a/config/Android.bp b/config/Android.bp
index 6a6f848..dd681ca 100644
--- a/config/Android.bp
+++ b/config/Android.bp
@@ -33,3 +33,9 @@
name: "preloaded-classes-denylist",
srcs: ["preloaded-classes-denylist"],
}
+
+prebuilt_etc {
+ name: "dirty-image-objects",
+ src: "dirty-image-objects",
+ filename: "dirty-image-objects",
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index c189a24c..c7fce1b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -282,7 +282,7 @@
field public static final String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
field public static final String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
field public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE = "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE";
- field @FlaggedApi("android.companion.flags.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE";
+ field @FlaggedApi("android.companion.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE";
field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
field public static final String RUN_USER_INITIATED_JOBS = "android.permission.RUN_USER_INITIATED_JOBS";
@@ -26804,7 +26804,6 @@
field public static final int STATE_FAST_FORWARDING = 4; // 0x4
field public static final int STATE_NONE = 0; // 0x0
field public static final int STATE_PAUSED = 2; // 0x2
- field @FlaggedApi("com.android.media.flags.enable_notifying_activity_manager_with_media_session_status_change") public static final int STATE_PLAYBACK_SUPPRESSED = 12; // 0xc
field public static final int STATE_PLAYING = 3; // 0x3
field public static final int STATE_REWINDING = 5; // 0x5
field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa
@@ -32743,7 +32742,7 @@
field public static final int S_V2 = 32; // 0x20
field public static final int TIRAMISU = 33; // 0x21
field public static final int UPSIDE_DOWN_CAKE = 34; // 0x22
- field @FlaggedApi("android.os.android_os_build_vanilla_ice_cream") public static final int VANILLA_ICE_CREAM = 10000; // 0x2710
+ field public static final int VANILLA_ICE_CREAM = 10000; // 0x2710
}
public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
@@ -39388,10 +39387,8 @@
}
public final class FileIntegrityManager {
- method @FlaggedApi("android.security.fsverity_api") @Nullable public byte[] getFsVerityDigest(@NonNull java.io.File) throws java.io.IOException;
method public boolean isApkVeritySupported();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public boolean isAppSourceCertificateTrusted(@NonNull java.security.cert.X509Certificate) throws java.security.cert.CertificateEncodingException;
- method @FlaggedApi("android.security.fsverity_api") public void setupFsVerity(@NonNull java.io.File) throws java.io.IOException;
}
public final class KeyChain {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b767c52..45bcd0d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -355,6 +355,7 @@
field public static final String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS";
field public static final String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION";
field public static final String SERIAL_PORT = "android.permission.SERIAL_PORT";
+ field @FlaggedApi("android.security.fsverity_api") public static final String SETUP_FSVERITY = "android.permission.SETUP_FSVERITY";
field public static final String SET_ACTIVITY_WATCHER = "android.permission.SET_ACTIVITY_WATCHER";
field public static final String SET_CLIP_SOURCE = "android.permission.SET_CLIP_SOURCE";
field public static final String SET_DEFAULT_ACCOUNT_FOR_CONTACTS = "android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS";
@@ -12106,6 +12107,11 @@
package android.security {
+ public final class FileIntegrityManager {
+ method @FlaggedApi("android.security.fsverity_api") @Nullable public byte[] getFsVerityDigest(@NonNull java.io.File) throws java.io.IOException;
+ method @FlaggedApi("android.security.fsverity_api") public void setupFsVerity(@NonNull java.io.File) throws java.io.IOException;
+ }
+
public final class KeyChain {
method @Nullable @WorkerThread public static String getWifiKeyGrantAsUser(@NonNull android.content.Context, @NonNull android.os.UserHandle, @NonNull String);
method @WorkerThread public static boolean hasWifiKeyGrantAsUser(@NonNull android.content.Context, @NonNull android.os.UserHandle, @NonNull String);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 443a6c0e..1383096 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1769,14 +1769,16 @@
}
public final class InputManager {
- method public void addUniqueIdAssociation(@NonNull String, @NonNull String);
+ method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociationByDescriptor(@NonNull String, @NonNull String);
+ method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociationByPort(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings();
method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptors();
method @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping();
method public int getMousePointerSpeed();
method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int);
- method public void removeUniqueIdAssociation(@NonNull String);
+ method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByDescriptor(@NonNull String);
+ method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByPort(@NonNull String);
field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL
}
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 685ea63..e6265ae 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -977,8 +977,16 @@
Method 'setHdmiCecVersion' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociation(String, String):
Method 'addUniqueIdAssociation' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociationByDescriptor(String, String):
+ Method 'addUniqueIdAssociationByDescriptor' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociationByPort(String, String):
+ Method 'addUniqueIdAssociationByPort' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociation(String):
Method 'removeUniqueIdAssociation' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociationByDescriptor(String):
+ Method 'removeUniqueIdAssociationByDescriptor' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociationByPort(String):
+ Method 'removeUniqueIdAssociationByPort' documentation mentions permissions already declared by @RequiresPermission
RequiresPermission: android.hardware.location.GeofenceHardware#addGeofence(int, int, android.hardware.location.GeofenceHardwareRequest, android.hardware.location.GeofenceHardwareCallback):
Method 'addGeofence' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.hardware.location.GeofenceHardware#getMonitoringTypes():
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 0dab3de..5e9fdfb 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1349,12 +1349,26 @@
public static final int RESTRICTION_LEVEL_BACKGROUND_RESTRICTED = 50;
/**
- * The most restricted level where the apps are considered "in-hibernation",
- * its package visibility to the rest of the system is limited.
+ * The restricted level where the apps are in a force-stopped state.
*
* @hide
*/
- public static final int RESTRICTION_LEVEL_HIBERNATION = 60;
+ public static final int RESTRICTION_LEVEL_FORCE_STOPPED = 60;
+
+ /**
+ * The heavily background restricted level, where apps cannot start without an explicit
+ * launch by the user.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_USER_LAUNCH_ONLY = 70;
+
+ /**
+ * A reserved restriction level that is not well-defined.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_CUSTOM = 90;
/**
* Not a valid restriction level, it defines the maximum numerical value of restriction level.
@@ -1371,12 +1385,116 @@
RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
RESTRICTION_LEVEL_RESTRICTED_BUCKET,
RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
- RESTRICTION_LEVEL_HIBERNATION,
+ RESTRICTION_LEVEL_FORCE_STOPPED,
+ RESTRICTION_LEVEL_USER_LAUNCH_ONLY,
+ RESTRICTION_LEVEL_CUSTOM,
RESTRICTION_LEVEL_MAX,
})
@Retention(RetentionPolicy.SOURCE)
public @interface RestrictionLevel{}
+ /**
+ * Maximum string length for sub reason for restriction.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_SUBREASON_MAX_LENGTH = 16;
+
+ /**
+ * Restriction reason unknown - do not use directly.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_UNKNOWN = 0;
+
+ /**
+ * Restriction reason to be used when this is normal behavior for the state.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_DEFAULT = 1;
+
+ /**
+ * Restriction reason is some kind of timeout that moves the app to a more restricted state.
+ * The threshold should specify how long the app was dormant, in milliseconds.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_DORMANT = 2;
+
+ /**
+ * Restriction reason to be used when removing a restriction due to direct or indirect usage
+ * of the app, especially to undo any automatic restrictions.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_USAGE = 3;
+
+ /**
+ * Restriction reason to be used when the user chooses to manually restrict the app, through
+ * UI or command line interface.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_USER = 4;
+
+ /**
+ * Restriction reason to be used when the user chooses to manually restrict the app on being
+ * prompted by the OS or some anomaly detection algorithm. For example, if the app is causing
+ * high battery drain or affecting system performance and the OS recommends that the user
+ * restrict the app.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_USER_NUDGED = 5;
+
+ /**
+ * Restriction reason to be used when the OS automatically detects that the app is causing
+ * system health issues such as performance degradation, battery drain, high memory usage, etc.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_SYSTEM_HEALTH = 6;
+
+ /**
+ * Restriction reason to be used when there is a server-side decision made to restrict an app
+ * that is showing widespread problems on user devices, or violating policy in some way.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_REMOTE_TRIGGER = 7;
+
+ /**
+ * Restriction reason to be used when some other problem requires restricting the app.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_OTHER = 8;
+
+ /** @hide */
+ @IntDef(prefix = { "RESTRICTION_REASON_" }, value = {
+ RESTRICTION_REASON_UNKNOWN,
+ RESTRICTION_REASON_DEFAULT,
+ RESTRICTION_REASON_DORMANT,
+ RESTRICTION_REASON_USAGE,
+ RESTRICTION_REASON_USER,
+ RESTRICTION_REASON_USER_NUDGED,
+ RESTRICTION_REASON_SYSTEM_HEALTH,
+ RESTRICTION_REASON_REMOTE_TRIGGER,
+ RESTRICTION_REASON_OTHER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RestrictionReason{}
+
/** @hide */
@android.ravenwood.annotation.RavenwoodKeep
public static String restrictionLevelToName(@RestrictionLevel int level) {
@@ -1393,12 +1511,16 @@
return "restricted_bucket";
case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
return "background_restricted";
- case RESTRICTION_LEVEL_HIBERNATION:
- return "hibernation";
+ case RESTRICTION_LEVEL_FORCE_STOPPED:
+ return "stopped";
+ case RESTRICTION_LEVEL_USER_LAUNCH_ONLY:
+ return "user_only";
+ case RESTRICTION_LEVEL_CUSTOM:
+ return "custom";
case RESTRICTION_LEVEL_MAX:
return "max";
default:
- return "";
+ return String.valueOf(level);
}
}
@@ -6127,6 +6249,43 @@
}
/**
+ * Requests the system to log the reason for restricting/unrestricting an app. This API
+ * should be called before applying any change to the restriction level.
+ * <p>
+ * The {@code enabled} value determines whether the state is being applied or removed.
+ * Not all restrictions are actual restrictions. For example,
+ * {@link #RESTRICTION_LEVEL_ADAPTIVE} is a normal state, where there is default lifecycle
+ * management applied to the app. Also, {@link #RESTRICTION_LEVEL_EXEMPTED} is used when the
+ * app is being put in a power-save allowlist.
+ *
+ * @param packageName the package name of the app
+ * @param uid the uid of the app
+ * @param restrictionLevel the restriction level specified in {@code RestrictionLevel}
+ * @param enabled whether the state is being applied or removed
+ * @param reason the reason for the restriction state change, from {@code RestrictionReason}
+ * @param subReason a string sub reason limited to 16 characters that specifies additional
+ * information about the reason for restriction.
+ * @param threshold for reasons that are due to exceeding some threshold, the threshold value
+ * must be specified. The unit of the threshold depends on the reason and/or
+ * subReason. For time, use milliseconds. For memory, use KB. For count, use
+ * the actual count or normalized as per-hour. For power, use milliwatts. Etc.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.DEVICE_POWER)
+ public void noteAppRestrictionEnabled(@NonNull String packageName, int uid,
+ @RestrictionLevel int restrictionLevel, boolean enabled,
+ @RestrictionReason int reason,
+ @Nullable String subReason, long threshold) {
+ try {
+ getService().noteAppRestrictionEnabled(packageName, uid, restrictionLevel, enabled,
+ reason, subReason, threshold);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Notifies {@link #getRunningAppProcesses app processes} that the system properties
* have changed.
*
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index eaa23b9..bc66127 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1000,6 +1000,7 @@
boolean autoStopProfiler;
boolean streamingOutput;
int mClockType;
+ int mProfilerOutputVersion;
boolean profiling;
boolean handlingProfiling;
public void setProfiler(ProfilerInfo profilerInfo) {
@@ -1027,6 +1028,7 @@
autoStopProfiler = profilerInfo.autoStopProfiler;
streamingOutput = profilerInfo.streamingOutput;
mClockType = profilerInfo.clockType;
+ mProfilerOutputVersion = profilerInfo.profilerOutputVersion;
}
public void startProfiling() {
if (profileFd == null || profiling) {
@@ -1034,9 +1036,11 @@
}
try {
int bufferSize = SystemProperties.getInt("debug.traceview-buffer-size-mb", 8);
+ int flags = 0;
+ flags = mClockType | ProfilerInfo.getFlagsForOutputVersion(mProfilerOutputVersion);
VMDebug.startMethodTracing(profileFile, profileFd.getFileDescriptor(),
- bufferSize * 1024 * 1024, mClockType, samplingInterval != 0,
- samplingInterval, streamingOutput);
+ bufferSize * 1024 * 1024, flags, samplingInterval != 0, samplingInterval,
+ streamingOutput);
profiling = true;
} catch (RuntimeException e) {
Slog.w(TAG, "Profiling failed on path " + profileFile, e);
@@ -7204,6 +7208,7 @@
mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler;
mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput;
mProfiler.mClockType = data.initProfilerInfo.clockType;
+ mProfiler.mProfilerOutputVersion = data.initProfilerInfo.profilerOutputVersion;
if (data.initProfilerInfo.attachAgentDuringBind) {
agent = data.initProfilerInfo.agent;
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index dca164d..3765c81 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -1009,4 +1009,12 @@
* @param originatingUid The UID of the instrumented app that initialized the override
*/
void clearAllOverridePermissionStates(int originatingUid);
+
+ /**
+ * Request the system to log the reason for restricting / unrestricting an app.
+ * @see ActivityManager#noteAppRestrictionEnabled
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DEVICE_POWER)")
+ void noteAppRestrictionEnabled(in String packageName, int uid, int restrictionType,
+ boolean enabled, int reason, in String subReason, long threshold);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index dbde7d2..6ff1bfc5 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2583,6 +2583,7 @@
this.when = System.currentTimeMillis();
if (updateRankingTime()) {
creationTime = when;
+ extras.putBoolean(EXTRA_SHOW_WHEN, true);
} else {
this.creationTime = System.currentTimeMillis();
}
@@ -2598,6 +2599,7 @@
{
if (updateRankingTime()) {
creationTime = when;
+ extras.putBoolean(EXTRA_SHOW_WHEN, true);
}
new Builder(context)
.setWhen(when)
@@ -2630,6 +2632,7 @@
this.when = when;
if (updateRankingTime()) {
creationTime = when;
+ extras.putBoolean(EXTRA_SHOW_WHEN, true);
} else {
this.creationTime = System.currentTimeMillis();
}
@@ -4382,14 +4385,16 @@
/**
* Add a timestamp pertaining to the notification (usually the time the event occurred).
*
- * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
- * shown anymore by default and must be opted into by using
- * {@link android.app.Notification.Builder#setShowWhen(boolean)}
- *
* @see Notification#when
*/
@NonNull
public Builder setWhen(long when) {
+ if (updateRankingTime()) {
+ // don't show a timestamp that's decades old
+ if (mN.extras.getBoolean(EXTRA_SHOW_WHEN, true) && when == 0) {
+ return this;
+ }
+ }
mN.when = when;
return this;
}
@@ -4397,8 +4402,6 @@
/**
* Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
* in the content view.
- * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to
- * {@code false}. For earlier apps, the default is {@code true}.
*/
@NonNull
public Builder setShowWhen(boolean show) {
diff --git a/core/java/android/app/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java
index f7a3d78..bcae22a 100644
--- a/core/java/android/app/ProfilerInfo.java
+++ b/core/java/android/app/ProfilerInfo.java
@@ -32,7 +32,8 @@
* {@hide}
*/
public class ProfilerInfo implements Parcelable {
-
+ // Version of the profiler output
+ public static final int OUTPUT_VERSION_DEFAULT = 1;
// CLOCK_TYPE_DEFAULT chooses the default used by ART. ART uses CLOCK_TYPE_DUAL by default (see
// kDefaultTraceClockSource in art/runtime/runtime_globals.h).
public static final int CLOCK_TYPE_DEFAULT = 0x000;
@@ -43,6 +44,9 @@
public static final int CLOCK_TYPE_WALL = 0x010;
public static final int CLOCK_TYPE_THREAD_CPU = 0x100;
public static final int CLOCK_TYPE_DUAL = 0x110;
+ // The second and third bits of the flags field specify the trace format version. This should
+ // match with kTraceFormatVersionShift defined in art/runtime/trace.h.
+ public static final int TRACE_FORMAT_VERSION_SHIFT = 1;
private static final String TAG = "ProfilerInfo";
@@ -83,8 +87,14 @@
*/
public final int clockType;
+ /**
+ * Indicates the version of profiler output.
+ */
+ public final int profilerOutputVersion;
+
public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop,
- boolean streaming, String agent, boolean attachAgentDuringBind, int clockType) {
+ boolean streaming, String agent, boolean attachAgentDuringBind, int clockType,
+ int profilerOutputVersion) {
profileFile = filename;
profileFd = fd;
samplingInterval = interval;
@@ -93,6 +103,7 @@
this.clockType = clockType;
this.agent = agent;
this.attachAgentDuringBind = attachAgentDuringBind;
+ this.profilerOutputVersion = profilerOutputVersion;
}
public ProfilerInfo(ProfilerInfo in) {
@@ -104,6 +115,7 @@
agent = in.agent;
attachAgentDuringBind = in.attachAgentDuringBind;
clockType = in.clockType;
+ profilerOutputVersion = in.profilerOutputVersion;
}
/**
@@ -125,13 +137,29 @@
}
/**
+ * Get the flags that need to be passed to VMDebug.startMethodTracing to specify the desired
+ * output format.
+ */
+ public static int getFlagsForOutputVersion(int version) {
+ // Only two version 1 and version 2 are supported. Just use the default if we see an unknown
+ // version.
+ if (version != 1 || version != 2) {
+ version = OUTPUT_VERSION_DEFAULT;
+ }
+
+ // The encoded version in the flags starts from 0, where as the version that we read from
+ // user starts from 1. So, subtract one before encoding it in the flags.
+ return (version - 1) << TRACE_FORMAT_VERSION_SHIFT;
+ }
+
+ /**
* Return a new ProfilerInfo instance, with fields populated from this object,
* and {@link agent} and {@link attachAgentDuringBind} as given.
*/
public ProfilerInfo setAgent(String agent, boolean attachAgentDuringBind) {
return new ProfilerInfo(this.profileFile, this.profileFd, this.samplingInterval,
this.autoStopProfiler, this.streamingOutput, agent, attachAgentDuringBind,
- this.clockType);
+ this.clockType, this.profilerOutputVersion);
}
/**
@@ -172,6 +200,7 @@
out.writeString(agent);
out.writeBoolean(attachAgentDuringBind);
out.writeInt(clockType);
+ out.writeInt(profilerOutputVersion);
}
/** @hide */
@@ -186,6 +215,7 @@
proto.write(ProfilerInfoProto.STREAMING_OUTPUT, streamingOutput);
proto.write(ProfilerInfoProto.AGENT, agent);
proto.write(ProfilerInfoProto.CLOCK_TYPE, clockType);
+ proto.write(ProfilerInfoProto.PROFILER_OUTPUT_VERSION, profilerOutputVersion);
proto.end(token);
}
@@ -211,6 +241,7 @@
agent = in.readString();
attachAgentDuringBind = in.readBoolean();
clockType = in.readInt();
+ profilerOutputVersion = in.readInt();
}
@Override
@@ -226,9 +257,9 @@
return Objects.equals(profileFile, other.profileFile)
&& autoStopProfiler == other.autoStopProfiler
&& samplingInterval == other.samplingInterval
- && streamingOutput == other.streamingOutput
- && Objects.equals(agent, other.agent)
- && clockType == other.clockType;
+ && streamingOutput == other.streamingOutput && Objects.equals(agent, other.agent)
+ && clockType == other.clockType
+ && profilerOutputVersion == other.profilerOutputVersion;
}
@Override
@@ -240,6 +271,7 @@
result = 31 * result + (streamingOutput ? 1 : 0);
result = 31 * result + Objects.hashCode(agent);
result = 31 * result + clockType;
+ result = 31 * result + profilerOutputVersion;
return result;
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 1aee9fe..a9f2d74 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -317,11 +317,6 @@
public abstract boolean isUserOrganizationManaged(@UserIdInt int userId);
/**
- * Returns whether the application exemptions feature flag is enabled.
- */
- public abstract boolean isApplicationExemptionsFlagEnabled();
-
- /**
* Returns a map of admin to {@link Bundle} map of restrictions set by the admins for the
* provided {@code packageName} in the provided {@code userId}
*/
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 6a07484..ac843cb 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -195,6 +195,25 @@
}
}
+flag {
+ name: "power_exemption_bg_usage_fix"
+ namespace: "enterprise"
+ description: "Ensure aps with EXEMPT_FROM_POWER_RESTRICTIONS can execute in the background"
+ bug: "333379020"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "disallow_user_control_bg_usage_fix"
+ namespace: "enterprise"
+ description: "Make DPM.setUserControlDisabledPackages() ensure background usage is allowed"
+ bug: "326031059"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
flag {
name: "esim_management_ux_enabled"
@@ -228,6 +247,16 @@
}
flag {
+ name: "always_persist_do"
+ namespace: "enterprise"
+ description: "Always write device_owners2.xml so that migration flags aren't lost"
+ bug: "335232744"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "is_recursive_required_app_merging_enabled"
namespace: "enterprise"
description: "Guards a new flow for recursive required enterprise app list merging"
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 722d5f0..c9b4aa1 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -23,6 +23,8 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.AnyThread;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.app.Activity;
import android.app.ActivityThread;
@@ -94,6 +96,7 @@
* The listener will be invoked with two parameters: {@link Activity#getActivityToken()} and
* {@link ActivityWindowInfo}.
*/
+ @AnyThread
public void registerActivityWindowInfoChangedListener(
@NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
if (!activityWindowInfoFlag()) {
@@ -108,6 +111,7 @@
* Unregisters the listener that was previously registered via
* {@link #registerActivityWindowInfoChangedListener(BiConsumer)}
*/
+ @AnyThread
public void unregisterActivityWindowInfoChangedListener(
@NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
if (!activityWindowInfoFlag()) {
@@ -122,6 +126,7 @@
* Called when receives a {@link ClientTransaction} that is updating an activity's
* {@link ActivityWindowInfo}.
*/
+ @MainThread
public void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
@NonNull ActivityWindowInfo activityWindowInfo) {
if (!activityWindowInfoFlag()) {
@@ -141,17 +146,20 @@
}
/** Called when starts executing a remote {@link ClientTransaction}. */
+ @MainThread
public void onClientTransactionStarted() {
mIsClientTransactionExecuting = true;
}
/** Called when finishes executing a remote {@link ClientTransaction}. */
+ @MainThread
public void onClientTransactionFinished() {
notifyDisplayManagerIfNeeded();
mIsClientTransactionExecuting = false;
}
/** Called before updating the Configuration of the given {@code context}. */
+ @MainThread
public void onContextConfigurationPreChanged(@NonNull Context context) {
if (!bundleClientTransactionFlag() || ActivityThread.isSystem()) {
// Not enable for system server.
@@ -166,6 +174,7 @@
}
/** Called after updating the Configuration of the given {@code context}. */
+ @MainThread
public void onContextConfigurationPostChanged(@NonNull Context context) {
if (!bundleClientTransactionFlag() || ActivityThread.isSystem()) {
// Not enable for system server.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f5bff9d..4c0da7c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4844,16 +4844,6 @@
public static final String FEATURE_ROTARY_ENCODER_LOW_RES =
"android.hardware.rotaryencoder.lowres";
- /**
- * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
- * support for contextual search helper.
- *
- * @hide
- */
- @SdkConstant(SdkConstantType.FEATURE)
- public static final String FEATURE_CONTEXTUAL_SEARCH_HELPER =
- "android.software.contextualsearch";
-
/** @hide */
public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 4963a4f..321e539 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -171,6 +171,13 @@
}
flag {
+ name: "schedule_stop_of_background_user"
+ namespace: "multiuser"
+ description: "Schedule background users to be stopped at a future point."
+ bug: "330351042"
+}
+
+flag {
name: "disable_private_space_items_on_home"
namespace: "profile_experiences"
description: "Disables adding items belonging to Private Space on Home Screen manually as well as automatically"
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index eb7afb8e..481ff2e 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -134,6 +134,13 @@
"enable_credential_description_api";
/**
+ * @hide
+ */
+ @Hide
+ public static final String EXTRA_AUTOFILL_RESULT_RECEIVER =
+ "android.credentials.AUTOFILL_RESULT_RECEIVER";
+
+ /**
* @hide instantiated by ContextImpl.
*/
public CredentialManager(Context context, ICredentialManager service) {
diff --git a/core/java/android/credentials/selection/Constants.java b/core/java/android/credentials/selection/Constants.java
index 2229f25..a620621 100644
--- a/core/java/android/credentials/selection/Constants.java
+++ b/core/java/android/credentials/selection/Constants.java
@@ -28,12 +28,5 @@
*/
public static final String EXTRA_RESULT_RECEIVER =
"android.credentials.selection.extra.RESULT_RECEIVER";
-
- /**
- * The intent extra key for the final result receiver object
- */
- public static final String EXTRA_FINAL_RESPONSE_RECEIVER =
- "android.credentials.selection.extra.FINAL_RESPONSE_RECEIVER";
-
private Constants() {}
}
diff --git a/core/java/android/hardware/CameraSessionStats.java b/core/java/android/hardware/CameraSessionStats.java
index 32938ff..e5c12e3 100644
--- a/core/java/android/hardware/CameraSessionStats.java
+++ b/core/java/android/hardware/CameraSessionStats.java
@@ -16,9 +16,11 @@
package android.hardware;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Range;
import java.util.ArrayList;
import java.util.List;
+
/**
* The camera action state used for passing camera usage information from
* camera service to camera service proxy .
@@ -66,6 +68,7 @@
private int mVideoStabilizationMode;
private boolean mUsedUltraWide;
private boolean mUsedZoomOverride;
+ private Range<Integer> mMostRequestedFpsRange;
private int mSessionIndex;
private CameraExtensionSessionStats mCameraExtensionSessionStats;
@@ -86,6 +89,7 @@
mVideoStabilizationMode = -1;
mUsedUltraWide = false;
mUsedZoomOverride = false;
+ mMostRequestedFpsRange = new Range<Integer>(0, 0);
mSessionIndex = 0;
mCameraExtensionSessionStats = new CameraExtensionSessionStats();
}
@@ -109,6 +113,7 @@
mVideoStabilizationMode = -1;
mUsedUltraWide = false;
mUsedZoomOverride = false;
+ mMostRequestedFpsRange = new Range<Integer>(0, 0);
mSessionIndex = sessionIdx;
mCameraExtensionSessionStats = new CameraExtensionSessionStats();
}
@@ -158,6 +163,8 @@
dest.writeBoolean(mUsedZoomOverride);
dest.writeInt(mSessionIndex);
mCameraExtensionSessionStats.writeToParcel(dest, 0);
+ dest.writeInt(mMostRequestedFpsRange.getLower());
+ dest.writeInt(mMostRequestedFpsRange.getUpper());
}
public void readFromParcel(Parcel in) {
@@ -188,6 +195,9 @@
mSessionIndex = in.readInt();
mCameraExtensionSessionStats = CameraExtensionSessionStats.CREATOR.createFromParcel(in);
+ int minFps = in.readInt();
+ int maxFps = in.readInt();
+ mMostRequestedFpsRange = new Range<Integer>(minFps, maxFps);
}
public String getCameraId() {
@@ -273,4 +283,8 @@
public CameraExtensionSessionStats getExtensionSessionStats() {
return mCameraExtensionSessionStats;
}
+
+ public Range<Integer> getMostRequestedFpsRange() {
+ return mMostRequestedFpsRange;
+ }
}
diff --git a/core/java/android/hardware/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
index 4a4d451..7b452a8 100644
--- a/core/java/android/hardware/OverlayProperties.java
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -74,7 +74,7 @@
* and {@link HardwareBuffer.Format} is supported on the device.
*
* @return True if the device can support efficiently compositing the content described by the
- * dataspace and format. False if GPOU composition fallback is otherwise required.
+ * dataspace and format. False if GPU composition fallback is otherwise required.
*/
@FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
public boolean isCombinationSupported(@DataSpace.ColorDataSpace int dataspace,
@@ -135,7 +135,6 @@
private static native long nGetDestructor();
private static native long nCreateDefault();
- private static native boolean nSupportFp16ForHdr(long nativeObject);
private static native boolean nSupportMixedColorSpaces(long nativeObject);
private static native boolean nIsCombinationSupported(
long nativeObject, int dataspace, int format);
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 89ab105..ec67212 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -364,14 +364,6 @@
public abstract List<RefreshRateLimitation> getRefreshRateLimitations(int displayId);
/**
- * Returns if vrr support is enabled for specified display
- *
- * @param displayId The id of the display.
- * @return true if associated display supports dvrr
- */
- public abstract boolean isVrrSupportEnabled(int displayId);
-
- /**
* For the given displayId, updates if WindowManager is responsible for mirroring on that
* display. If {@code false}, then SurfaceFlinger performs no layer mirroring to the
* given display.
diff --git a/core/java/android/hardware/face/FaceCallback.java b/core/java/android/hardware/face/FaceCallback.java
new file mode 100644
index 0000000..b69024f
--- /dev/null
+++ b/core/java/android/hardware/face/FaceCallback.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.face;
+
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_VENDOR;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_VENDOR_BASE;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_VENDOR;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_VENDOR_BASE;
+import static android.hardware.face.FaceManager.getAuthHelpMessage;
+import static android.hardware.face.FaceManager.getEnrollHelpMessage;
+import static android.hardware.face.FaceManager.getErrorString;
+
+import android.content.Context;
+import android.hardware.biometrics.CryptoObject;
+import android.hardware.face.FaceManager.AuthenticationCallback;
+import android.hardware.face.FaceManager.EnrollmentCallback;
+import android.hardware.face.FaceManager.FaceDetectionCallback;
+import android.hardware.face.FaceManager.GenerateChallengeCallback;
+import android.hardware.face.FaceManager.GetFeatureCallback;
+import android.hardware.face.FaceManager.RemovalCallback;
+import android.hardware.face.FaceManager.SetFeatureCallback;
+import android.util.Slog;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Encapsulates callbacks and client specific information for each face related request.
+ * @hide
+ */
+public class FaceCallback {
+ private static final String TAG = " FaceCallback";
+
+ @Nullable
+ private AuthenticationCallback mAuthenticationCallback;
+ @Nullable
+ private EnrollmentCallback mEnrollmentCallback;
+ @Nullable
+ private RemovalCallback mRemovalCallback;
+ @Nullable
+ private GenerateChallengeCallback mGenerateChallengeCallback;
+ @Nullable
+ private FaceDetectionCallback mFaceDetectionCallback;
+ @Nullable
+ private SetFeatureCallback mSetFeatureCallback;
+ @Nullable
+ private GetFeatureCallback mGetFeatureCallback;
+ @Nullable
+ private Face mRemovalFace;
+ @Nullable
+ private CryptoObject mCryptoObject;
+
+ /**
+ * Construction for face authentication client callback.
+ */
+ FaceCallback(AuthenticationCallback authenticationCallback, CryptoObject cryptoObject) {
+ mAuthenticationCallback = authenticationCallback;
+ mCryptoObject = cryptoObject;
+ }
+
+ /**
+ * Construction for face detect client callback.
+ */
+ FaceCallback(FaceDetectionCallback faceDetectionCallback) {
+ mFaceDetectionCallback = faceDetectionCallback;
+ }
+
+ /**
+ * Construction for face enroll client callback.
+ */
+ FaceCallback(EnrollmentCallback enrollmentCallback) {
+ mEnrollmentCallback = enrollmentCallback;
+ }
+
+ /**
+ * Construction for face generate challenge client callback.
+ */
+ FaceCallback(GenerateChallengeCallback generateChallengeCallback) {
+ mGenerateChallengeCallback = generateChallengeCallback;
+ }
+
+ /**
+ * Construction for face set feature client callback.
+ */
+ FaceCallback(SetFeatureCallback setFeatureCallback) {
+ mSetFeatureCallback = setFeatureCallback;
+ }
+
+ /**
+ * Construction for face get feature client callback.
+ */
+ FaceCallback(GetFeatureCallback getFeatureCallback) {
+ mGetFeatureCallback = getFeatureCallback;
+ }
+
+ /**
+ * Construction for single face removal client callback.
+ */
+ FaceCallback(RemovalCallback removalCallback, Face removalFace) {
+ mRemovalCallback = removalCallback;
+ mRemovalFace = removalFace;
+ }
+
+ /**
+ * Construction for all face removal client callback.
+ */
+ FaceCallback(RemovalCallback removalCallback) {
+ mRemovalCallback = removalCallback;
+ }
+
+ /**
+ * Propagate set feature completed via the callback.
+ * @param success if the operation was completed successfully
+ * @param feature the feature that was set
+ */
+ public void sendSetFeatureCompleted(boolean success, int feature) {
+ if (mSetFeatureCallback == null) {
+ return;
+ }
+ mSetFeatureCallback.onCompleted(success, feature);
+ }
+
+ /**
+ * Propagate get feature completed via the callback.
+ * @param success if the operation was completed successfully
+ * @param features list of features available
+ * @param featureState status of the features corresponding to the previous parameter
+ */
+ public void sendGetFeatureCompleted(boolean success, int[] features, boolean[] featureState) {
+ if (mGetFeatureCallback == null) {
+ return;
+ }
+ mGetFeatureCallback.onCompleted(success, features, featureState);
+ }
+
+ /**
+ * Propagate challenge generated completed via the callback.
+ * @param sensorId id of the corresponding sensor
+ * @param userId id of the corresponding sensor
+ * @param challenge value of the challenge generated
+ */
+ public void sendChallengeGenerated(int sensorId, int userId, long challenge) {
+ if (mGenerateChallengeCallback == null) {
+ return;
+ }
+ mGenerateChallengeCallback.onGenerateChallengeResult(sensorId, userId, challenge);
+ }
+
+ /**
+ * Propagate face detected completed via the callback.
+ * @param sensorId id of the corresponding sensor
+ * @param userId id of the corresponding user
+ * @param isStrongBiometric if the sensor is strong or not
+ */
+ public void sendFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
+ if (mFaceDetectionCallback == null) {
+ Slog.e(TAG, "sendFaceDetected, callback null");
+ return;
+ }
+ mFaceDetectionCallback.onFaceDetected(sensorId, userId, isStrongBiometric);
+ }
+
+ /**
+ * Propagate remove face completed via the callback.
+ * @param face removed identifier
+ * @param remaining number of face enrollments remaining
+ */
+ public void sendRemovedResult(Face face, int remaining) {
+ if (mRemovalCallback == null) {
+ return;
+ }
+ mRemovalCallback.onRemovalSucceeded(face, remaining);
+ }
+
+ /**
+ * Propagate errors via the callback.
+ * @param context corresponding context
+ * @param errMsgId represents the framework error id
+ * @param vendorCode represents the vendor error code
+ */
+ public void sendErrorResult(Context context, int errMsgId, int vendorCode) {
+ // emulate HAL 2.1 behavior and send real errMsgId
+ final int clientErrMsgId = errMsgId == FACE_ERROR_VENDOR
+ ? (vendorCode + FACE_ERROR_VENDOR_BASE) : errMsgId;
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mRemovalCallback != null) {
+ mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mFaceDetectionCallback != null) {
+ mFaceDetectionCallback.onDetectionError(errMsgId);
+ mFaceDetectionCallback = null;
+ }
+ }
+
+ /**
+ * Propagate enroll progress via the callback.
+ * @param remaining number of enrollment steps remaining
+ */
+ public void sendEnrollResult(int remaining) {
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentProgress(remaining);
+ }
+ }
+
+ /**
+ * Propagate authentication succeeded via the callback.
+ * @param face matched identifier
+ * @param userId id of the corresponding user
+ * @param isStrongBiometric if the sensor is strong or not
+ */
+ public void sendAuthenticatedSucceeded(Face face, int userId, boolean isStrongBiometric) {
+ if (mAuthenticationCallback != null) {
+ final FaceManager.AuthenticationResult result = new FaceManager.AuthenticationResult(
+ mCryptoObject, face, userId, isStrongBiometric);
+ mAuthenticationCallback.onAuthenticationSucceeded(result);
+ }
+ }
+
+ /**
+ * Propagate authentication failed via the callback.
+ */
+ public void sendAuthenticatedFailed() {
+ if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationFailed();
+ }
+ }
+
+ /**
+ * Propagate acquired result via the callback.
+ * @param context corresponding context
+ * @param acquireInfo represents the framework acquired id
+ * @param vendorCode represents the vendor acquired code
+ */
+ public void sendAcquiredResult(Context context, int acquireInfo, int vendorCode) {
+ if (mAuthenticationCallback != null) {
+ final FaceAuthenticationFrame frame = new FaceAuthenticationFrame(
+ new FaceDataFrame(acquireInfo, vendorCode));
+ sendAuthenticationFrame(context, frame);
+ } else if (mEnrollmentCallback != null) {
+ final FaceEnrollFrame frame = new FaceEnrollFrame(
+ null /* cell */,
+ FaceEnrollStages.UNKNOWN,
+ new FaceDataFrame(acquireInfo, vendorCode));
+ sendEnrollmentFrame(context, frame);
+ }
+ }
+
+ /**
+ * Propagate authentication frame via the callback.
+ * @param context corresponding context
+ * @param frame authentication frame to be sent
+ */
+ public void sendAuthenticationFrame(@NonNull Context context,
+ @Nullable FaceAuthenticationFrame frame) {
+ if (frame == null) {
+ Slog.w(TAG, "Received null authentication frame");
+ } else if (mAuthenticationCallback != null) {
+ // TODO(b/178414967): Send additional frame data to callback
+ final int acquireInfo = frame.getData().getAcquiredInfo();
+ final int vendorCode = frame.getData().getVendorCode();
+ final int helpCode = getHelpCode(acquireInfo, vendorCode);
+ final String helpMessage = getAuthHelpMessage(context, acquireInfo, vendorCode);
+ mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
+
+ // Ensure that only non-null help messages are sent.
+ if (helpMessage != null) {
+ mAuthenticationCallback.onAuthenticationHelp(helpCode, helpMessage);
+ }
+ }
+ }
+
+ /**
+ * Propagate enrollment via the callback.
+ * @param context corresponding context
+ * @param frame enrollment frame to be sent
+ */
+ public void sendEnrollmentFrame(Context context, @Nullable FaceEnrollFrame frame) {
+ if (frame == null) {
+ Slog.w(TAG, "Received null enrollment frame");
+ } else if (mEnrollmentCallback != null) {
+ final FaceDataFrame data = frame.getData();
+ final int acquireInfo = data.getAcquiredInfo();
+ final int vendorCode = data.getVendorCode();
+ final int helpCode = getHelpCode(acquireInfo, vendorCode);
+ final String helpMessage = getEnrollHelpMessage(context, acquireInfo, vendorCode);
+ mEnrollmentCallback.onEnrollmentFrame(
+ helpCode,
+ helpMessage,
+ frame.getCell(),
+ frame.getStage(),
+ data.getPan(),
+ data.getTilt(),
+ data.getDistance());
+ }
+ }
+
+ private static int getHelpCode(int acquireInfo, int vendorCode) {
+ return acquireInfo == FACE_ACQUIRED_VENDOR
+ ? vendorCode + FACE_ACQUIRED_VENDOR_BASE
+ : acquireInfo;
+ }
+}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 210ce2b..2592630 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -37,9 +37,9 @@
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IRemoteCallback;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.Trace;
@@ -49,7 +49,6 @@
import android.view.Surface;
import com.android.internal.R;
-import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
import java.util.List;
@@ -63,71 +62,56 @@
private static final String TAG = "FaceManager";
- private static final int MSG_ENROLL_RESULT = 100;
- private static final int MSG_ACQUIRED = 101;
- private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
- private static final int MSG_AUTHENTICATION_FAILED = 103;
- private static final int MSG_ERROR = 104;
- private static final int MSG_REMOVED = 105;
- private static final int MSG_GET_FEATURE_COMPLETED = 106;
- private static final int MSG_SET_FEATURE_COMPLETED = 107;
- private static final int MSG_CHALLENGE_GENERATED = 108;
- private static final int MSG_FACE_DETECTED = 109;
- private static final int MSG_AUTHENTICATION_FRAME = 112;
- private static final int MSG_ENROLLMENT_FRAME = 113;
-
private final IFaceService mService;
private final Context mContext;
private final IBinder mToken = new Binder();
- @Nullable private AuthenticationCallback mAuthenticationCallback;
- @Nullable private FaceDetectionCallback mFaceDetectionCallback;
- @Nullable private EnrollmentCallback mEnrollmentCallback;
- @Nullable private RemovalCallback mRemovalCallback;
- @Nullable private SetFeatureCallback mSetFeatureCallback;
- @Nullable private GetFeatureCallback mGetFeatureCallback;
- @Nullable private GenerateChallengeCallback mGenerateChallengeCallback;
- private CryptoObject mCryptoObject;
- private Face mRemovalFace;
private Handler mHandler;
private List<FaceSensorPropertiesInternal> mProps = new ArrayList<>();
+ private HandlerExecutor mExecutor;
- private final IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
+ private class FaceServiceReceiver extends IFaceServiceReceiver.Stub {
+ private final FaceCallback mFaceCallback;
+
+ FaceServiceReceiver(FaceCallback faceCallback) {
+ mFaceCallback = faceCallback;
+ }
@Override // binder call
public void onEnrollResult(Face face, int remaining) {
- mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, face).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendEnrollResult(remaining));
}
@Override // binder call
public void onAcquired(int acquireInfo, int vendorCode) {
- mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendAcquiredResult(mContext, acquireInfo,
+ vendorCode));
}
@Override // binder call
public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId,
- isStrongBiometric ? 1 : 0, face).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendAuthenticatedSucceeded(face, userId,
+ isStrongBiometric));
}
@Override // binder call
public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
- mHandler.obtainMessage(MSG_FACE_DETECTED, sensorId, userId, isStrongBiometric)
- .sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendFaceDetected(sensorId, userId,
+ isStrongBiometric));
}
@Override // binder call
public void onAuthenticationFailed() {
- mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
+ mExecutor.execute(mFaceCallback::sendAuthenticatedFailed);
}
@Override // binder call
public void onError(int error, int vendorCode) {
- mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendErrorResult(mContext, error, vendorCode));
}
@Override // binder call
public void onRemoved(Face face, int remaining) {
- mHandler.obtainMessage(MSG_REMOVED, remaining, 0, face).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendRemovedResult(face, remaining));
if (remaining == 0) {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
@@ -137,34 +121,31 @@
@Override
public void onFeatureSet(boolean success, int feature) {
- mHandler.obtainMessage(MSG_SET_FEATURE_COMPLETED, feature, 0, success).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendSetFeatureCompleted(success, feature));
}
@Override
public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = success;
- args.arg2 = features;
- args.arg3 = featureState;
- mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendGetFeatureCompleted(success, features,
+ featureState));
}
@Override
public void onChallengeGenerated(int sensorId, int userId, long challenge) {
- mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
- .sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendChallengeGenerated(sensorId, userId,
+ challenge));
}
@Override
public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendAuthenticationFrame(mContext, frame));
}
@Override
public void onEnrollmentFrame(FaceEnrollFrame frame) {
- mHandler.obtainMessage(MSG_ENROLLMENT_FRAME, frame).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendEnrollmentFrame(mContext, frame));
}
- };
+ }
/**
* @hide
@@ -175,7 +156,8 @@
if (mService == null) {
Slog.v(TAG, "FaceAuthenticationManagerService was null");
}
- mHandler = new MyHandler(context);
+ mHandler = context.getMainThreadHandler();
+ mExecutor = new HandlerExecutor(mHandler);
if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
== PackageManager.PERMISSION_GRANTED) {
addAuthenticatorsRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
@@ -193,9 +175,11 @@
*/
private void useHandler(Handler handler) {
if (handler != null) {
- mHandler = new MyHandler(handler.getLooper());
- } else if (mHandler.getLooper() != mContext.getMainLooper()) {
- mHandler = new MyHandler(mContext.getMainLooper());
+ mHandler = handler;
+ mExecutor = new HandlerExecutor(mHandler);
+ } else if (mHandler != mContext.getMainThreadHandler()) {
+ mHandler = mContext.getMainThreadHandler();
+ mExecutor = new HandlerExecutor(mHandler);
}
}
@@ -249,13 +233,12 @@
if (mService != null) {
try {
+ final FaceCallback faceCallback = new FaceCallback(callback, crypto);
useHandler(handler);
- mAuthenticationCallback = callback;
- mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
Trace.beginSection("FaceManager#authenticate");
final long authId = mService.authenticate(
- mToken, operationId, mServiceReceiver, options);
+ mToken, operationId, new FaceServiceReceiver(faceCallback), options);
if (cancel != null) {
cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
}
@@ -292,10 +275,11 @@
options.setOpPackageName(mContext.getOpPackageName());
options.setAttributionTag(mContext.getAttributionTag());
- mFaceDetectionCallback = callback;
+ final FaceCallback faceCallback = new FaceCallback(callback);
try {
- final long authId = mService.detectFace(mToken, mServiceReceiver, options);
+ final long authId = mService.detectFace(mToken,
+ new FaceServiceReceiver(faceCallback), options);
cancel.setOnCancelListener(new OnFaceDetectionCancelListener(authId));
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception when requesting finger detect", e);
@@ -367,11 +351,11 @@
if (mService != null) {
try {
- mEnrollmentCallback = callback;
+ final FaceCallback faceCallback = new FaceCallback(callback);
Trace.beginSection("FaceManager#enroll");
final long enrollId = mService.enroll(userId, mToken, hardwareAuthToken,
- mServiceReceiver, mContext.getOpPackageName(), disabledFeatures,
- previewSurface, debugConsent, options);
+ new FaceServiceReceiver(faceCallback), mContext.getOpPackageName(),
+ disabledFeatures, previewSurface, debugConsent, options);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
}
@@ -419,10 +403,11 @@
if (mService != null) {
try {
- mEnrollmentCallback = callback;
+ final FaceCallback faceCallback = new FaceCallback(callback);
Trace.beginSection("FaceManager#enrollRemotely");
final long enrolId = mService.enrollRemotely(userId, mToken, hardwareAuthToken,
- mServiceReceiver, mContext.getOpPackageName(), disabledFeatures);
+ new FaceServiceReceiver(faceCallback), mContext.getOpPackageName(),
+ disabledFeatures);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrolId));
}
@@ -455,9 +440,9 @@
public void generateChallenge(int sensorId, int userId, GenerateChallengeCallback callback) {
if (mService != null) {
try {
- mGenerateChallengeCallback = callback;
- mService.generateChallenge(mToken, sensorId, userId, mServiceReceiver,
- mContext.getOpPackageName());
+ final FaceCallback faceCallback = new FaceCallback(callback);
+ mService.generateChallenge(mToken, sensorId, userId,
+ new FaceServiceReceiver(faceCallback), mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -528,9 +513,9 @@
SetFeatureCallback callback) {
if (mService != null) {
try {
- mSetFeatureCallback = callback;
+ final FaceCallback faceCallback = new FaceCallback(callback);
mService.setFeature(mToken, userId, feature, enabled, hardwareAuthToken,
- mServiceReceiver, mContext.getOpPackageName());
+ new FaceServiceReceiver(faceCallback), mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -544,8 +529,8 @@
public void getFeature(int userId, int feature, GetFeatureCallback callback) {
if (mService != null) {
try {
- mGetFeatureCallback = callback;
- mService.getFeature(mToken, userId, feature, mServiceReceiver,
+ final FaceCallback faceCallback = new FaceCallback(callback);
+ mService.getFeature(mToken, userId, feature, new FaceServiceReceiver(faceCallback),
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -566,10 +551,9 @@
public void remove(Face face, int userId, RemovalCallback callback) {
if (mService != null) {
try {
- mRemovalCallback = callback;
- mRemovalFace = face;
- mService.remove(mToken, face.getBiometricId(), userId, mServiceReceiver,
- mContext.getOpPackageName());
+ final FaceCallback faceCallback = new FaceCallback(callback, face);
+ mService.remove(mToken, face.getBiometricId(), userId,
+ new FaceServiceReceiver(faceCallback), mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -584,8 +568,9 @@
public void removeAll(int userId, @NonNull RemovalCallback callback) {
if (mService != null) {
try {
- mRemovalCallback = callback;
- mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+ final FaceCallback faceCallback = new FaceCallback(callback);
+ mService.removeAll(mToken, userId, new FaceServiceReceiver(faceCallback),
+ mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1270,203 +1255,6 @@
}
}
- private class MyHandler extends Handler {
- private MyHandler(Context context) {
- super(context.getMainLooper());
- }
-
- private MyHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(android.os.Message msg) {
- Trace.beginSection("FaceManager#handleMessage: " + Integer.toString(msg.what));
- switch (msg.what) {
- case MSG_ENROLL_RESULT:
- sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
- break;
- case MSG_ACQUIRED:
- sendAcquiredResult(msg.arg1 /* acquire info */, msg.arg2 /* vendorCode */);
- break;
- case MSG_AUTHENTICATION_SUCCEEDED:
- sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */,
- msg.arg2 == 1 /* isStrongBiometric */);
- break;
- case MSG_AUTHENTICATION_FAILED:
- sendAuthenticatedFailed();
- break;
- case MSG_ERROR:
- sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */);
- break;
- case MSG_REMOVED:
- sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */);
- break;
- case MSG_SET_FEATURE_COMPLETED:
- sendSetFeatureCompleted((boolean) msg.obj /* success */,
- msg.arg1 /* feature */);
- break;
- case MSG_GET_FEATURE_COMPLETED:
- SomeArgs args = (SomeArgs) msg.obj;
- sendGetFeatureCompleted((boolean) args.arg1 /* success */,
- (int[]) args.arg2 /* features */,
- (boolean[]) args.arg3 /* featureState */);
- args.recycle();
- break;
- case MSG_CHALLENGE_GENERATED:
- sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
- (long) msg.obj /* challenge */);
- break;
- case MSG_FACE_DETECTED:
- sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
- (boolean) msg.obj /* isStrongBiometric */);
- break;
- case MSG_AUTHENTICATION_FRAME:
- sendAuthenticationFrame((FaceAuthenticationFrame) msg.obj /* frame */);
- break;
- case MSG_ENROLLMENT_FRAME:
- sendEnrollmentFrame((FaceEnrollFrame) msg.obj /* frame */);
- break;
- default:
- Slog.w(TAG, "Unknown message: " + msg.what);
- }
- Trace.endSection();
- }
- }
-
- private void sendSetFeatureCompleted(boolean success, int feature) {
- if (mSetFeatureCallback == null) {
- return;
- }
- mSetFeatureCallback.onCompleted(success, feature);
- }
-
- private void sendGetFeatureCompleted(boolean success, int[] features, boolean[] featureState) {
- if (mGetFeatureCallback == null) {
- return;
- }
- mGetFeatureCallback.onCompleted(success, features, featureState);
- }
-
- private void sendChallengeGenerated(int sensorId, int userId, long challenge) {
- if (mGenerateChallengeCallback == null) {
- return;
- }
- mGenerateChallengeCallback.onGenerateChallengeResult(sensorId, userId, challenge);
- }
-
- private void sendFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
- if (mFaceDetectionCallback == null) {
- Slog.e(TAG, "sendFaceDetected, callback null");
- return;
- }
- mFaceDetectionCallback.onFaceDetected(sensorId, userId, isStrongBiometric);
- }
-
- private void sendRemovedResult(Face face, int remaining) {
- if (mRemovalCallback == null) {
- return;
- }
- mRemovalCallback.onRemovalSucceeded(face, remaining);
- }
-
- private void sendErrorResult(int errMsgId, int vendorCode) {
- // emulate HAL 2.1 behavior and send real errMsgId
- final int clientErrMsgId = errMsgId == FACE_ERROR_VENDOR
- ? (vendorCode + FACE_ERROR_VENDOR_BASE) : errMsgId;
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mRemovalCallback != null) {
- mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mFaceDetectionCallback != null) {
- mFaceDetectionCallback.onDetectionError(errMsgId);
- mFaceDetectionCallback = null;
- }
- }
-
- private void sendEnrollResult(Face face, int remaining) {
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentProgress(remaining);
- }
- }
-
- private void sendAuthenticatedSucceeded(Face face, int userId, boolean isStrongBiometric) {
- if (mAuthenticationCallback != null) {
- final AuthenticationResult result =
- new AuthenticationResult(mCryptoObject, face, userId, isStrongBiometric);
- mAuthenticationCallback.onAuthenticationSucceeded(result);
- }
- }
-
- private void sendAuthenticatedFailed() {
- if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationFailed();
- }
- }
-
- private void sendAcquiredResult(int acquireInfo, int vendorCode) {
- if (mAuthenticationCallback != null) {
- final FaceAuthenticationFrame frame = new FaceAuthenticationFrame(
- new FaceDataFrame(acquireInfo, vendorCode));
- sendAuthenticationFrame(frame);
- } else if (mEnrollmentCallback != null) {
- final FaceEnrollFrame frame = new FaceEnrollFrame(
- null /* cell */,
- FaceEnrollStages.UNKNOWN,
- new FaceDataFrame(acquireInfo, vendorCode));
- sendEnrollmentFrame(frame);
- }
- }
-
- private void sendAuthenticationFrame(@Nullable FaceAuthenticationFrame frame) {
- if (frame == null) {
- Slog.w(TAG, "Received null authentication frame");
- } else if (mAuthenticationCallback != null) {
- // TODO(b/178414967): Send additional frame data to callback
- final int acquireInfo = frame.getData().getAcquiredInfo();
- final int vendorCode = frame.getData().getVendorCode();
- final int helpCode = getHelpCode(acquireInfo, vendorCode);
- final String helpMessage = getAuthHelpMessage(mContext, acquireInfo, vendorCode);
- mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
-
- // Ensure that only non-null help messages are sent.
- if (helpMessage != null) {
- mAuthenticationCallback.onAuthenticationHelp(helpCode, helpMessage);
- }
- }
- }
-
- private void sendEnrollmentFrame(@Nullable FaceEnrollFrame frame) {
- if (frame == null) {
- Slog.w(TAG, "Received null enrollment frame");
- } else if (mEnrollmentCallback != null) {
- final FaceDataFrame data = frame.getData();
- final int acquireInfo = data.getAcquiredInfo();
- final int vendorCode = data.getVendorCode();
- final int helpCode = getHelpCode(acquireInfo, vendorCode);
- final String helpMessage = getEnrollHelpMessage(mContext, acquireInfo, vendorCode);
- mEnrollmentCallback.onEnrollmentFrame(
- helpCode,
- helpMessage,
- frame.getCell(),
- frame.getStage(),
- data.getPan(),
- data.getTilt(),
- data.getDistance());
- }
- }
-
- private static int getHelpCode(int acquireInfo, int vendorCode) {
- return acquireInfo == FACE_ACQUIRED_VENDOR
- ? vendorCode + FACE_ACQUIRED_VENDOR_BASE
- : acquireInfo;
- }
-
/**
* @hide
*/
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 553d9f7..5a0f3db 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -164,15 +164,9 @@
void getFeature(IBinder token, int userId, int feature, IFaceServiceReceiver receiver,
String opPackageName);
- // Registers all HIDL and AIDL sensors. Only HIDL sensor properties need to be provided, because
- // AIDL sensor properties are retrieved directly from the available HALs. If no HIDL HALs exist,
- // hidlSensors must be non-null and empty. See AuthService.java
- @EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void registerAuthenticators(in List<FaceSensorPropertiesInternal> hidlSensors);
-
//Register all available face sensors.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void registerAuthenticatorsLegacy(in FaceSensorConfigurations faceSensorConfigurations);
+ void registerAuthenticators(in FaceSensorConfigurations faceSensorConfigurations);
// Adds a callback which gets called when the service registers all of the face
// authenticators. The callback is automatically removed after it's invoked.
diff --git a/core/java/android/hardware/fingerprint/FingerprintCallback.java b/core/java/android/hardware/fingerprint/FingerprintCallback.java
new file mode 100644
index 0000000..e4fbe6e
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/FingerprintCallback.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.fingerprint;
+
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR_BASE;
+import static android.hardware.fingerprint.FingerprintManager.getAcquiredString;
+import static android.hardware.fingerprint.FingerprintManager.getErrorString;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
+import android.hardware.fingerprint.FingerprintManager.CryptoObject;
+import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback;
+import android.hardware.fingerprint.FingerprintManager.FingerprintDetectionCallback;
+import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback;
+import android.hardware.fingerprint.FingerprintManager.RemovalCallback;
+import android.util.Slog;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Encapsulates callbacks and client specific information for each fingerprint related request.
+ * @hide
+ */
+public class FingerprintCallback {
+ private static final String TAG = "FingerprintCallback";
+ public static final int REMOVE_SINGLE = 1;
+ public static final int REMOVE_ALL = 2;
+ @IntDef({REMOVE_SINGLE, REMOVE_ALL})
+ public @interface RemoveRequest {}
+ @Nullable
+ private AuthenticationCallback mAuthenticationCallback;
+ @Nullable
+ private EnrollmentCallback mEnrollmentCallback;
+ @Nullable
+ private RemovalCallback mRemovalCallback;
+ @Nullable
+ private GenerateChallengeCallback mGenerateChallengeCallback;
+ @Nullable
+ private FingerprintDetectionCallback mFingerprintDetectionCallback;
+ @Nullable
+ private CryptoObject mCryptoObject;
+ @Nullable
+ private @RemoveRequest int mRemoveRequest;
+ @Nullable
+ private Fingerprint mRemoveFingerprint;
+
+ /**
+ * Construction for fingerprint authentication client callback.
+ */
+ FingerprintCallback(@NonNull AuthenticationCallback authenticationCallback,
+ @Nullable CryptoObject cryptoObject) {
+ mAuthenticationCallback = authenticationCallback;
+ mCryptoObject = cryptoObject;
+ }
+
+ /**
+ * Construction for fingerprint detect client callback.
+ */
+ FingerprintCallback(@NonNull FingerprintDetectionCallback fingerprintDetectionCallback) {
+ mFingerprintDetectionCallback = fingerprintDetectionCallback;
+ }
+
+ /**
+ * Construction for fingerprint enroll client callback.
+ */
+ FingerprintCallback(@NonNull EnrollmentCallback enrollmentCallback) {
+ mEnrollmentCallback = enrollmentCallback;
+ }
+
+ /**
+ * Construction for fingerprint generate challenge client callback.
+ */
+ FingerprintCallback(@NonNull GenerateChallengeCallback generateChallengeCallback) {
+ mGenerateChallengeCallback = generateChallengeCallback;
+ }
+
+ /**
+ * Construction for fingerprint removal client callback.
+ */
+ FingerprintCallback(@NonNull RemovalCallback removalCallback, @RemoveRequest int removeRequest,
+ @Nullable Fingerprint removeFingerprint) {
+ mRemovalCallback = removalCallback;
+ mRemoveRequest = removeRequest;
+ mRemoveFingerprint = removeFingerprint;
+ }
+
+ /**
+ * Propagate enroll progress via the callback.
+ * @param remaining number of enrollment steps remaining
+ */
+ public void sendEnrollResult(int remaining) {
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentProgress(remaining);
+ }
+ }
+
+ /**
+ * Propagate remove face completed via the callback.
+ * @param fingerprint removed identifier
+ * @param remaining number of face enrollments remaining
+ */
+ public void sendRemovedResult(@Nullable Fingerprint fingerprint, int remaining) {
+ if (mRemovalCallback == null) {
+ return;
+ }
+
+ if (mRemoveRequest == REMOVE_SINGLE) {
+ if (fingerprint == null) {
+ Slog.e(TAG, "Received MSG_REMOVED, but fingerprint is null");
+ return;
+ }
+
+ if (mRemoveFingerprint == null) {
+ Slog.e(TAG, "Missing fingerprint");
+ return;
+ }
+
+ final int fingerId = fingerprint.getBiometricId();
+ int reqFingerId = mRemoveFingerprint.getBiometricId();
+ if (reqFingerId != 0 && fingerId != 0 && fingerId != reqFingerId) {
+ Slog.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId);
+ return;
+ }
+ }
+
+ mRemovalCallback.onRemovalSucceeded(fingerprint, remaining);
+ }
+
+ /**
+ * Propagate authentication succeeded via the callback.
+ * @param fingerprint matched identifier
+ * @param userId id of the corresponding user
+ * @param isStrongBiometric if the sensor is strong or not
+ */
+ public void sendAuthenticatedSucceeded(@NonNull Fingerprint fingerprint, int userId,
+ boolean isStrongBiometric) {
+ if (mAuthenticationCallback == null) {
+ Slog.e(TAG, "Authentication succeeded but callback is null.");
+ return;
+ }
+
+ final AuthenticationResult result = new AuthenticationResult(mCryptoObject, fingerprint,
+ userId, isStrongBiometric);
+ mAuthenticationCallback.onAuthenticationSucceeded(result);
+ }
+
+ /**
+ * Propagate authentication failed via the callback.
+ */
+ public void sendAuthenticatedFailed() {
+ if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationFailed();
+ }
+ }
+
+ /**
+ * Propagate acquired result via the callback.
+ * @param context corresponding context
+ * @param acquireInfo represents the framework acquired id
+ * @param vendorCode represents the vendor acquired code
+ */
+ public void sendAcquiredResult(@NonNull Context context, int acquireInfo, int vendorCode) {
+ if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
+ }
+ if (mEnrollmentCallback != null && acquireInfo != FINGERPRINT_ACQUIRED_START) {
+ mEnrollmentCallback.onAcquired(acquireInfo == FINGERPRINT_ACQUIRED_GOOD);
+ }
+ final String msg = getAcquiredString(context, acquireInfo, vendorCode);
+ if (msg == null) {
+ return;
+ }
+ // emulate HAL 2.1 behavior and send real acquiredInfo
+ final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR
+ ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo;
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
+ } else if (mAuthenticationCallback != null) {
+ if (acquireInfo != FINGERPRINT_ACQUIRED_START) {
+ mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
+ }
+ }
+ }
+
+ /**
+ * Propagate errors via the callback.
+ * @param context corresponding context
+ * @param errMsgId represents the framework error id
+ * @param vendorCode represents the vendor error code
+ */
+ public void sendErrorResult(@NonNull Context context, int errMsgId, int vendorCode) {
+ // emulate HAL 2.1 behavior and send real errMsgId
+ final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR
+ ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId;
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mRemovalCallback != null) {
+ mRemovalCallback.onRemovalError(mRemoveFingerprint, clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mFingerprintDetectionCallback != null) {
+ mFingerprintDetectionCallback.onDetectionError(errMsgId);
+ mFingerprintDetectionCallback = null;
+ }
+ }
+
+ /**
+ * Propagate challenge generated completed via the callback.
+ * @param sensorId id of the corresponding sensor
+ * @param userId id of the corresponding sensor
+ * @param challenge value of the challenge generated
+ */
+ public void sendChallengeGenerated(long challenge, int sensorId, int userId) {
+ if (mGenerateChallengeCallback == null) {
+ Slog.e(TAG, "sendChallengeGenerated, callback null");
+ return;
+ }
+ mGenerateChallengeCallback.onChallengeGenerated(sensorId, userId, challenge);
+ }
+
+ /**
+ * Propagate fingerprint detected completed via the callback.
+ * @param sensorId id of the corresponding sensor
+ * @param userId id of the corresponding user
+ * @param isStrongBiometric if the sensor is strong or not
+ */
+ public void sendFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
+ if (mFingerprintDetectionCallback == null) {
+ Slog.e(TAG, "sendFingerprintDetected, callback null");
+ return;
+ }
+ mFingerprintDetectionCallback.onFingerprintDetected(sensorId, userId, isStrongBiometric);
+ }
+
+ /**
+ * Propagate udfps pointer down via the callback.
+ * @param sensorId id of the corresponding sensor
+ */
+ public void sendUdfpsPointerDown(int sensorId) {
+ if (mAuthenticationCallback == null) {
+ Slog.e(TAG, "sendUdfpsPointerDown, callback null");
+ } else {
+ mAuthenticationCallback.onUdfpsPointerDown(sensorId);
+ }
+
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onUdfpsPointerDown(sensorId);
+ }
+ }
+
+ /**
+ * Propagate udfps pointer up via the callback.
+ * @param sensorId id of the corresponding sensor
+ */
+ public void sendUdfpsPointerUp(int sensorId) {
+ if (mAuthenticationCallback == null) {
+ Slog.e(TAG, "sendUdfpsPointerUp, callback null");
+ } else {
+ mAuthenticationCallback.onUdfpsPointerUp(sensorId);
+ }
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onUdfpsPointerUp(sensorId);
+ }
+ }
+
+ /**
+ * Propagate udfps overlay shown via the callback.
+ */
+ public void sendUdfpsOverlayShown() {
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onUdfpsOverlayShown();
+ }
+ }
+}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 81e321d..37f2fb2 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -25,6 +25,8 @@
import static android.Manifest.permission.USE_FINGERPRINT;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT;
+import static android.hardware.fingerprint.FingerprintCallback.REMOVE_ALL;
+import static android.hardware.fingerprint.FingerprintCallback.REMOVE_SINGLE;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_AUTHENTICATE;
@@ -57,9 +59,9 @@
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IRemoteCallback;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -94,19 +96,6 @@
@RequiresFeature(PackageManager.FEATURE_FINGERPRINT)
public class FingerprintManager implements BiometricAuthenticator, BiometricFingerprintConstants {
private static final String TAG = "FingerprintManager";
- private static final boolean DEBUG = true;
- private static final int MSG_ENROLL_RESULT = 100;
- private static final int MSG_ACQUIRED = 101;
- private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
- private static final int MSG_AUTHENTICATION_FAILED = 103;
- private static final int MSG_ERROR = 104;
- private static final int MSG_REMOVED = 105;
- private static final int MSG_CHALLENGE_GENERATED = 106;
- private static final int MSG_FINGERPRINT_DETECTED = 107;
- private static final int MSG_UDFPS_POINTER_DOWN = 108;
- private static final int MSG_UDFPS_POINTER_UP = 109;
- private static final int MSG_POWER_BUTTON_PRESSED = 110;
- private static final int MSG_UDFPS_OVERLAY_SHOWN = 111;
/**
* @hide
@@ -148,34 +137,14 @@
*/
public static final int SENSOR_ID_ANY = -1;
- private static class RemoveTracker {
- static final int REMOVE_SINGLE = 1;
- static final int REMOVE_ALL = 2;
- @IntDef({REMOVE_SINGLE, REMOVE_ALL})
- @interface RemoveRequest {}
+ private final IFingerprintService mService;
+ private final Context mContext;
+ private final IBinder mToken = new Binder();
- final @RemoveRequest int mRemoveRequest;
- @Nullable final Fingerprint mSingleFingerprint;
-
- RemoveTracker(@RemoveRequest int request, @Nullable Fingerprint fingerprint) {
- mRemoveRequest = request;
- mSingleFingerprint = fingerprint;
- }
- }
-
- private IFingerprintService mService;
- private Context mContext;
- private IBinder mToken = new Binder();
- private AuthenticationCallback mAuthenticationCallback;
- private FingerprintDetectionCallback mFingerprintDetectionCallback;
- private EnrollmentCallback mEnrollmentCallback;
- private RemovalCallback mRemovalCallback;
- private GenerateChallengeCallback mGenerateChallengeCallback;
- private CryptoObject mCryptoObject;
- @Nullable private RemoveTracker mRemoveTracker;
private Handler mHandler;
@Nullable private float[] mEnrollStageThresholds;
private List<FingerprintSensorPropertiesInternal> mProps = new ArrayList<>();
+ private HandlerExecutor mExecutor;
/**
* Retrieves a list of properties for all fingerprint sensors on the device.
@@ -395,7 +364,7 @@
* @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
*/
@Deprecated
- public static abstract class AuthenticationCallback
+ public abstract static class AuthenticationCallback
extends BiometricAuthenticator.AuthenticationCallback {
/**
* Called when an unrecoverable error has been encountered and the operation is complete.
@@ -479,7 +448,7 @@
*
* @hide
*/
- public static abstract class EnrollmentCallback {
+ public abstract static class EnrollmentCallback {
/**
* Called when an unrecoverable error has been encountered and the operation is complete.
* No further callbacks will be made on this object.
@@ -536,7 +505,7 @@
*
* @hide
*/
- public static abstract class RemovalCallback {
+ public abstract static class RemovalCallback {
/**
* Called when the given fingerprint can't be removed.
* @param fp The fingerprint that the call attempted to remove
@@ -559,7 +528,7 @@
/**
* @hide
*/
- public static abstract class LockoutResetCallback {
+ public abstract static class LockoutResetCallback {
/**
* Called when lockout period expired and clients are allowed to listen for fingerprint
@@ -584,9 +553,11 @@
*/
private void useHandler(Handler handler) {
if (handler != null) {
- mHandler = new MyHandler(handler.getLooper());
- } else if (mHandler.getLooper() != mContext.getMainLooper()) {
- mHandler = new MyHandler(mContext.getMainLooper());
+ mHandler = handler;
+ mExecutor = new HandlerExecutor(mHandler);
+ } else if (mHandler != mContext.getMainThreadHandler()) {
+ mHandler = mContext.getMainThreadHandler();
+ mExecutor = new HandlerExecutor(mHandler);
}
}
@@ -676,11 +647,12 @@
if (mService != null) {
try {
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback,
+ crypto);
useHandler(handler);
- mAuthenticationCallback = callback;
- mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
- final long authId = mService.authenticate(mToken, operationId, mServiceReceiver, options);
+ final long authId = mService.authenticate(mToken, operationId,
+ new FingerprintServiceReceiver(fingerprintCallback), options);
if (cancel != null) {
cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
}
@@ -715,10 +687,11 @@
options.setOpPackageName(mContext.getOpPackageName());
options.setAttributionTag(mContext.getAttributionTag());
- mFingerprintDetectionCallback = callback;
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback);
try {
- final long authId = mService.detectFingerprint(mToken, mServiceReceiver, options);
+ final long authId = mService.detectFingerprint(mToken,
+ new FingerprintServiceReceiver(fingerprintCallback), options);
cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener(authId));
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception when requesting finger detect", e);
@@ -767,9 +740,10 @@
if (mService != null) {
try {
- mEnrollmentCallback = callback;
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback);
final long enrollId = mService.enroll(mToken, hardwareAuthToken, userId,
- mServiceReceiver, mContext.getOpPackageName(), enrollReason, options);
+ new FingerprintServiceReceiver(fingerprintCallback),
+ mContext.getOpPackageName(), enrollReason, options);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
}
@@ -799,12 +773,13 @@
@RequiresPermission(MANAGE_FINGERPRINT)
public void generateChallenge(int sensorId, int userId, GenerateChallengeCallback callback) {
if (mService != null) try {
- mGenerateChallengeCallback = callback;
- mService.generateChallenge(mToken, sensorId, userId, mServiceReceiver,
- mContext.getOpPackageName());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback);
+ mService.generateChallenge(mToken, sensorId, userId,
+ new FingerprintServiceReceiver(fingerprintCallback),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -875,13 +850,14 @@
@RequiresPermission(MANAGE_FINGERPRINT)
public void remove(Fingerprint fp, int userId, RemovalCallback callback) {
if (mService != null) try {
- mRemovalCallback = callback;
- mRemoveTracker = new RemoveTracker(RemoveTracker.REMOVE_SINGLE, fp);
- mService.remove(mToken, fp.getBiometricId(), userId, mServiceReceiver,
- mContext.getOpPackageName());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback,
+ REMOVE_SINGLE, fp);
+ mService.remove(mToken, fp.getBiometricId(), userId,
+ new FingerprintServiceReceiver(fingerprintCallback),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -892,9 +868,11 @@
public void removeAll(int userId, @NonNull RemovalCallback callback) {
if (mService != null) {
try {
- mRemovalCallback = callback;
- mRemoveTracker = new RemoveTracker(RemoveTracker.REMOVE_ALL, null /* fp */);
- mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback,
+ REMOVE_ALL, null);
+ mService.removeAll(mToken, userId,
+ new FingerprintServiceReceiver(fingerprintCallback),
+ mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1162,7 +1140,7 @@
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public void onPowerPressed() {
Slog.i(TAG, "onPowerPressed");
- mHandler.obtainMessage(MSG_POWER_BUTTON_PRESSED).sendToTarget();
+ mExecutor.execute(() -> sendPowerPressed());
}
/**
@@ -1346,199 +1324,6 @@
}
}
- private class MyHandler extends Handler {
- private MyHandler(Context context) {
- super(context.getMainLooper());
- }
-
- private MyHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(android.os.Message msg) {
- switch (msg.what) {
- case MSG_ENROLL_RESULT:
- sendEnrollResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
- break;
- case MSG_ACQUIRED:
- sendAcquiredResult(msg.arg1 /* acquire info */,
- msg.arg2 /* vendorCode */);
- break;
- case MSG_AUTHENTICATION_SUCCEEDED:
- sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */,
- msg.arg2 == 1 /* isStrongBiometric */);
- break;
- case MSG_AUTHENTICATION_FAILED:
- sendAuthenticatedFailed();
- break;
- case MSG_ERROR:
- sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */);
- break;
- case MSG_REMOVED:
- sendRemovedResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
- break;
- case MSG_CHALLENGE_GENERATED:
- sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
- (long) msg.obj /* challenge */);
- break;
- case MSG_FINGERPRINT_DETECTED:
- sendFingerprintDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
- (boolean) msg.obj /* isStrongBiometric */);
- break;
- case MSG_UDFPS_POINTER_DOWN:
- sendUdfpsPointerDown(msg.arg1 /* sensorId */);
- break;
- case MSG_UDFPS_POINTER_UP:
- sendUdfpsPointerUp(msg.arg1 /* sensorId */);
- break;
- case MSG_POWER_BUTTON_PRESSED:
- sendPowerPressed();
- break;
- case MSG_UDFPS_OVERLAY_SHOWN:
- sendUdfpsOverlayShown();
- default:
- Slog.w(TAG, "Unknown message: " + msg.what);
-
- }
- }
- }
-
- private void sendRemovedResult(Fingerprint fingerprint, int remaining) {
- if (mRemovalCallback == null) {
- return;
- }
-
- if (mRemoveTracker == null) {
- Slog.w(TAG, "Removal tracker is null");
- return;
- }
-
- if (mRemoveTracker.mRemoveRequest == RemoveTracker.REMOVE_SINGLE) {
- if (fingerprint == null) {
- Slog.e(TAG, "Received MSG_REMOVED, but fingerprint is null");
- return;
- }
-
- if (mRemoveTracker.mSingleFingerprint == null) {
- Slog.e(TAG, "Missing fingerprint");
- return;
- }
-
- final int fingerId = fingerprint.getBiometricId();
- int reqFingerId = mRemoveTracker.mSingleFingerprint.getBiometricId();
- if (reqFingerId != 0 && fingerId != 0 && fingerId != reqFingerId) {
- Slog.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId);
- return;
- }
- }
-
- mRemovalCallback.onRemovalSucceeded(fingerprint, remaining);
- }
-
- private void sendEnrollResult(Fingerprint fp, int remaining) {
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentProgress(remaining);
- }
- }
-
- private void sendAuthenticatedSucceeded(Fingerprint fp, int userId, boolean isStrongBiometric) {
- if (mAuthenticationCallback != null) {
- final AuthenticationResult result =
- new AuthenticationResult(mCryptoObject, fp, userId, isStrongBiometric);
- mAuthenticationCallback.onAuthenticationSucceeded(result);
- }
- }
-
- private void sendAuthenticatedFailed() {
- if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationFailed();
- }
- }
-
- private void sendAcquiredResult(int acquireInfo, int vendorCode) {
- if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
- }
- if (mEnrollmentCallback != null && acquireInfo != FINGERPRINT_ACQUIRED_START) {
- mEnrollmentCallback.onAcquired(acquireInfo == FINGERPRINT_ACQUIRED_GOOD);
- }
- final String msg = getAcquiredString(mContext, acquireInfo, vendorCode);
- if (msg == null) {
- return;
- }
- // emulate HAL 2.1 behavior and send real acquiredInfo
- final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR
- ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo;
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
- } else if (mAuthenticationCallback != null) {
- if (acquireInfo != BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START) {
- mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
- }
- }
- }
-
- private void sendErrorResult(int errMsgId, int vendorCode) {
- // emulate HAL 2.1 behavior and send real errMsgId
- final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR
- ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId;
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mRemovalCallback != null) {
- final Fingerprint fp = mRemoveTracker != null
- ? mRemoveTracker.mSingleFingerprint : null;
- mRemovalCallback.onRemovalError(fp, clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mFingerprintDetectionCallback != null) {
- mFingerprintDetectionCallback.onDetectionError(errMsgId);
- mFingerprintDetectionCallback = null;
- }
- }
-
- private void sendChallengeGenerated(int sensorId, int userId, long challenge) {
- if (mGenerateChallengeCallback == null) {
- Slog.e(TAG, "sendChallengeGenerated, callback null");
- return;
- }
- mGenerateChallengeCallback.onChallengeGenerated(sensorId, userId, challenge);
- }
-
- private void sendFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
- if (mFingerprintDetectionCallback == null) {
- Slog.e(TAG, "sendFingerprintDetected, callback null");
- return;
- }
- mFingerprintDetectionCallback.onFingerprintDetected(sensorId, userId, isStrongBiometric);
- }
-
- private void sendUdfpsPointerDown(int sensorId) {
- if (mAuthenticationCallback == null) {
- Slog.e(TAG, "sendUdfpsPointerDown, callback null");
- } else {
- mAuthenticationCallback.onUdfpsPointerDown(sensorId);
- }
-
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onUdfpsPointerDown(sensorId);
- }
- }
-
- private void sendUdfpsPointerUp(int sensorId) {
- if (mAuthenticationCallback == null) {
- Slog.e(TAG, "sendUdfpsPointerUp, callback null");
- } else {
- mAuthenticationCallback.onUdfpsPointerUp(sensorId);
- }
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onUdfpsPointerUp(sensorId);
- }
- }
-
private void sendPowerPressed() {
try {
mService.onPowerPressed();
@@ -1547,12 +1332,6 @@
}
}
- private void sendUdfpsOverlayShown() {
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onUdfpsOverlayShown();
- }
- }
-
/**
* @hide
*/
@@ -1562,7 +1341,6 @@
if (mService == null) {
Slog.v(TAG, "FingerprintService was null");
}
- mHandler = new MyHandler(context);
if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
== PackageManager.PERMISSION_GRANTED) {
addAuthenticatorsRegisteredCallback(
@@ -1574,6 +1352,8 @@
}
});
}
+ mHandler = context.getMainThreadHandler();
+ mExecutor = new HandlerExecutor(mHandler);
}
private int getCurrentUserId() {
@@ -1773,66 +1553,72 @@
return null;
}
- private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() {
+ class FingerprintServiceReceiver extends IFingerprintServiceReceiver.Stub {
+ private final FingerprintCallback mFingerprintCallback;
+
+ FingerprintServiceReceiver(FingerprintCallback fingerprintCallback) {
+ mFingerprintCallback = fingerprintCallback;
+ }
@Override // binder call
public void onEnrollResult(Fingerprint fp, int remaining) {
- mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, fp).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendEnrollResult(remaining));
}
@Override // binder call
public void onAcquired(int acquireInfo, int vendorCode) {
- mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendAcquiredResult(mContext, acquireInfo,
+ vendorCode));
}
@Override // binder call
public void onAuthenticationSucceeded(Fingerprint fp, int userId,
boolean isStrongBiometric) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, isStrongBiometric ? 1 : 0,
- fp).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendAuthenticatedSucceeded(fp, userId,
+ isStrongBiometric));
}
@Override
public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
- mHandler.obtainMessage(MSG_FINGERPRINT_DETECTED, sensorId, userId, isStrongBiometric)
- .sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendFingerprintDetected(sensorId, userId,
+ isStrongBiometric));
}
@Override // binder call
public void onAuthenticationFailed() {
- mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
+ mExecutor.execute(mFingerprintCallback::sendAuthenticatedFailed);
}
@Override // binder call
public void onError(int error, int vendorCode) {
- mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendErrorResult(mContext, error,
+ vendorCode));
}
@Override // binder call
public void onRemoved(Fingerprint fp, int remaining) {
- mHandler.obtainMessage(MSG_REMOVED, remaining, 0, fp).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendRemovedResult(fp, remaining));
}
@Override // binder call
public void onChallengeGenerated(int sensorId, int userId, long challenge) {
- mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
- .sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendChallengeGenerated(challenge, sensorId,
+ userId));
}
@Override // binder call
public void onUdfpsPointerDown(int sensorId) {
- mHandler.obtainMessage(MSG_UDFPS_POINTER_DOWN, sensorId, 0).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendUdfpsPointerDown(sensorId));
}
@Override // binder call
public void onUdfpsPointerUp(int sensorId) {
- mHandler.obtainMessage(MSG_UDFPS_POINTER_UP, sensorId, 0).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendUdfpsPointerUp(sensorId));
}
@Override
public void onUdfpsOverlayShown() {
- mHandler.obtainMessage(MSG_UDFPS_OVERLAY_SHOWN).sendToTarget();
+ mExecutor.execute(mFingerprintCallback::sendUdfpsOverlayShown);
}
- };
-
+ }
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 8b37c24..6a96ac4 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -177,13 +177,7 @@
//Register all available fingerprint sensors.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void registerAuthenticatorsLegacy(in FingerprintSensorConfigurations fingerprintSensorConfigurations);
-
- // Registers all HIDL and AIDL sensors. Only HIDL sensor properties need to be provided, because
- // AIDL sensor properties are retrieved directly from the available HALs. If no HIDL HALs exist,
- // hidlSensors must be non-null and empty. See AuthService.java
- @EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void registerAuthenticators(in List<FingerprintSensorPropertiesInternal> hidlSensors);
+ void registerAuthenticators(in FingerprintSensorConfigurations fingerprintSensorConfigurations);
// Adds a callback which gets called when the service registers all of the fingerprint
// authenticators. The callback is automatically removed after it's invoked.
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 1c37aa2..2d474d6 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -165,10 +165,17 @@
// static association for the cleared input port will be restored.
void removePortAssociation(in String inputPort);
- // Add a runtime association between the input device and display.
- void addUniqueIdAssociation(in String inputPort, in String displayUniqueId);
- // Remove the runtime association between the input device and display.
- void removeUniqueIdAssociation(in String inputPort);
+ // Add a runtime association between the input device and display, using device's descriptor.
+ void addUniqueIdAssociationByDescriptor(in String inputDeviceDescriptor,
+ in String displayUniqueId);
+ // Remove the runtime association between the input device and display, using device's
+ // descriptor.
+ void removeUniqueIdAssociationByDescriptor(in String inputDeviceDescriptor);
+
+ // Add a runtime association between the input device and display, using device's port.
+ void addUniqueIdAssociationByPort(in String inputPort, in String displayUniqueId);
+ // Remove the runtime association between the input device and display, using device's port.
+ void removeUniqueIdAssociationByPort(in String inputPort);
InputSensorInfo[] getSensorList(int deviceId);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index f949158..4dda2c7 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -17,6 +17,7 @@
package android.hardware.input;
import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API;
+import static com.android.input.flags.Flags.FLAG_DEVICE_ASSOCIATIONS;
import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag;
import android.Manifest;
@@ -1054,13 +1055,14 @@
/**
* Add a runtime association between the input port and the display port. This overrides any
* static associations.
- * @param inputPort The port of the input device.
- * @param displayPort The physical port of the associated display.
+ * @param inputPort the port of the input device
+ * @param displayPort the physical port of the associated display
* <p>
* Requires {@link android.Manifest.permission#ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
* </p>
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
public void addPortAssociation(@NonNull String inputPort, int displayPort) {
try {
mIm.addPortAssociation(inputPort, displayPort);
@@ -1072,12 +1074,13 @@
/**
* Remove the runtime association between the input port and the display port. Any existing
* static association for the cleared input port will be restored.
- * @param inputPort The port of the input device to be cleared.
+ * @param inputPort the port of the input device to be cleared
* <p>
* Requires {@link android.Manifest.permission#ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
* </p>
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
public void removePortAssociation(@NonNull String inputPort) {
try {
mIm.removePortAssociation(inputPort);
@@ -1089,30 +1092,74 @@
/**
* Add a runtime association between the input port and display, by unique id. Input ports are
* expected to be unique.
- * @param inputPort The port of the input device.
- * @param displayUniqueId The unique id of the associated display.
+ * @param inputPort the port of the input device
+ * @param displayUniqueId the unique id of the associated display
* <p>
* Requires {@link android.Manifest.permission#ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
* </p>
* @hide
*/
+ @FlaggedApi(FLAG_DEVICE_ASSOCIATIONS)
+ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
@TestApi
- public void addUniqueIdAssociation(@NonNull String inputPort,
+ public void addUniqueIdAssociationByPort(@NonNull String inputPort,
@NonNull String displayUniqueId) {
- mGlobal.addUniqueIdAssociation(inputPort, displayUniqueId);
+ mGlobal.addUniqueIdAssociationByPort(inputPort, displayUniqueId);
}
/**
* Removes a runtime association between the input device and display.
- * @param inputPort The port of the input device.
+ * @param inputPort the port of the input device
* <p>
* Requires {@link android.Manifest.permission#ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
* </p>
* @hide
*/
+ @FlaggedApi(FLAG_DEVICE_ASSOCIATIONS)
+ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
@TestApi
- public void removeUniqueIdAssociation(@NonNull String inputPort) {
- mGlobal.removeUniqueIdAssociation(inputPort);
+ public void removeUniqueIdAssociationByPort(@NonNull String inputPort) {
+ mGlobal.removeUniqueIdAssociationByPort(inputPort);
+ }
+
+ /**
+ * Add a runtime association between the input device name and display, by descriptor. Input
+ * device descriptors are expected to be unique per physical device, though one physical
+ * device can have multiple virtual input devices that possess the same descriptor.
+ * E.g. a keyboard with built in trackpad will be 2 different input devices with the same
+ * descriptor.
+ * @param inputDeviceDescriptor the descriptor of the input device
+ * @param displayUniqueId the unique id of the associated display
+ * <p>
+ * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
+ * </p>
+ * @hide
+ */
+ @FlaggedApi(FLAG_DEVICE_ASSOCIATIONS)
+ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
+ @TestApi
+ public void addUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor,
+ @NonNull String displayUniqueId) {
+ mGlobal.addUniqueIdAssociationByDescriptor(inputDeviceDescriptor, displayUniqueId);
+ }
+
+ /**
+ * Removes a runtime association between the input device and display.
+ }
+
+ /**
+ * Removes a runtime association between the input device and display.
+ * @param inputDeviceDescriptor the descriptor of the input device
+ * <p>
+ * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
+ * </p>
+ * @hide
+ */
+ @FlaggedApi(FLAG_DEVICE_ASSOCIATIONS)
+ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
+ @TestApi
+ public void removeUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor) {
+ mGlobal.removeUniqueIdAssociationByDescriptor(inputDeviceDescriptor);
}
/**
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 7b29666..1d253d9 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -1467,22 +1467,46 @@
}
/**
- * @see InputManager#addUniqueIdAssociation(String, String)
+ * @see InputManager#addUniqueIdAssociationByPort(String, String)
*/
- public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
+ public void addUniqueIdAssociationByPort(@NonNull String inputPort,
+ @NonNull String displayUniqueId) {
try {
- mIm.addUniqueIdAssociation(inputPort, displayUniqueId);
+ mIm.addUniqueIdAssociationByPort(inputPort, displayUniqueId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * @see InputManager#removeUniqueIdAssociation(String)
+ * @see InputManager#removeUniqueIdAssociationByPort(String)
*/
- public void removeUniqueIdAssociation(@NonNull String inputPort) {
+ public void removeUniqueIdAssociationByPort(@NonNull String inputPort) {
try {
- mIm.removeUniqueIdAssociation(inputPort);
+ mIm.removeUniqueIdAssociationByPort(inputPort);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see InputManager#addUniqueIdAssociationByDescriptor(String, String)
+ */
+ public void addUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor,
+ @NonNull String displayUniqueId) {
+ try {
+ mIm.addUniqueIdAssociationByDescriptor(inputDeviceDescriptor, displayUniqueId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see InputManager#removeUniqueIdAssociationByDescriptor(String)
+ */
+ public void removeUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor) {
+ try {
+ mIm.removeUniqueIdAssociationByDescriptor(inputDeviceDescriptor);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 6246dd7..91cdf8d 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -124,6 +124,22 @@
"vcn_network_selection_ipsec_packet_loss_percent_threshold";
/**
+ * Key for detecting unusually large increases in IPsec packet sequence numbers.
+ *
+ * <p>If the sequence number increases by more than this value within a second, it may indicate
+ * an intentional leap on the server's downlink. To avoid false positives, the packet loss
+ * detector will suppress loss reporting.
+ *
+ * <p>By default, there's no maximum limit enforced, prioritizing detection of lossy networks.
+ * To reduce false positives, consider setting an appropriate maximum threshold.
+ *
+ * @hide
+ */
+ @NonNull
+ public static final String VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY =
+ "vcn_network_selection_max_seq_num_increase_per_second";
+
+ /**
* Key for the list of timeouts in minute to stop penalizing an underlying network candidate
*
* @hide
@@ -180,6 +196,7 @@
VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
+ VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY,
VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY,
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index e64823a..6fde398 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -34,4 +34,14 @@
namespace: "vcn"
description: "Re-evaluate IPsec packet loss on LinkProperties or NetworkCapabilities change"
bug: "323238888"
+}
+
+flag{
+ name: "handle_seq_num_leap"
+ namespace: "vcn"
+ description: "Do not report bad network when there is a suspected sequence number leap"
+ bug: "332598276"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
index d681a2c..d1531a1 100644
--- a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
+++ b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
@@ -162,12 +162,21 @@
result.putInt(IP_VERSION_KEY, params.getIpVersion());
result.putInt(ENCAP_TYPE_KEY, params.getEncapType());
- // TODO: b/185941731 Make sure IkeSessionParamsUtils is automatically updated when a new
- // IKE_OPTION is defined in IKE module and added in the IkeSessionParams
final List<Integer> enabledIkeOptions = new ArrayList<>();
- for (int option : IKE_OPTIONS) {
- if (isIkeOptionValid(option) && params.hasIkeOption(option)) {
- enabledIkeOptions.add(option);
+
+ try {
+ // TODO: b/328844044: Ideally this code should gate the behavior by checking the
+ // com.android.ipsec.flags.enabled_ike_options_api flag but that flag is not accessible
+ // right now. We should either update the code when the flag is accessible or remove the
+ // legacy behavior after VIC SDK finalization
+ enabledIkeOptions.addAll(params.getIkeOptions());
+ } catch (Exception e) {
+ // getIkeOptions throws. It means the API is not available
+ enabledIkeOptions.clear();
+ for (int option : IKE_OPTIONS) {
+ if (isIkeOptionValid(option) && params.hasIkeOption(option)) {
+ enabledIkeOptions.add(option);
+ }
}
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 3977bdf..2b6b358 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -17,7 +17,6 @@
package android.os;
import android.Manifest;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -1230,7 +1229,6 @@
/**
* Vanilla Ice Cream.
*/
- @FlaggedApi(Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM)
public static final int VANILLA_ICE_CREAM = CUR_DEVELOPMENT;
}
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 4ae0a57..3b59901 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -98,6 +98,8 @@
IBinder registerAttributionSource(in AttributionSourceState source);
+ int getNumRegisteredAttributionSources(int uid);
+
boolean isRegisteredAttributionSource(in AttributionSourceState source);
int checkPermission(String packageName, String permissionName, String persistentDeviceId,
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index fe3fa8c..2daf4ac 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1675,6 +1675,21 @@
}
/**
+ * Gets the number of currently registered attribution sources for a particular UID. This should
+ * only be used for testing purposes.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.UPDATE_APP_OPS_STATS)
+ public int getNumRegisteredAttributionSourcesForTest(int uid) {
+ try {
+ return mPermissionManager.getNumRegisteredAttributionSources(uid);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return -1;
+ }
+
+ /**
* Revoke the POST_NOTIFICATIONS permission, without killing the app. This method must ONLY BE
* USED in CTS or local tests.
*
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 23ece31..abb4917 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -125,6 +125,30 @@
}
flag {
+ name: "sensitive_content_metrics_bugfix"
+ namespace: "permissions"
+ description: "Enables metrics bugfixes for sensitive content/notification features"
+ bug: "312784351"
+ # Referenced in WM where WM starts before DeviceConfig
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "sensitive_content_recents_screenshot_bugfix"
+ namespace: "permissions"
+ description: "Enables recents screenshot bugfixes for sensitive content/notification features"
+ bug: "312784351"
+ # Referenced in WM where WM starts before DeviceConfig
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "device_aware_permissions_enabled"
is_fixed_read_only: true
namespace: "permissions"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index dd93972..6ad7422 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9020,6 +9020,16 @@
"accessibility_display_daltonizer";
/**
+ * Integer property that determines the saturation level of color correction. Default value
+ * is defined in Settings config.xml.
+ * [0-10] inclusive where 0 would look as if color space adustment is not applied at all.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL =
+ "accessibility_display_daltonizer_saturation_level";
+
+ /**
* Setting that specifies whether automatic click when the mouse pointer stops moving is
* enabled.
*
diff --git a/core/java/android/security/FileIntegrityManager.java b/core/java/android/security/FileIntegrityManager.java
index 025aac9..478435b 100644
--- a/core/java/android/security/FileIntegrityManager.java
+++ b/core/java/android/security/FileIntegrityManager.java
@@ -20,6 +20,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.os.IInstalld.IFsveritySetupAuthToken;
@@ -99,8 +101,11 @@
* @throws IOException If the operation failed.
*
* @see <a href="https://www.kernel.org/doc/html/next/filesystems/fsverity.html">Kernel doc</a>
+ * @hide
*/
@FlaggedApi(Flags.FLAG_FSVERITY_API)
+ @SuppressLint("StreamFiles")
+ @SystemApi
public void setupFsVerity(@NonNull File file) throws IOException {
if (!file.isAbsolute()) {
// fs-verity is to be enabled by installd, which enforces the validation to the
@@ -138,8 +143,11 @@
* @param file The file to measure the fs-verity digest.
* @return The fs-verity digest in byte[], null if none.
* @see <a href="https://www.kernel.org/doc/html/next/filesystems/fsverity.html">Kernel doc</a>
+ * @hide
*/
@FlaggedApi(Flags.FLAG_FSVERITY_API)
+ @SuppressLint("StreamFiles")
+ @SystemApi
public @Nullable byte[] getFsVerityDigest(@NonNull File file) throws IOException {
return VerityUtils.getFsverityDigest(file.getPath());
}
diff --git a/core/java/android/security/IFileIntegrityService.aidl b/core/java/android/security/IFileIntegrityService.aidl
index 1a6cf88..ddb662ad 100644
--- a/core/java/android/security/IFileIntegrityService.aidl
+++ b/core/java/android/security/IFileIntegrityService.aidl
@@ -28,6 +28,8 @@
boolean isAppSourceCertificateTrusted(in byte[] certificateBytes, in String packageName);
IInstalld.IFsveritySetupAuthToken createAuthToken(in ParcelFileDescriptor authFd);
+
+ @EnforcePermission("SETUP_FSVERITY")
int setupFsverity(IInstalld.IFsveritySetupAuthToken authToken, in String filePath,
in String packageName);
}
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 9696dbc..9c14946 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -20,6 +20,7 @@
import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.app.Notification;
+import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -146,8 +147,13 @@
/**
* Data type: boolean, when true it suggests that the content text of this notification is
- * sensitive. A notification listener can use this information to redact notifications on locked
- * devices.
+ * sensitive. The system uses this information to improve privacy around the notification
+ * content. In {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, sensitive notification content is
+ * redacted from updates to most {@link NotificationListenerService
+ * NotificationListenerServices}. Also if an app posts a sensitive notification while
+ * {@link android.media.projection.MediaProjection screen-sharing} is active, that app's windows
+ * are blocked from screen-sharing and a {@link android.widget.Toast Toast} is shown to inform
+ * the user about this.
*/
public static final String KEY_SENSITIVE_CONTENT = "key_sensitive_content";
diff --git a/core/java/android/tracing/perfetto/DataSource.java b/core/java/android/tracing/perfetto/DataSource.java
index c33fa7d..b9ab82c 100644
--- a/core/java/android/tracing/perfetto/DataSource.java
+++ b/core/java/android/tracing/perfetto/DataSource.java
@@ -18,10 +18,6 @@
import android.util.proto.ProtoInputStream;
-import com.android.internal.annotations.VisibleForTesting;
-
-import dalvik.annotation.optimization.CriticalNative;
-
/**
* Templated base class meant to be derived by embedders to create a custom data
* source.
@@ -73,7 +69,8 @@
* @param fun The tracing lambda that will be called with the tracing contexts of each active
* tracing instance.
*/
- public final void trace(TraceFunction<TlsStateType, IncrementalStateType> fun) {
+ public final void trace(
+ TraceFunction<DataSourceInstanceType, TlsStateType, IncrementalStateType> fun) {
boolean startedIterator = nativePerfettoDsTraceIterateBegin(mNativeObj);
if (!startedIterator) {
@@ -82,8 +79,10 @@
try {
do {
- TracingContext<TlsStateType, IncrementalStateType> ctx =
- new TracingContext<>(mNativeObj);
+ int instanceIndex = nativeGetPerfettoDsInstanceIndex(mNativeObj);
+
+ TracingContext<DataSourceInstanceType, TlsStateType, IncrementalStateType> ctx =
+ new TracingContext<>(this, instanceIndex);
fun.trace(ctx);
ctx.flush();
@@ -104,9 +103,7 @@
* Override this method to create a custom TlsState object for your DataSource. A new instance
* will be created per trace instance per thread.
*
- * NOTE: Should only be called from native side.
*/
- @VisibleForTesting
public TlsStateType createTlsState(CreateTlsStateArgs<DataSourceInstanceType> args) {
return null;
}
@@ -114,9 +111,8 @@
/**
* Override this method to create and use a custom IncrementalState object for your DataSource.
*
- * NOTE: Should only be called from native side.
*/
- protected IncrementalStateType createIncrementalState(
+ public IncrementalStateType createIncrementalState(
CreateIncrementalStateArgs<DataSourceInstanceType> args) {
return null;
}
@@ -179,10 +175,8 @@
private static native void nativeReleasePerfettoInstanceLocked(
long dataSourcePtr, int dsInstanceIdx);
- @CriticalNative
private static native boolean nativePerfettoDsTraceIterateBegin(long dataSourcePtr);
- @CriticalNative
private static native boolean nativePerfettoDsTraceIterateNext(long dataSourcePtr);
- @CriticalNative
private static native void nativePerfettoDsTraceIterateBreak(long dataSourcePtr);
+ private static native int nativeGetPerfettoDsInstanceIndex(long dataSourcePtr);
}
diff --git a/core/java/android/tracing/perfetto/TraceFunction.java b/core/java/android/tracing/perfetto/TraceFunction.java
index 13e663d..d8854f9 100644
--- a/core/java/android/tracing/perfetto/TraceFunction.java
+++ b/core/java/android/tracing/perfetto/TraceFunction.java
@@ -19,12 +19,14 @@
/**
* The interface for the trace function called from native on a trace call with a context.
*
+ * @param <DataSourceInstanceType> The type of DataSource this tracing context is for.
* @param <TlsStateType> The type of the custom TLS state, if any is used.
* @param <IncrementalStateType> The type of the custom incremental state, if any is used.
*
* @hide
*/
-public interface TraceFunction<TlsStateType, IncrementalStateType> {
+public interface TraceFunction<DataSourceInstanceType extends DataSourceInstance,
+ TlsStateType, IncrementalStateType> {
/**
* This function will be called synchronously (i.e., always before trace() returns) only if
@@ -34,5 +36,5 @@
*
* @param ctx the tracing context to trace for in the trace function.
*/
- void trace(TracingContext<TlsStateType, IncrementalStateType> ctx);
+ void trace(TracingContext<DataSourceInstanceType, TlsStateType, IncrementalStateType> ctx);
}
diff --git a/core/java/android/tracing/perfetto/TracingContext.java b/core/java/android/tracing/perfetto/TracingContext.java
index 060f964..6b7df54 100644
--- a/core/java/android/tracing/perfetto/TracingContext.java
+++ b/core/java/android/tracing/perfetto/TracingContext.java
@@ -24,18 +24,25 @@
/**
* Argument passed to the lambda function passed to Trace().
*
+ * @param <DataSourceInstanceType> The type of the datasource this tracing context is for.
* @param <TlsStateType> The type of the custom TLS state, if any is used.
* @param <IncrementalStateType> The type of the custom incremental state, if any is used.
*
* @hide
*/
-public class TracingContext<TlsStateType, IncrementalStateType> {
+public class TracingContext<DataSourceInstanceType extends DataSourceInstance, TlsStateType,
+ IncrementalStateType> {
- private final long mNativeDsPtr;
+ private final DataSource<DataSourceInstanceType, TlsStateType, IncrementalStateType>
+ mDataSource;
+ private final int mInstanceIndex;
private final List<ProtoOutputStream> mTracePackets = new ArrayList<>();
- TracingContext(long nativeDsPtr) {
- this.mNativeDsPtr = nativeDsPtr;
+ TracingContext(DataSource<DataSourceInstanceType, TlsStateType, IncrementalStateType>
+ dataSource,
+ int instanceIndex) {
+ this.mDataSource = dataSource;
+ this.mInstanceIndex = instanceIndex;
}
/**
@@ -61,18 +68,26 @@
* Stop timeout expires.
*/
public void flush() {
- nativeFlush(mNativeDsPtr, getAndClearAllPendingTracePackets());
+ nativeFlush(mDataSource.mNativeObj, getAndClearAllPendingTracePackets());
}
/**
* Can optionally be used to store custom per-sequence
* session data, which is not reset when incremental state is cleared
* (e.g. configuration options).
- *
+ *h
* @return The TlsState instance for the tracing thread and instance.
*/
public TlsStateType getCustomTlsState() {
- return (TlsStateType) nativeGetCustomTls(mNativeDsPtr);
+ TlsStateType tlsState = (TlsStateType) nativeGetCustomTls(mDataSource.mNativeObj);
+ if (tlsState == null) {
+ final CreateTlsStateArgs<DataSourceInstanceType> args =
+ new CreateTlsStateArgs<>(mDataSource, mInstanceIndex);
+ tlsState = mDataSource.createTlsState(args);
+ nativeSetCustomTls(mDataSource.mNativeObj, tlsState);
+ }
+
+ return tlsState;
}
/**
@@ -82,7 +97,16 @@
* @return The current IncrementalState object instance.
*/
public IncrementalStateType getIncrementalState() {
- return (IncrementalStateType) nativeGetIncrementalState(mNativeDsPtr);
+ IncrementalStateType incrementalState =
+ (IncrementalStateType) nativeGetIncrementalState(mDataSource.mNativeObj);
+ if (incrementalState == null) {
+ final CreateIncrementalStateArgs<DataSourceInstanceType> args =
+ new CreateIncrementalStateArgs<>(mDataSource, mInstanceIndex);
+ incrementalState = mDataSource.createIncrementalState(args);
+ nativeSetIncrementalState(mDataSource.mNativeObj, incrementalState);
+ }
+
+ return incrementalState;
}
private byte[][] getAndClearAllPendingTracePackets() {
@@ -97,6 +121,10 @@
}
private static native void nativeFlush(long dataSourcePtr, byte[][] packetData);
+
private static native Object nativeGetCustomTls(long nativeDsPtr);
+ private static native void nativeSetCustomTls(long nativeDsPtr, Object tlsState);
+
private static native Object nativeGetIncrementalState(long nativeDsPtr);
+ private static native void nativeSetIncrementalState(long nativeDsPtr, Object incrementalState);
}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 4d4cb6c..815fc58 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -87,12 +87,6 @@
"settings_need_connected_ble_device_for_broadcast";
/**
- * Enable new language and keyboard settings UI
- * @hide
- */
- public static final String SETTINGS_NEW_KEYBOARD_UI = "settings_new_keyboard_ui";
-
- /**
* Enable new modifier key settings UI
* @hide
*/
@@ -221,7 +215,6 @@
DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true");
- DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "true");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
@@ -249,7 +242,6 @@
PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
- PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE);
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 3e61539..f315f55 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -48,6 +48,7 @@
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.AccessibilityRequestPreparer;
+import android.view.accessibility.Flags;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import android.window.ScreenCapture;
@@ -1286,6 +1287,15 @@
}
/**
+ * Destroy {@link AccessibilityInteractionController} and clean up the pending actions.
+ */
+ public void destroy() {
+ if (Flags.preventLeakingViewrootimpl()) {
+ mHandler.removeCallbacksAndMessages(null);
+ }
+ }
+
+ /**
* This class encapsulates a prefetching strategy for the accessibility APIs for
* querying window content. It is responsible to prefetch a batch of
* AccessibilityNodeInfos in addition to the one for a requested node.
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 188ad8f..56edfe72 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -103,9 +103,9 @@
long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy);
private static native void nativeDestroy(long nativeObject);
- // 5MB is a wild guess for what the average surface should be. On most new phones, a full-screen
- // surface is about 9MB... but not all surfaces are screen size. This should be a nice balance.
- private static final long SURFACE_NATIVE_ALLOCATION_SIZE_BYTES = 5_000_000;
+ // 5KB is a balanced guess, since these are still pretty heavyweight objects, but if we make
+ // this too big, it can overwhelm the GC.
+ private static final long SURFACE_NATIVE_ALLOCATION_SIZE_BYTES = 5_000;
public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR =
new Parcelable.Creator<Surface>() {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index c95d6ff..a23df79 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -951,7 +951,7 @@
private boolean performSurfaceTransaction(ViewRootImpl viewRoot, Translator translator,
boolean creating, boolean sizeChanged, boolean hintChanged, boolean relativeZChanged,
- Transaction surfaceUpdateTransaction) {
+ boolean hdrHeadroomChanged, Transaction surfaceUpdateTransaction) {
boolean realSizeChanged = false;
mSurfaceLock.lock();
@@ -986,7 +986,7 @@
updateBackgroundVisibility(surfaceUpdateTransaction);
updateBackgroundColor(surfaceUpdateTransaction);
- if (mLimitedHdrEnabled) {
+ if (mLimitedHdrEnabled && hdrHeadroomChanged) {
surfaceUpdateTransaction.setDesiredHdrHeadroom(
mBlastSurfaceControl, mHdrHeadroom);
}
@@ -1203,7 +1203,7 @@
}
final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator,
- creating, sizeChanged, hintChanged, relativeZChanged,
+ creating, sizeChanged, hintChanged, relativeZChanged, hdrHeadroomChanged,
surfaceUpdateTransaction);
try {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cacf0d2..4c4a22c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2428,6 +2428,26 @@
*/
public static final int FRAME_RATE_CATEGORY_REASON_IDLE = 0x0700_0000;
+ /**
+ * This indicates that the frame rate category was chosen because it is currently boosting.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_BOOST = 0x0800_0000;
+
+ /**
+ * This indicates that the frame rate category was chosen because it is currently having
+ * touch boost.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_TOUCH = 0x0900_0000;
+
+ /**
+ * This indicates that the frame rate category was chosen because it is currently having
+ * touch boost.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_CONFLICTED = 0x0A00_0000;
+
private static final int FRAME_RATE_CATEGORY_REASON_MASK = 0xFFFF_0000;
/**
@@ -5742,7 +5762,7 @@
*/
private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
- private static final float MAX_FRAME_RATE = 140;
+ static final float MAX_FRAME_RATE = 140;
private static final int INFREQUENT_UPDATE_INTERVAL_MILLIS = 100;
private static final int INFREQUENT_UPDATE_COUNTS = 2;
@@ -13423,6 +13443,15 @@
}
/**
+ * @return True if the window has the {@link OnContentApplyWindowInsetsListener}, and this means
+ * the framework will apply window insets on the content of the window.
+ * @hide
+ */
+ protected boolean hasContentOnApplyWindowInsetsListener() {
+ return mAttachInfo != null && mAttachInfo.mContentOnApplyWindowInsetsListener != null;
+ }
+
+ /**
* Sets whether or not this view should account for system screen decorations
* such as the status bar and inset its content; that is, controlling whether
* the default implementation of {@link #fitSystemWindows(Rect)} will be
@@ -33897,36 +33926,41 @@
int height = mBottom - mTop;
if (viewRootImpl != null && (width != 0 && height != 0)) {
- if (mAttachInfo.mViewVelocityApi) {
- float velocity = mFrameContentVelocity;
- int mask = PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN;
- float frameRate = 0;
+ if (viewRootImpl.shouldCheckFrameRate(mPreferredFrameRate > 0f)) {
+ float velocityFrameRate = 0f;
+ if (mAttachInfo.mViewVelocityApi) {
+ float velocity = mFrameContentVelocity;
- if (velocity < 0f
- && (mPrivateFlags4 & mask) == mask
- && mParent instanceof View
- && ((View) mParent).mFrameContentVelocity <= 0
- ) {
- // This current calculation is very simple. If something on the screen moved,
- // then it votes for the highest velocity. If it doesn't move, then return 0.
- velocity = Float.POSITIVE_INFINITY;
- frameRate = MAX_FRAME_RATE;
- }
- if (velocity > 0f) {
- if (sToolkitFrameRateVelocityMappingReadOnlyFlagValue) {
- frameRate = convertVelocityToFrameRate(velocity);
+ if (velocity < 0f
+ && (mPrivateFlags4 & (PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)) == (
+ PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)
+ && mParent instanceof View
+ && ((View) mParent).mFrameContentVelocity <= 0
+ ) {
+ // This current calculation is very simple. If something on the screen
+ // moved, then it votes for the highest velocity.
+ velocityFrameRate = MAX_FRAME_RATE;
+ } else if (velocity > 0f) {
+ velocityFrameRate = convertVelocityToFrameRate(velocity);
}
- viewRootImpl.votePreferredFrameRate(frameRate, FRAME_RATE_COMPATIBILITY_GTE);
- return;
+ }
+ if (velocityFrameRate > 0f || mPreferredFrameRate > 0f) {
+ int compatibility = FRAME_RATE_COMPATIBILITY_GTE;
+ float frameRate = velocityFrameRate;
+ if (mPreferredFrameRate > velocityFrameRate) {
+ compatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+ frameRate = mPreferredFrameRate;
+ }
+ viewRootImpl.votePreferredFrameRate(frameRate, compatibility);
}
}
- if (!willNotDraw() && isDirty()) {
+ if (!willNotDraw() && isDirty() && viewRootImpl.shouldCheckFrameRateCategory()) {
if (sToolkitMetricsForFrameRateDecisionFlagValue) {
float sizePercentage = width * height / mAttachInfo.mDisplayPixelCount;
viewRootImpl.recordViewPercentage(sizePercentage);
}
- int frameRateCategory;
+ int frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
if (Float.isNaN(mPreferredFrameRate)) {
frameRateCategory = calculateFrameRateCategory();
} else if (mPreferredFrameRate < 0) {
@@ -33951,10 +33985,6 @@
| FRAME_RATE_CATEGORY_REASON_INVALID;
}
}
- } else {
- viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
- mFrameRateCompatibility);
- return;
}
int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cd0602cd..8d55777 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -27,6 +27,7 @@
import static android.view.DragEvent.ACTION_DRAG_LOCATION;
import static android.view.InputDevice.SOURCE_CLASS_NONE;
import static android.view.InsetsSource.ID_IME;
+import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
@@ -34,14 +35,18 @@
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_BOOST;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_CONFLICTED;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_IDLE;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_INVALID;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_LARGE;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_REQUESTED;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_SMALL;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_TOUCH;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_UNKNOWN;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_VELOCITY;
+import static android.view.View.MAX_FRAME_RATE;
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;
@@ -84,6 +89,7 @@
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
@@ -105,12 +111,12 @@
import static android.view.accessibility.Flags.forceInvertColor;
import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
import static android.view.flags.Flags.sensitiveContentAppProtection;
+import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly;
import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly;
import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly;
+import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly;
import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
-import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly;
-import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
@@ -234,6 +240,7 @@
import android.view.autofill.AutofillManager;
import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.ContentCaptureSession;
+import android.view.flags.Flags;
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller;
@@ -1041,10 +1048,10 @@
// 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;
+ private int mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT;
// 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;
+ private int mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT;
// The preferred frame rate of the view that is mainly used for
// touch boosting, view velocity handling, and TextureView.
private float mPreferredFrameRate = 0;
@@ -1148,7 +1155,9 @@
private static boolean sToolkitFrameRateTypingReadOnlyFlagValue;
private static final boolean sToolkitFrameRateViewEnablingReadOnlyFlagValue;
private static boolean sToolkitFrameRateVelocityMappingReadOnlyFlagValue =
- toolkitFrameRateVelocityMappingReadOnly();;
+ toolkitFrameRateVelocityMappingReadOnly();
+ private static boolean sToolkitEnableInvalidateCheckThreadFlagValue =
+ Flags.enableInvalidateCheckThread();
static {
sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
@@ -1433,6 +1442,7 @@
// Keep track of the actual window flags supplied by the client.
mClientWindowLayoutFlags = attrs.flags;
+ adjustLayoutInDisplayCutoutMode(attrs);
setAccessibilityFocus(null, null);
if (view instanceof RootViewSurfaceTaker) {
@@ -2035,6 +2045,9 @@
final int appearance = mWindowAttributes.insetsFlags.appearance;
final int behavior = mWindowAttributes.insetsFlags.behavior;
+ // Calling this before copying prevents redundant LAYOUT_CHANGED.
+ final int layoutInDisplayCutoutModeFromCaller = adjustLayoutInDisplayCutoutMode(attrs);
+
final int changes = mWindowAttributes.copyFrom(attrs);
if ((changes & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
// Recompute system ui visibility.
@@ -2051,6 +2064,9 @@
mWindowAttributes.packageName = mBasePackageName;
}
+ // Restore the layoutInDisplayCutoutMode of the caller;
+ attrs.layoutInDisplayCutoutMode = layoutInDisplayCutoutModeFromCaller;
+
// Restore preserved flags.
mWindowAttributes.systemUiVisibility = systemUiVisibility;
mWindowAttributes.subtreeSystemUiVisibility = subtreeSystemUiVisibility;
@@ -2093,6 +2109,19 @@
}
}
+ private int adjustLayoutInDisplayCutoutMode(WindowManager.LayoutParams attrs) {
+ final int originalMode = attrs.layoutInDisplayCutoutMode;
+ if ((attrs.privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
+ && attrs.isFullscreen()
+ && attrs.getFitInsetsTypes() == 0
+ && attrs.getFitInsetsSides() == 0) {
+ if (originalMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
+ attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ }
+ }
+ return originalMode;
+ }
+
void handleAppVisibility(boolean visible) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.instant(Trace.TRACE_TAG_VIEW, TextUtils.formatSimple(
@@ -2370,8 +2399,9 @@
@Override
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
- // TODO: Re-enable after camera is fixed or consider targetSdk checking this
- // checkThread();
+ if (sToolkitEnableInvalidateCheckThreadFlagValue) {
+ checkThread();
+ }
if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
mIsAnimating = true;
}
@@ -4190,18 +4220,25 @@
// For the variable refresh rate project.
// We set the preferred frame rate and frame rate category at the end of performTraversals
// when the values are applicable.
+ setCategoryFromCategoryCounts();
setPreferredFrameRate(mPreferredFrameRate);
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
+ if (!mIsFrameRateConflicted) {
+ mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
+ mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING,
+ FRAME_RATE_SETTING_REEVALUATE_TIME);
+ }
+ checkIdleness();
mFrameRateCategoryHighCount = mFrameRateCategoryHighCount > 0
? mFrameRateCategoryHighCount - 1 : mFrameRateCategoryHighCount;
mFrameRateCategoryNormalCount = mFrameRateCategoryNormalCount > 0
? mFrameRateCategoryNormalCount - 1 : mFrameRateCategoryNormalCount;
mFrameRateCategoryLowCount = mFrameRateCategoryLowCount > 0
? mFrameRateCategoryLowCount - 1 : mFrameRateCategoryLowCount;
- mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT;
mPreferredFrameRate = -1;
- mFrameRateCompatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
mIsFrameRateConflicted = false;
+ mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
}
private void createSyncIfNeeded() {
@@ -6072,6 +6109,11 @@
mAccessibilityInteractionConnectionManager.ensureNoConnection();
mAccessibilityInteractionConnectionManager.ensureNoDirectConnection();
removeSendWindowContentChangedCallback();
+ if (android.view.accessibility.Flags.preventLeakingViewrootimpl()
+ && mAccessibilityInteractionController != null) {
+ mAccessibilityInteractionController.destroy();
+ mAccessibilityInteractionController = null;
+ }
destroyHardwareRenderer();
@@ -6628,8 +6670,6 @@
*/
mIsFrameRateBoosting = false;
mIsTouchBoosting = false;
- setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
- mLastPreferredFrameRateCategory));
break;
case MSG_CHECK_INVALIDATION_IDLE:
if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
@@ -7675,7 +7715,6 @@
mWindowAttributes.type)) {
// set the frame rate to the maximum value.
mIsTouchBoosting = true;
- setPreferredFrameRateCategory(mPreferredFrameRateCategory);
}
/**
* We want to lower the refresh rate when MotionEvent.ACTION_UP,
@@ -12467,58 +12506,50 @@
EventLog.writeEvent(LOGTAG_VIEWROOT_DRAW_EVENT, mTag, msg);
}
+ /**
+ * Sets the mPreferredFrameRateCategory from the high, high_hint, normal, and low counts.
+ */
+ private void setCategoryFromCategoryCounts() {
+ if (mFrameRateCategoryHighCount > 0) {
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+ } else if (mFrameRateCategoryHighHintCount > 0) {
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH_HINT;
+ } else if (mFrameRateCategoryNormalCount > 0) {
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NORMAL;
+ } else if (mFrameRateCategoryLowCount > 0) {
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_LOW;
+ }
+ }
+
private void setPreferredFrameRateCategory(int preferredFrameRateCategory) {
- if (!shouldSetFrameRateCategory()
- || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
- && mPreferredFrameRate > 0
- && sToolkitFrameRateVelocityMappingReadOnlyFlagValue)) {
+ if (!shouldSetFrameRateCategory()) {
return;
}
- int categoryFromConflictedFrameRates = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- if (mIsFrameRateConflicted) {
- categoryFromConflictedFrameRates = mPreferredFrameRate > 60
- ? FRAME_RATE_CATEGORY_HIGH : FRAME_RATE_CATEGORY_NORMAL;
- }
- int frameRateCategory = mIsTouchBoosting
- ? FRAME_RATE_CATEGORY_HIGH_HINT
- : Math.max(preferredFrameRateCategory, categoryFromConflictedFrameRates);
+ int frameRateCategory;
+ int frameRateReason;
+ String view;
- // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT
- // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
- // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction
- // (e.g., Window Initialization).
- if (mIsFrameRateBoosting || mInsetsAnimationRunning
- || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
- && mPreferredFrameRate > 0)) {
+ if (mIsFrameRateBoosting || mInsetsAnimationRunning) {
frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
- if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE) {
- // We've received a velocity, so we'll let the velocity control the
- // frame rate unless we receive additional motion events.
- mIsTouchBoosting = false;
- mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_VELOCITY;
- mFrameRateCategoryView = null;
- } else {
- mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
- }
+ frameRateReason = FRAME_RATE_CATEGORY_REASON_BOOST;
+ view = null;
+ } else if (mIsTouchBoosting && preferredFrameRateCategory < FRAME_RATE_CATEGORY_HIGH_HINT) {
+ frameRateCategory = FRAME_RATE_CATEGORY_HIGH_HINT;
+ frameRateReason = FRAME_RATE_CATEGORY_REASON_TOUCH;
+ view = null;
+ } else {
+ frameRateCategory = preferredFrameRateCategory;
+ frameRateReason = mFrameRateCategoryChangeReason;
+ view = mFrameRateCategoryView;
}
try {
- if (mLastPreferredFrameRateCategory != frameRateCategory) {
+ if (frameRateCategory != FRAME_RATE_CATEGORY_DEFAULT
+ && mLastPreferredFrameRateCategory != frameRateCategory) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
- String reason = reasonToString(mFrameRateCategoryChangeReason);
- String sourceView = mFrameRateCategoryView == null ? "-"
- : mFrameRateCategoryView;
- if (preferredFrameRateCategory == FRAME_RATE_CATEGORY_HIGH_HINT) {
- reason = "touch boost";
- sourceView = "-";
- } else if (categoryFromConflictedFrameRates == frameRateCategory
- && frameRateCategory != preferredFrameRateCategory
- && mIsFrameRateConflicted
- ) {
- reason = "conflict";
- sourceView = "-";
- }
+ String reason = reasonToString(frameRateReason);
+ String sourceView = view == null ? "-" : view;
String category = categoryToString(frameRateCategory);
Trace.traceBegin(
Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRateCategory "
@@ -12562,24 +12593,21 @@
case FRAME_RATE_CATEGORY_REASON_VELOCITY -> str = "velocity";
case FRAME_RATE_CATEGORY_REASON_IDLE -> str = "idle";
case FRAME_RATE_CATEGORY_REASON_UNKNOWN -> str = "unknown";
+ case FRAME_RATE_CATEGORY_REASON_BOOST -> str = "boost";
+ case FRAME_RATE_CATEGORY_REASON_TOUCH -> str = "touch";
+ case FRAME_RATE_CATEGORY_REASON_CONFLICTED -> str = "conflicted";
default -> str = String.valueOf(reason);
}
return str;
}
private void setPreferredFrameRate(float preferredFrameRate) {
- if (!shouldSetFrameRate()) {
- return;
- }
- if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
- && preferredFrameRate > 0 && !sToolkitFrameRateVelocityMappingReadOnlyFlagValue) {
- mIsTouchBoosting = false;
+ if (!shouldSetFrameRate() || preferredFrameRate < 0) {
return;
}
try {
- if (mLastPreferredFrameRate != preferredFrameRate
- && preferredFrameRate >= 0) {
+ if (mLastPreferredFrameRate != preferredFrameRate) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(
Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRate "
@@ -12588,7 +12616,7 @@
}
if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
- mFrameRateCompatibility).applyAsyncUnsafe();
+ mFrameRateCompatibility).applyAsyncUnsafe();
}
mLastPreferredFrameRate = preferredFrameRate;
}
@@ -12599,12 +12627,6 @@
}
}
- 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() && shouldEnableDvrr();
@@ -12642,25 +12664,34 @@
case FRAME_RATE_CATEGORY_HIGH ->
mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
}
-
- int oldCategory = mPreferredFrameRateCategory;
- if (mFrameRateCategoryHighCount > 0) {
- mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
- } else if (mFrameRateCategoryHighHintCount > 0) {
- mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH_HINT;
- } else if (mFrameRateCategoryNormalCount > 0) {
- mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NORMAL;
- } else if (mFrameRateCategoryLowCount > 0) {
- mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_LOW;
+ if (frameRateCategory > mPreferredFrameRateCategory) {
+ mPreferredFrameRateCategory = frameRateCategory;
+ mFrameRateCategoryChangeReason = reason;
+ mFrameRateCategoryView = view == null ? "-" : view.getClass().getSimpleName();
}
mHasInvalidation = true;
- checkIdleness();
- if (mPreferredFrameRateCategory != oldCategory
- && mPreferredFrameRateCategory == frameRateCategory
- ) {
- mFrameRateCategoryChangeReason = reason;
- mFrameRateCategoryView = view == null ? "null" : view.getClass().getSimpleName();
- }
+ }
+
+ /**
+ * Returns whether a View should vote for frame rate category. When the category is HIGH
+ * already, there's no need to calculate the category on the View and vote.
+ */
+ public boolean shouldCheckFrameRateCategory() {
+ return mPreferredFrameRateCategory < FRAME_RATE_CATEGORY_HIGH;
+ }
+
+ /**
+ * Returns whether a View should vote for frame rate. When the maximum frame rate has already
+ * been voted for, there's no point in calculating and voting for the frame rate. When
+ * isDirect is false, then it will return false when the velocity-calculated frame rate
+ * can be avoided.
+ * @param isDirect true when the frame rate has been set directly on the View or false if
+ * the calculation is based only on velocity.
+ */
+ public boolean shouldCheckFrameRate(boolean isDirect) {
+ return mPreferredFrameRate < MAX_FRAME_RATE
+ || (!isDirect && !sToolkitFrameRateVelocityMappingReadOnlyFlagValue
+ && mPreferredFrameRateCategory < FRAME_RATE_CATEGORY_HIGH);
}
/**
@@ -12686,24 +12717,44 @@
if (frameRate <= 0) {
return;
}
+ if (frameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE) {
+ mIsTouchBoosting = false;
+ if (!sToolkitFrameRateVelocityMappingReadOnlyFlagValue) {
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+ mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
+ mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_VELOCITY;
+ mFrameRateCategoryView = null;
+ return;
+ }
+ }
+ float nextFrameRate;
+ int nextFrameRateCompatibility;
+ if (frameRate > mPreferredFrameRate) {
+ nextFrameRate = frameRate;
+ nextFrameRateCompatibility = frameRateCompatibility;
+ } else {
+ nextFrameRate = mPreferredFrameRate;
+ nextFrameRateCompatibility = mFrameRateCompatibility;
+ }
+
if (mPreferredFrameRate > 0 && mPreferredFrameRate % frameRate != 0
&& frameRate % mPreferredFrameRate != 0) {
mIsFrameRateConflicted = true;
- mFrameRateCompatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
- }
- if (frameRate > mPreferredFrameRate) {
- mFrameRateCompatibility = frameRateCompatibility;
+ if (nextFrameRate > 60 && mFrameRateCategoryHighCount != FRAME_RATE_CATEGORY_COUNT) {
+ mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
+ mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_CONFLICTED;
+ mFrameRateCategoryView = null;
+ } else if (mFrameRateCategoryHighCount == 0 && mFrameRateCategoryHighHintCount == 0
+ && mFrameRateCategoryNormalCount < FRAME_RATE_CATEGORY_COUNT) {
+ mFrameRateCategoryNormalCount = FRAME_RATE_CATEGORY_COUNT;
+ mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_CONFLICTED;
+ mFrameRateCategoryView = null;
+ }
}
- mPreferredFrameRate = Math.max(mPreferredFrameRate, frameRate);
+ mPreferredFrameRate = nextFrameRate;
+ mFrameRateCompatibility = nextFrameRateCompatibility;
mHasInvalidation = true;
-
- if (!mIsFrameRateConflicted) {
- mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
- mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING,
- FRAME_RATE_SETTING_REEVALUATE_TIME);
- }
- checkIdleness();
}
/**
@@ -12773,7 +12824,6 @@
private void boostFrameRate(int boostTimeOut) {
mIsFrameRateBoosting = true;
- setPreferredFrameRateCategory(mPreferredFrameRateCategory);
mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT);
mHandler.sendEmptyMessageDelayed(MSG_TOUCH_BOOST_TIMEOUT,
boostTimeOut);
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 12ce0f4..bdfc236 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -27,6 +27,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresNoPermission;
import android.annotation.SuppressLint;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -1190,6 +1191,8 @@
/**
* {@inheritDoc}
*/
+ @Override
+ @RequiresNoPermission
public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info,
int interactionId) {
synchronized (mInstanceLock) {
@@ -1231,6 +1234,8 @@
/**
* {@inheritDoc}
*/
+ @Override
+ @RequiresNoPermission
public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
int interactionId) {
synchronized (mInstanceLock) {
@@ -1260,6 +1265,7 @@
* {@inheritDoc}
*/
@Override
+ @RequiresNoPermission
public void setPrefetchAccessibilityNodeInfoResult(@NonNull List<AccessibilityNodeInfo> infos,
int interactionId) {
int interactionIdWaitingForPrefetchResultCopy = -1;
@@ -1324,6 +1330,8 @@
/**
* {@inheritDoc}
*/
+ @Override
+ @RequiresNoPermission
public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) {
synchronized (mInstanceLock) {
if (interactionId > mInteractionId) {
@@ -1372,6 +1380,7 @@
* @param interactionId The interaction id of the request.
*/
@Override
+ @RequiresNoPermission
public void sendTakeScreenshotOfWindowError(
@AccessibilityService.ScreenshotErrorCode int errorCode, int interactionId) {
synchronized (mInstanceLock) {
@@ -1729,6 +1738,7 @@
* @param interactionId The interaction id of the request.
*/
@Override
+ @RequiresNoPermission
public void sendAttachOverlayResult(
@AccessibilityService.AttachOverlayResult int result, int interactionId) {
if (!Flags.a11yOverlayCallbacks()) {
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
index fe59519..a9e5db5 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -34,6 +34,7 @@
* @param interactionId The interaction id to match the result with the request.
*/
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ @RequiresNoPermission
void setFindAccessibilityNodeInfoResult(in AccessibilityNodeInfo info, int interactionId);
/**
@@ -43,6 +44,7 @@
* @param interactionId The interaction id to match the result with the request.
*/
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ @RequiresNoPermission
void setFindAccessibilityNodeInfosResult(in List<AccessibilityNodeInfo> infos,
int interactionId);
@@ -52,6 +54,7 @@
* @param root The {@link AccessibilityNodeInfo} for which the prefetching is based off of.
* @param infos The result {@link AccessibilityNodeInfo}s.
*/
+ @RequiresNoPermission
void setPrefetchAccessibilityNodeInfoResult(
in List<AccessibilityNodeInfo> infos, int interactionId);
@@ -62,15 +65,18 @@
* @param interactionId The interaction id to match the result with the request.
*/
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ @RequiresNoPermission
void setPerformAccessibilityActionResult(boolean succeeded, int interactionId);
/**
* Sends an error code for a window screenshot request to the requesting client.
*/
+ @RequiresNoPermission
void sendTakeScreenshotOfWindowError(int errorCode, int interactionId);
/**
* Sends an result code for an attach overlay request to the requesting client.
*/
+ @RequiresNoPermission
void sendAttachOverlayResult(int result, int interactionId);
}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 614df7c..cd11314 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -42,112 +42,154 @@
*/
interface IAccessibilityManager {
+ @RequiresNoPermission
oneway void interrupt(int userId);
+ @RequiresNoPermission
oneway void sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId);
+ @RequiresNoPermission
long addClient(IAccessibilityManagerClient client, int userId);
+ @RequiresNoPermission
boolean removeClient(IAccessibilityManagerClient client, int userId);
+ @RequiresNoPermission
ParceledListSlice<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId);
+ @RequiresNoPermission
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId);
+ @RequiresNoPermission
int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken,
in IAccessibilityInteractionConnection connection,
String packageName, int userId);
+ @RequiresNoPermission
void removeAccessibilityInteractionConnection(IWindow windowToken);
+ @EnforcePermission("MODIFY_ACCESSIBILITY_DATA")
void setPictureInPictureActionReplacingConnection(
in IAccessibilityInteractionConnection connection);
+ @EnforcePermission("RETRIEVE_WINDOW_CONTENT")
void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient client,
in AccessibilityServiceInfo info, int userId, int flags);
+ @RequiresNoPermission
void unregisterUiTestAutomationService(IAccessibilityServiceClient client);
// Used by UiAutomation
+ @EnforcePermission("RETRIEVE_WINDOW_CONTENT")
IBinder getWindowToken(int windowId, int userId);
+ @EnforcePermission("STATUS_BAR_SERVICE")
void notifyAccessibilityButtonClicked(int displayId, String targetName);
+
+ @EnforcePermission("STATUS_BAR_SERVICE")
void notifyAccessibilityButtonVisibilityChanged(boolean available);
- // Requires Manifest.permission.MANAGE_ACCESSIBILITY
+ @EnforcePermission("MANAGE_ACCESSIBILITY")
void performAccessibilityShortcut(String targetName);
- // Requires Manifest.permission.MANAGE_ACCESSIBILITY
+ @EnforcePermission("MANAGE_ACCESSIBILITY")
List<String> getAccessibilityShortcutTargets(int shortcutType);
// System process only
+ @RequiresNoPermission
boolean sendFingerprintGesture(int gestureKeyCode);
// System process only
+ @RequiresNoPermission
int getAccessibilityWindowId(IBinder windowToken);
+ @RequiresNoPermission
long getRecommendedTimeoutMillis();
+ @EnforcePermission("MANAGE_ACCESSIBILITY")
oneway void registerSystemAction(in RemoteAction action, int actionId);
+
+ @EnforcePermission("MANAGE_ACCESSIBILITY")
oneway void unregisterSystemAction(int actionId);
+
+ @EnforcePermission("STATUS_BAR_SERVICE")
oneway void setMagnificationConnection(in IMagnificationConnection connection);
+ @RequiresNoPermission
void associateEmbeddedHierarchy(IBinder host, IBinder embedded);
+ @RequiresNoPermission
void disassociateEmbeddedHierarchy(IBinder token);
+ @RequiresNoPermission
int getFocusStrokeWidth();
+ @RequiresNoPermission
int getFocusColor();
+ @RequiresNoPermission
boolean isAudioDescriptionByDefaultEnabled();
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)")
+ @EnforcePermission("SET_SYSTEM_AUDIO_CAPTION")
void setSystemAudioCaptioningEnabled(boolean isEnabled, int userId);
+ @RequiresNoPermission
boolean isSystemAudioCaptioningUiEnabled(int userId);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)")
+ @EnforcePermission("SET_SYSTEM_AUDIO_CAPTION")
void setSystemAudioCaptioningUiEnabled(boolean isEnabled, int userId);
+ @RequiresNoPermission
oneway void setAccessibilityWindowAttributes(int displayId, int windowId, int userId, in AccessibilityWindowAttributes attributes);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+ // Requires CREATE_VIRTUAL_DEVICE permission. Also requires either a11y permission or role.
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean registerProxyForDisplay(IAccessibilityServiceClient proxy, int displayId);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+ // Requires CREATE_VIRTUAL_DEVICE permission. Also requires either a11y permission or role.
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean unregisterProxyForDisplay(int displayId);
// Used by UiAutomation for tests on the InputFilter
+ @EnforcePermission("INJECT_EVENTS")
void injectInputEventToInputFilter(in InputEvent event);
+ @RequiresNoPermission
boolean startFlashNotificationSequence(String opPkg, int reason, IBinder token);
+
+ @RequiresNoPermission
boolean stopFlashNotificationSequence(String opPkg);
+
+ @RequiresNoPermission
boolean startFlashNotificationEvent(String opPkg, int reason, String reasonPkg);
+ @RequiresNoPermission
boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId);
+
+ @RequiresNoPermission
boolean sendRestrictedDialogIntent(String packageName, int uid, int userId);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+ @EnforcePermission("MANAGE_ACCESSIBILITY")
boolean isAccessibilityServiceWarningRequired(in AccessibilityServiceInfo info);
parcelable WindowTransformationSpec {
float[] transformationMatrix;
MagnificationSpec magnificationSpec;
}
+ @RequiresNoPermission
WindowTransformationSpec getWindowTransformationSpec(int windowId);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
+ @EnforcePermission("INTERNAL_SYSTEM_WINDOW")
void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl surfaceControl);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.STATUS_BAR_SERVICE,android.Manifest.permission.MANAGE_ACCESSIBILITY})")
+ @EnforcePermission(allOf={"STATUS_BAR_SERVICE","MANAGE_ACCESSIBILITY"})
oneway void notifyQuickSettingsTilesChanged(int userId, in List<ComponentName> tileComponentNames);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+ @EnforcePermission("MANAGE_ACCESSIBILITY")
oneway void enableShortcutsForTargets(boolean enable, int shortcutTypes, in List<String> shortcutTargets, int userId);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+ @EnforcePermission("MANAGE_ACCESSIBILITY")
Bundle getA11yFeatureToTileMap(int userId);
}
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 334965a..c9d99d1 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -142,6 +142,16 @@
}
flag {
+ name: "prevent_leaking_viewrootimpl"
+ namespace: "accessibility"
+ description: "Clear pending messages and callbacks of the handler in AccessibilityInteractionController when the ViewRootImpl is detached from Window to prevent leaking ViewRootImpl"
+ bug: "320701910"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "support_system_pinch_zoom_opt_out_apis"
namespace: "accessibility"
description: "Feature flag for declaring system pinch zoom opt-out apis"
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index c482f8b..486c2ab 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -50,3 +50,11 @@
description: "Enable default arrow icon when hovering on buttons or clickable widgets."
bug: "299269803"
}
+
+flag {
+ name: "enable_invalidate_check_thread"
+ namespace: "toolkit"
+ description: "Enable checkThread call in ViewRootImpl#onDescendentInvalidated"
+ bug: "333752000"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 8f1b72e..1034479 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -40,6 +40,7 @@
import android.util.AndroidRuntimeException;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Slog;
import java.io.File;
import java.lang.reflect.Method;
@@ -609,7 +610,7 @@
startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo);
} catch (Throwable t) {
// Log and discard errors at this stage as we must not crash the system server.
- Log.e(LOGTAG, "error preparing webview native library", t);
+ Slog.wtf(LOGTAG, "error preparing webview native library", t);
}
WebViewZygote.onWebViewProviderChanged(packageInfo);
diff --git a/core/java/android/webkit/WebViewLibraryLoader.java b/core/java/android/webkit/WebViewLibraryLoader.java
index a68a577..1a8745c 100644
--- a/core/java/android/webkit/WebViewLibraryLoader.java
+++ b/core/java/android/webkit/WebViewLibraryLoader.java
@@ -25,6 +25,7 @@
import android.os.Build;
import android.os.Process;
import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
@@ -137,7 +138,7 @@
if (!success) throw new Exception("Failed to start the relro file creator process");
} catch (Throwable t) {
// Log and discard errors as we must not crash the system server.
- Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
+ Slog.wtf(LOGTAG, "error starting relro file creator for abi " + abi, t);
crashHandler.run();
}
}
diff --git a/core/java/android/window/IRemoteTransition.aidl b/core/java/android/window/IRemoteTransition.aidl
index ec8b66d..33421ba 100644
--- a/core/java/android/window/IRemoteTransition.aidl
+++ b/core/java/android/window/IRemoteTransition.aidl
@@ -19,6 +19,7 @@
import android.view.SurfaceControl;
import android.window.IRemoteTransitionFinishedCallback;
import android.window.TransitionInfo;
+import android.window.WindowAnimationState;
/**
* Interface allowing remote processes to play transition animations.
@@ -61,6 +62,19 @@
in IRemoteTransitionFinishedCallback finishCallback);
/**
+ * Takes over the animation of the windows from an existing transition. Once complete, the
+ * implementation should call `finishCallback`.
+ *
+ * @param transition An identifier for the transition to be taken over.
+ * @param states The animation states of the windows involved in the transition. These must be
+ * sorted in the same way as the Changes inside `info`, and each state may be
+ * null.
+ */
+ void takeOverAnimation(in IBinder transition, in TransitionInfo info,
+ in SurfaceControl.Transaction t, in IRemoteTransitionFinishedCallback finishCallback,
+ in WindowAnimationState[] states);
+
+ /**
* Called when a different handler has consumed the transition
*
* @param transition An identifier for the transition that was consumed.
diff --git a/core/java/android/window/RemoteTransitionStub.java b/core/java/android/window/RemoteTransitionStub.java
index c9932ab..4a97b1e 100644
--- a/core/java/android/window/RemoteTransitionStub.java
+++ b/core/java/android/window/RemoteTransitionStub.java
@@ -31,6 +31,16 @@
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {}
+
+ @Override
+ public void takeOverAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction startTransaction,
+ IRemoteTransitionFinishedCallback finishCallback,
+ WindowAnimationState[] states) throws RemoteException {
+ throw new RemoteException("Takeovers are not supported by this IRemoteTransition");
+ }
+
+
@Override
public void onTransitionConsumed(IBinder transition, boolean aborted)
throws RemoteException {}
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index 93297e6..89327fe 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -18,10 +18,13 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WindowingMode;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.TestApi;
+import android.content.pm.ActivityInfo.ScreenOrientation;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.Parcel;
@@ -101,11 +104,20 @@
*/
private final boolean mAllowTransitionWhenEmpty;
+ /**
+ * The override orientation for the TaskFragment. This is effective only for a system organizer.
+ * The value is ignored otherwise. Default to {@code SCREEN_ORIENTATION_UNSPECIFIED}.
+ *
+ * @see TaskFragmentOrganizer#registerOrganizer(boolean)
+ */
+ private final @ScreenOrientation int mOverrideOrientation;
+
private TaskFragmentCreationParams(
@NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect initialRelativeBounds,
@WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
- @Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty) {
+ @Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty,
+ @ScreenOrientation int overrideOrientation) {
if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
+ " pairedActivityToken should not be set at the same time.");
@@ -118,6 +130,7 @@
mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
mPairedActivityToken = pairedActivityToken;
mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
+ mOverrideOrientation = overrideOrientation;
}
@NonNull
@@ -168,6 +181,11 @@
return mAllowTransitionWhenEmpty;
}
+ /** @hide */
+ public @ScreenOrientation int getOverrideOrientation() {
+ return mOverrideOrientation;
+ }
+
private TaskFragmentCreationParams(Parcel in) {
mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
mFragmentToken = in.readStrongBinder();
@@ -177,6 +195,7 @@
mPairedPrimaryFragmentToken = in.readStrongBinder();
mPairedActivityToken = in.readStrongBinder();
mAllowTransitionWhenEmpty = in.readBoolean();
+ mOverrideOrientation = in.readInt();
}
/** @hide */
@@ -190,6 +209,7 @@
dest.writeStrongBinder(mPairedPrimaryFragmentToken);
dest.writeStrongBinder(mPairedActivityToken);
dest.writeBoolean(mAllowTransitionWhenEmpty);
+ dest.writeInt(mOverrideOrientation);
}
@NonNull
@@ -217,6 +237,7 @@
+ " pairedFragmentToken=" + mPairedPrimaryFragmentToken
+ " pairedActivityToken=" + mPairedActivityToken
+ " allowTransitionWhenEmpty=" + mAllowTransitionWhenEmpty
+ + " overrideOrientation=" + mOverrideOrientation
+ "}";
}
@@ -252,6 +273,8 @@
private boolean mAllowTransitionWhenEmpty;
+ private @ScreenOrientation int mOverrideOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
public Builder(@NonNull TaskFragmentOrganizerToken organizer,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
mOrganizer = organizer;
@@ -330,12 +353,28 @@
return this;
}
+ /**
+ * Sets the override orientation for the TaskFragment. This is effective only for a system
+ * organizer. The value is ignored otherwise. Default to
+ * {@code SCREEN_ORIENTATION_UNSPECIFIED}.
+ *
+ * @see TaskFragmentOrganizer#registerOrganizer(boolean)
+ *
+ * @hide
+ */
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ @NonNull
+ public Builder setOverrideOrientation(@ScreenOrientation int overrideOrientation) {
+ mOverrideOrientation = overrideOrientation;
+ return this;
+ }
+
/** Constructs the options to create TaskFragment with. */
@NonNull
public TaskFragmentCreationParams build() {
return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
mInitialRelativeBounds, mWindowingMode, mPairedPrimaryFragmentToken,
- mPairedActivityToken, mAllowTransitionWhenEmpty);
+ mPairedActivityToken, mAllowTransitionWhenEmpty, mOverrideOrientation);
}
}
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 994e732..bcae571 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -523,6 +523,9 @@
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
}
+ if ((flags & FLAG_CONFIG_AT_END) != 0) {
+ sb.append(sb.length() == 0 ? "" : "|").append("CONFIG_AT_END");
+ }
if ((flags & FLAG_MOVED_TO_TOP) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("MOVE_TO_TOP");
}
diff --git a/core/java/android/window/WindowAnimationState.aidl b/core/java/android/window/WindowAnimationState.aidl
new file mode 100644
index 0000000..e662860
--- /dev/null
+++ b/core/java/android/window/WindowAnimationState.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.graphics.PointF;
+import android.graphics.RectF;
+
+/**
+ * Properties of a window animation at a given point in time.
+ *
+ * {@hide}
+ */
+parcelable WindowAnimationState {
+ long timestamp;
+ RectF bounds;
+ float scale;
+ float topLeftRadius;
+ float topRightRadius;
+ float bottomRightRadius;
+ float bottomLeftRadius;
+ PointF velocityPxPerMs;
+}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 4148e00..5e88d97c 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -1298,6 +1298,12 @@
if ((mChangeMask & CHANGE_FOCUSABLE) != 0) {
sb.append("focusable:" + mFocusable + ",");
}
+ if ((mChangeMask & CHANGE_FORCE_TRANSLUCENT) != 0) {
+ sb.append("forceTranslucent:" + mForceTranslucent + ",");
+ }
+ if ((mChangeMask & CHANGE_HIDDEN) != 0) {
+ sb.append("hidden:" + mHidden + ",");
+ }
if ((mChangeMask & CHANGE_DRAG_RESIZING) != 0) {
sb.append("dragResizing:" + mDragResizing + ",");
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index bcbac93..47a4052 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -36,6 +36,8 @@
import androidx.annotation.VisibleForTesting;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -75,14 +77,17 @@
@Nullable
private ImeBackAnimationController mImeBackAnimationController;
+ @GuardedBy("mLock")
/** Convenience hashmap to quickly decide if a callback has been added. */
private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
/** Holds all callbacks by priorities. */
@VisibleForTesting
+ @GuardedBy("mLock")
public final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
mOnBackInvokedCallbacks = new TreeMap<>();
private Checker mChecker;
+ private final Object mLock = new Object();
public WindowOnBackInvokedDispatcher(@NonNull Context context) {
mChecker = new Checker(context);
@@ -94,20 +99,24 @@
*/
public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window,
@Nullable ImeBackAnimationController imeBackAnimationController) {
- mWindowSession = windowSession;
- mWindow = window;
- mImeBackAnimationController = imeBackAnimationController;
- if (!mAllCallbacks.isEmpty()) {
- setTopOnBackInvokedCallback(getTopCallback());
+ synchronized (mLock) {
+ mWindowSession = windowSession;
+ mWindow = window;
+ mImeBackAnimationController = imeBackAnimationController;
+ if (!mAllCallbacks.isEmpty()) {
+ setTopOnBackInvokedCallback(getTopCallback());
+ }
}
}
/** Detaches the dispatcher instance from its window. */
public void detachFromWindow() {
- clear();
- mWindow = null;
- mWindowSession = null;
- mImeBackAnimationController = null;
+ synchronized (mLock) {
+ clear();
+ mWindow = null;
+ mWindowSession = null;
+ mImeBackAnimationController = null;
+ }
}
// TODO: Take an Executor for the callback to run on.
@@ -125,65 +134,71 @@
*/
public void registerOnBackInvokedCallbackUnchecked(
@NonNull OnBackInvokedCallback callback, @Priority int priority) {
- if (mImeDispatcher != null) {
- mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
- return;
- }
- if (!mOnBackInvokedCallbacks.containsKey(priority)) {
- mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
- }
- if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
- callback = mImeBackAnimationController;
- }
- ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
-
- // If callback has already been added, remove it and re-add it.
- if (mAllCallbacks.containsKey(callback)) {
- if (DEBUG) {
- Log.i(TAG, "Callback already added. Removing and re-adding it.");
+ synchronized (mLock) {
+ if (mImeDispatcher != null) {
+ mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
+ return;
}
- Integer prevPriority = mAllCallbacks.get(callback);
- mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
- }
+ if (!mOnBackInvokedCallbacks.containsKey(priority)) {
+ mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
+ }
+ if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
+ callback = mImeBackAnimationController;
+ }
+ ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
- OnBackInvokedCallback previousTopCallback = getTopCallback();
- callbacks.add(callback);
- mAllCallbacks.put(callback, priority);
- if (previousTopCallback == null
- || (previousTopCallback != callback
- && mAllCallbacks.get(previousTopCallback) <= priority)) {
- setTopOnBackInvokedCallback(callback);
+ // If callback has already been added, remove it and re-add it.
+ if (mAllCallbacks.containsKey(callback)) {
+ if (DEBUG) {
+ Log.i(TAG, "Callback already added. Removing and re-adding it.");
+ }
+ Integer prevPriority = mAllCallbacks.get(callback);
+ mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
+ }
+
+ OnBackInvokedCallback previousTopCallback = getTopCallback();
+ callbacks.add(callback);
+ mAllCallbacks.put(callback, priority);
+ if (previousTopCallback == null
+ || (previousTopCallback != callback
+ && mAllCallbacks.get(previousTopCallback) <= priority)) {
+ setTopOnBackInvokedCallback(callback);
+ }
}
}
@Override
public void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) {
- if (mImeDispatcher != null) {
- mImeDispatcher.unregisterOnBackInvokedCallback(callback);
- return;
- }
- if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
- callback = mImeBackAnimationController;
- }
- if (!mAllCallbacks.containsKey(callback)) {
- if (DEBUG) {
- Log.i(TAG, "Callback not found. returning...");
+ synchronized (mLock) {
+ if (mImeDispatcher != null) {
+ mImeDispatcher.unregisterOnBackInvokedCallback(callback);
+ return;
}
- return;
- }
- OnBackInvokedCallback previousTopCallback = getTopCallback();
- Integer priority = mAllCallbacks.get(callback);
- ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
- callbacks.remove(callback);
- if (callbacks.isEmpty()) {
- mOnBackInvokedCallbacks.remove(priority);
- }
- mAllCallbacks.remove(callback);
- // Re-populate the top callback to WM if the removed callback was previously the top one.
- if (previousTopCallback == callback) {
- // We should call onBackCancelled() when an active callback is removed from dispatcher.
- sendCancelledIfInProgress(callback);
- setTopOnBackInvokedCallback(getTopCallback());
+ if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
+ callback = mImeBackAnimationController;
+ }
+ if (!mAllCallbacks.containsKey(callback)) {
+ if (DEBUG) {
+ Log.i(TAG, "Callback not found. returning...");
+ }
+ return;
+ }
+ OnBackInvokedCallback previousTopCallback = getTopCallback();
+ Integer priority = mAllCallbacks.get(callback);
+ ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+ callbacks.remove(callback);
+ if (callbacks.isEmpty()) {
+ mOnBackInvokedCallbacks.remove(priority);
+ }
+ mAllCallbacks.remove(callback);
+ // Re-populate the top callback to WM if the removed callback was previously the top
+ // one.
+ if (previousTopCallback == callback) {
+ // We should call onBackCancelled() when an active callback is removed from
+ // dispatcher.
+ sendCancelledIfInProgress(callback);
+ setTopOnBackInvokedCallback(getTopCallback());
+ }
}
}
@@ -191,15 +206,21 @@
* Indicates if the dispatcher is actively dispatching to a callback.
*/
public boolean isDispatching() {
- return mIsDispatching;
+ synchronized (mLock) {
+ return mIsDispatching;
+ }
}
private void onStartDispatching() {
- mIsDispatching = true;
+ synchronized (mLock) {
+ mIsDispatching = true;
+ }
}
private void onStopDispatching() {
- mIsDispatching = false;
+ synchronized (mLock) {
+ mIsDispatching = false;
+ }
}
private void sendCancelledIfInProgress(@NonNull OnBackInvokedCallback callback) {
@@ -223,27 +244,29 @@
/** Clears all registered callbacks on the instance. */
public void clear() {
- if (mImeDispatcher != null) {
- mImeDispatcher.clear();
- mImeDispatcher = null;
- }
- if (!mAllCallbacks.isEmpty()) {
- OnBackInvokedCallback topCallback = getTopCallback();
- if (topCallback != null) {
- sendCancelledIfInProgress(topCallback);
- } else {
- // Should not be possible
- Log.e(TAG, "There is no topCallback, even if mAllCallbacks is not empty");
+ synchronized (mLock) {
+ if (mImeDispatcher != null) {
+ mImeDispatcher.clear();
+ mImeDispatcher = null;
}
- // Clear binder references in WM.
- setTopOnBackInvokedCallback(null);
- }
+ if (!mAllCallbacks.isEmpty()) {
+ OnBackInvokedCallback topCallback = getTopCallback();
+ if (topCallback != null) {
+ sendCancelledIfInProgress(topCallback);
+ } else {
+ // Should not be possible
+ Log.e(TAG, "There is no topCallback, even if mAllCallbacks is not empty");
+ }
+ // Clear binder references in WM.
+ setTopOnBackInvokedCallback(null);
+ }
- // We should also stop running animations since all callbacks have been removed.
- // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
- Handler.getMain().post(mProgressAnimator::reset);
- mAllCallbacks.clear();
- mOnBackInvokedCallbacks.clear();
+ // We should also stop running animations since all callbacks have been removed.
+ // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
+ Handler.getMain().post(mProgressAnimator::reset);
+ mAllCallbacks.clear();
+ mOnBackInvokedCallbacks.clear();
+ }
}
private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {
@@ -275,13 +298,15 @@
}
public OnBackInvokedCallback getTopCallback() {
- if (mAllCallbacks.isEmpty()) {
- return null;
- }
- for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
- ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
- if (!callbacks.isEmpty()) {
- return callbacks.get(callbacks.size() - 1);
+ synchronized (mLock) {
+ if (mAllCallbacks.isEmpty()) {
+ return null;
+ }
+ for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
+ ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+ if (!callbacks.isEmpty()) {
+ return callbacks.get(callbacks.size() - 1);
+ }
}
}
return null;
@@ -315,16 +340,18 @@
public void dump(String prefix, PrintWriter writer) {
String innerPrefix = prefix + " ";
writer.println(prefix + "WindowOnBackDispatcher:");
- if (mAllCallbacks.isEmpty()) {
- writer.println(prefix + "<None>");
- return;
- }
+ synchronized (mLock) {
+ if (mAllCallbacks.isEmpty()) {
+ writer.println(prefix + "<None>");
+ return;
+ }
- writer.println(innerPrefix + "Top Callback: " + getTopCallback());
- writer.println(innerPrefix + "Callbacks: ");
- mAllCallbacks.forEach((callback, priority) -> {
- writer.println(innerPrefix + " Callback: " + callback + " Priority=" + priority);
- });
+ writer.println(innerPrefix + "Top Callback: " + getTopCallback());
+ writer.println(innerPrefix + "Callbacks: ");
+ mAllCallbacks.forEach((callback, priority) -> {
+ writer.println(innerPrefix + " Callback: " + callback + " Priority=" + priority);
+ });
+ }
}
static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index a868d48..1ffb4ff 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -144,17 +144,26 @@
if (context == null) {
return;
}
- final ClientTransactionListenerController controller =
- ClientTransactionListenerController.getInstance();
- controller.onContextConfigurationPreChanged(context);
- try {
+ if (shouldReportConfigChange) {
+ // Only report to ClientTransactionListenerController when shouldReportConfigChange,
+ // which is on the MainThread.
+ final ClientTransactionListenerController controller =
+ getClientTransactionListenerController();
+ controller.onContextConfigurationPreChanged(context);
+ try {
+ onConfigurationChangedInner(context, newConfig, newDisplayId,
+ shouldReportConfigChange);
+ } finally {
+ controller.onContextConfigurationPostChanged(context);
+ }
+ } else {
onConfigurationChangedInner(context, newConfig, newDisplayId, shouldReportConfigChange);
- } finally {
- controller.onContextConfigurationPostChanged(context);
}
}
- private void onConfigurationChangedInner(@NonNull Context context,
+ /** Handles onConfiguration changed. */
+ @VisibleForTesting
+ public void onConfigurationChangedInner(@NonNull Context context,
@NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange) {
CompatibilityInfo.applyOverrideScaleIfNeeded(newConfig);
final boolean displayChanged;
@@ -233,4 +242,11 @@
mContextRef.clear();
}
}
+
+ /** Gets {@link ClientTransactionListenerController}. */
+ @VisibleForTesting
+ @NonNull
+ public ClientTransactionListenerController getClientTransactionListenerController() {
+ return ClientTransactionListenerController.getInstance();
+ }
}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 9524a6e..65e5f1a 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -49,3 +49,10 @@
description: "Shows running apps in Desktop Mode Taskbar"
bug: "332504528"
}
+
+flag {
+ name: "enable_desktop_windowing_wallpaper_activity"
+ namespace: "lse_desktop_experience"
+ description: "Enables desktop wallpaper activity to show wallpaper in the desktop mode"
+ bug: "309014605"
+}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 4b3d8e8..e49089d 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -88,4 +88,14 @@
description: "Whether to enable WM Extensions for all devices"
bug: "306666082"
is_fixed_read_only: true
+}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "always_defer_transition_when_apply_wct"
+ description: "Report error when defer transition fails when it should not"
+ bug: "335562144"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 1841353..52487fb 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2486,9 +2486,6 @@
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
params.setFitInsetsSides(0);
params.setFitInsetsTypes(0);
- if (mEdgeToEdgeEnforced) {
- params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- }
}
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index fca4e91..b6558cb 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -440,6 +440,7 @@
}
private int internStacktraceString(TracingContext<
+ ProtoLogDataSource.Instance,
ProtoLogDataSource.TlsState,
ProtoLogDataSource.IncrementalState> ctx,
String stacktrace) {
@@ -449,7 +450,8 @@
}
private int internStringArg(
- TracingContext<ProtoLogDataSource.TlsState, ProtoLogDataSource.IncrementalState> ctx,
+ TracingContext<ProtoLogDataSource.Instance, ProtoLogDataSource.TlsState,
+ ProtoLogDataSource.IncrementalState> ctx,
String string
) {
final ProtoLogDataSource.IncrementalState incrementalState = ctx.getIncrementalState();
@@ -458,7 +460,8 @@
}
private int internString(
- TracingContext<ProtoLogDataSource.TlsState, ProtoLogDataSource.IncrementalState> ctx,
+ TracingContext<ProtoLogDataSource.Instance, ProtoLogDataSource.TlsState,
+ ProtoLogDataSource.IncrementalState> ctx,
Map<String, Integer> internMap,
long fieldId,
String string
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index bdb33c4..cb1abf1 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -16,10 +16,12 @@
package com.android.internal.view.menu;
+import android.app.AppGlobals;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.text.TextFlags;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -59,6 +61,8 @@
private int mMenuType;
+ private boolean mUseNewContextMenu;
+
private LayoutInflater mInflater;
private boolean mForceShowIcon;
@@ -85,6 +89,10 @@
a.recycle();
b.recycle();
+
+ mUseNewContextMenu = AppGlobals.getIntCoreSetting(
+ TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
+ TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0;
}
public ListMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -281,7 +289,9 @@
private void insertIconView() {
LayoutInflater inflater = getInflater();
- mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon,
+ mIconView = (ImageView) inflater.inflate(
+ mUseNewContextMenu ? com.android.internal.R.layout.list_menu_item_fixed_size_icon :
+ com.android.internal.R.layout.list_menu_item_icon,
this, false);
addContentView(mIconView, 0);
}
diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
index 36828f2..1979e4f 100644
--- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
@@ -16,26 +16,24 @@
package com.android.internal.view.menu;
-import android.app.AppGlobals;
import android.content.Context;
import android.content.res.Resources;
import android.os.Parcelable;
-import android.text.TextFlags;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.View.OnKeyListener;
-import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.widget.AdapterView.OnItemClickListener;
+import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ListView;
import android.widget.MenuPopupWindow;
import android.widget.PopupWindow;
-import android.widget.PopupWindow.OnDismissListener;
import android.widget.TextView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.PopupWindow.OnDismissListener;
import java.util.Objects;
@@ -46,8 +44,6 @@
final class StandardMenuPopup extends MenuPopup implements OnDismissListener, OnItemClickListener,
MenuPresenter, OnKeyListener {
private static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
- private static final int ITEM_LAYOUT_MATERIAL =
- com.android.internal.R.layout.popup_menu_item_layout_material;
private final Context mContext;
@@ -57,7 +53,6 @@
private final int mPopupMaxWidth;
private final int mPopupStyleAttr;
private final int mPopupStyleRes;
-
// The popup window is final in order to couple its lifecycle to the lifecycle of the
// StandardMenuPopup.
private final MenuPopupWindow mPopup;
@@ -119,15 +114,10 @@
public StandardMenuPopup(Context context, MenuBuilder menu, View anchorView, int popupStyleAttr,
int popupStyleRes, boolean overflowOnly) {
mContext = Objects.requireNonNull(context);
- boolean useNewContextMenu = AppGlobals.getIntCoreSetting(
- TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
- TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0;
-
mMenu = menu;
mOverflowOnly = overflowOnly;
final LayoutInflater inflater = LayoutInflater.from(context);
- mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly,
- useNewContextMenu ? ITEM_LAYOUT_MATERIAL : ITEM_LAYOUT);
+ mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly, ITEM_LAYOUT);
mPopupStyleAttr = popupStyleAttr;
mPopupStyleRes = popupStyleRes;
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index adbf645..0992db9 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -436,7 +436,7 @@
// overlay.
mContentInsets.set(mBaseContentInsets);
mInnerInsets = mBaseInnerInsets;
- if (!mOverlayMode && !stable) {
+ if (!mOverlayMode && !stable && hasContentOnApplyWindowInsetsListener()) {
mContentInsets.top += topInset;
mContentInsets.bottom += bottomInset;
// Content view has been shrunk, shrink all insets to match.
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 773823d..80a7599 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -82,6 +82,14 @@
"android_util_StringBlock.cpp",
"android_util_XmlBlock.cpp",
"android_util_jar_StrictJarFile.cpp",
+ "android_view_InputDevice.cpp",
+ "android_view_KeyCharacterMap.cpp",
+ "android_view_KeyEvent.cpp",
+ "android_view_MotionEvent.cpp",
+ "android_view_Surface.cpp",
+ "android_view_VelocityTracker.cpp",
+ "android_view_VerifiedKeyEvent.cpp",
+ "android_view_VerifiedMotionEvent.cpp",
"com_android_internal_util_VirtualRefBasePtr.cpp",
"core_jni_helpers.cpp",
":deviceproductinfoconstants_aidl",
@@ -158,16 +166,11 @@
"android_view_CompositionSamplingListener.cpp",
"android_view_DisplayEventReceiver.cpp",
"android_view_InputChannel.cpp",
- "android_view_InputDevice.cpp",
"android_view_InputEventReceiver.cpp",
"android_view_InputEventSender.cpp",
"android_view_InputQueue.cpp",
- "android_view_KeyCharacterMap.cpp",
- "android_view_KeyEvent.cpp",
- "android_view_MotionEvent.cpp",
"android_view_MotionPredictor.cpp",
"android_view_PointerIcon.cpp",
- "android_view_Surface.cpp",
"android_view_SurfaceControl.cpp",
"android_view_SurfaceControlHdrLayerInfoListener.cpp",
"android_view_WindowManagerGlobal.cpp",
@@ -175,9 +178,6 @@
"android_view_SurfaceSession.cpp",
"android_view_TextureView.cpp",
"android_view_TunnelModeEnabledListener.cpp",
- "android_view_VelocityTracker.cpp",
- "android_view_VerifiedKeyEvent.cpp",
- "android_view_VerifiedMotionEvent.cpp",
"android_text_Hyphenator.cpp",
"android_os_Debug.cpp",
"android_os_GraphicsEnvironment.cpp",
@@ -394,7 +394,8 @@
"-Wno-unused-function",
],
srcs: [
- "LayoutlibLoader.cpp",
+ "platform/host/HostRuntime.cpp",
+ "platform/host/native_window_jni.cpp",
],
include_dirs: [
"external/vulkan-headers/include",
@@ -414,6 +415,7 @@
"libhostgraphics",
"libhwui",
"libimage_type_recognition",
+ "libinput",
"libjpeg",
"libpiex",
"libpng",
@@ -441,16 +443,9 @@
"android_os_MessageQueue.cpp",
"android_os_Parcel.cpp",
- "android_view_KeyCharacterMap.cpp",
- "android_view_KeyEvent.cpp",
"android_view_InputChannel.cpp",
- "android_view_InputDevice.cpp",
"android_view_InputEventReceiver.cpp",
"android_view_InputEventSender.cpp",
- "android_view_MotionEvent.cpp",
- "android_view_VelocityTracker.cpp",
- "android_view_VerifiedKeyEvent.cpp",
- "android_view_VerifiedMotionEvent.cpp",
"android_util_AssetManager.cpp",
"android_util_Binder.cpp",
@@ -458,7 +453,6 @@
"android_util_FileObserver.cpp",
],
static_libs: [
- "libinput",
"libbinderthreadstateutils",
"libsqlite",
"libgui_window_info_static",
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
index 5c7b470..f82ebfe 100644
--- a/core/jni/android_tracing_PerfettoDataSource.cpp
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -93,49 +93,6 @@
return instance;
}
-jobject PerfettoDataSource::createTlsStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id) {
- ScopedLocalRef<jobject> args(env,
- env->NewObject(gCreateTlsStateArgsClassInfo.clazz,
- gCreateTlsStateArgsClassInfo.init, mJavaDataSource,
- inst_id));
-
- ScopedLocalRef<jobject> tslState(env,
- env->CallObjectMethod(mJavaDataSource,
- gPerfettoDataSourceClassInfo
- .createTlsState,
- args.get()));
-
- if (env->ExceptionCheck()) {
- LOGE_EX(env);
- env->ExceptionClear();
- LOG_ALWAYS_FATAL("Failed to create new Java Perfetto incremental state");
- }
-
- return env->NewGlobalRef(tslState.get());
-}
-
-jobject PerfettoDataSource::createIncrementalStateGlobalRef(JNIEnv* env,
- PerfettoDsInstanceIndex inst_id) {
- ScopedLocalRef<jobject> args(env,
- env->NewObject(gCreateIncrementalStateArgsClassInfo.clazz,
- gCreateIncrementalStateArgsClassInfo.init,
- mJavaDataSource, inst_id));
-
- ScopedLocalRef<jobject> incrementalState(env,
- env->CallObjectMethod(mJavaDataSource,
- gPerfettoDataSourceClassInfo
- .createIncrementalState,
- args.get()));
-
- if (env->ExceptionCheck()) {
- LOGE_EX(env);
- env->ExceptionClear();
- LOG_ALWAYS_FATAL("Failed to create Java Perfetto incremental state");
- }
-
- return env->NewGlobalRef(incrementalState.get());
-}
-
bool PerfettoDataSource::TraceIterateBegin() {
if (gInIteration) {
return false;
@@ -177,6 +134,15 @@
gInIteration = false;
}
+PerfettoDsInstanceIndex PerfettoDataSource::GetInstanceIndex() {
+ if (!gInIteration) {
+ LOG_ALWAYS_FATAL("Tried calling GetInstanceIndex outside of a tracer iteration.");
+ return -1;
+ }
+
+ return gIterator.impl.inst_id;
+}
+
jobject PerfettoDataSource::GetCustomTls() {
if (!gInIteration) {
LOG_ALWAYS_FATAL("Tried getting CustomTls outside of a tracer iteration.");
@@ -189,6 +155,18 @@
return tls_state->jobj;
}
+void PerfettoDataSource::SetCustomTls(jobject tlsState) {
+ if (!gInIteration) {
+ LOG_ALWAYS_FATAL("Tried getting CustomTls outside of a tracer iteration.");
+ return;
+ }
+
+ TlsState* tls_state =
+ reinterpret_cast<TlsState*>(PerfettoDsGetCustomTls(&dataSource, &gIterator));
+
+ tls_state->jobj = tlsState;
+}
+
jobject PerfettoDataSource::GetIncrementalState() {
if (!gInIteration) {
LOG_ALWAYS_FATAL("Tried getting IncrementalState outside of a tracer iteration.");
@@ -201,6 +179,18 @@
return incr_state->jobj;
}
+void PerfettoDataSource::SetIncrementalState(jobject incrementalState) {
+ if (!gInIteration) {
+ LOG_ALWAYS_FATAL("Tried getting IncrementalState outside of a tracer iteration.");
+ return;
+ }
+
+ IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(
+ PerfettoDsGetIncrementalState(&dataSource, &gIterator));
+
+ incr_state->jobj = incrementalState;
+}
+
void PerfettoDataSource::WritePackets(JNIEnv* env, jobjectArray packets) {
if (!gInIteration) {
LOG_ALWAYS_FATAL("Tried writing packets outside of a tracer iteration.");
@@ -211,7 +201,7 @@
for (int i = 0; i < packets_count; i++) {
jbyteArray packet_proto_buffer = (jbyteArray)env->GetObjectArrayElement(packets, i);
- jbyte* raw_proto_buffer = env->GetByteArrayElements(packet_proto_buffer, 0);
+ jbyte* raw_proto_buffer = env->GetByteArrayElements(packet_proto_buffer, nullptr);
int buffer_size = env->GetArrayLength(packet_proto_buffer);
struct PerfettoDsRootTracePacket trace_packet;
@@ -219,6 +209,8 @@
PerfettoPbMsgAppendBytes(&trace_packet.msg.msg, (const uint8_t*)raw_proto_buffer,
buffer_size);
PerfettoDsTracerPacketEnd(&gIterator, &trace_packet);
+
+ env->ReleaseByteArrayElements(packet_proto_buffer, raw_proto_buffer, 0 /* default mode */);
}
}
@@ -264,7 +256,7 @@
}
void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
- int buffer_exhausted_policy) {
+ jint buffer_exhausted_policy) {
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(datasource_ptr);
struct PerfettoDsParams params = PerfettoDsParamsDefault();
@@ -291,13 +283,8 @@
params.on_create_tls_cb = [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id,
struct PerfettoDsTracerImpl* tracer, void* user_arg) -> void* {
- JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
-
- auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg);
-
- jobject java_tls_state = datasource->createTlsStateGlobalRef(env, inst_id);
-
- auto* tls_state = new TlsState(java_tls_state);
+ // Populated later and only if required by the java side
+ auto* tls_state = new TlsState(NULL);
return static_cast<void*>(tls_state);
};
@@ -306,18 +293,16 @@
TlsState* tls_state = reinterpret_cast<TlsState*>(ptr);
- env->DeleteGlobalRef(tls_state->jobj);
+ if (tls_state->jobj != NULL) {
+ env->DeleteGlobalRef(tls_state->jobj);
+ }
delete tls_state;
};
params.on_create_incr_cb = [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id,
struct PerfettoDsTracerImpl* tracer, void* user_arg) -> void* {
- JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
-
- auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg);
- jobject java_incr_state = datasource->createIncrementalStateGlobalRef(env, inst_id);
-
- auto* incr_state = new IncrementalState(java_incr_state);
+ // Populated later and only if required by the java side
+ auto* incr_state = new IncrementalState(NULL);
return static_cast<void*>(incr_state);
};
@@ -326,7 +311,9 @@
IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(ptr);
- env->DeleteGlobalRef(incr_state->jobj);
+ if (incr_state->jobj != NULL) {
+ env->DeleteGlobalRef(incr_state->jobj);
+ }
delete incr_state;
};
@@ -386,31 +373,49 @@
PerfettoDsImplReleaseInstanceLocked(datasource->dataSource.impl, instance_idx);
}
-bool nativePerfettoDsTraceIterateBegin(jlong dataSourcePtr) {
+bool nativePerfettoDsTraceIterateBegin(JNIEnv* /* env */, jclass /* clazz */, jlong dataSourcePtr) {
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
return datasource->TraceIterateBegin();
}
-bool nativePerfettoDsTraceIterateNext(jlong dataSourcePtr) {
+bool nativePerfettoDsTraceIterateNext(JNIEnv* /* env */, jclass /* clazz */, jlong dataSourcePtr) {
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
return datasource->TraceIterateNext();
}
-void nativePerfettoDsTraceIterateBreak(jlong dataSourcePtr) {
+void nativePerfettoDsTraceIterateBreak(JNIEnv* /* env */, jclass /* clazz */, jlong dataSourcePtr) {
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
return datasource->TraceIterateBreak();
}
+jint nativeGetPerfettoDsInstanceIndex(JNIEnv* /* env */, jclass /* clazz */, jlong dataSourcePtr) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+ return (jint)datasource->GetInstanceIndex();
+}
+
jobject nativeGetCustomTls(JNIEnv* /* env */, jclass /* clazz */, jlong dataSourcePtr) {
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
return datasource->GetCustomTls();
}
+void nativeSetCustomTls(JNIEnv* env, jclass /* clazz */, jlong dataSourcePtr, jobject tlsState) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+ tlsState = env->NewGlobalRef(tlsState);
+ return datasource->SetCustomTls(tlsState);
+}
+
jobject nativeGetIncrementalState(JNIEnv* /* env */, jclass /* clazz */, jlong dataSourcePtr) {
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
return datasource->GetIncrementalState();
}
+void nativeSetIncrementalState(JNIEnv* env, jclass /* clazz */, jlong dataSourcePtr,
+ jobject incrementalState) {
+ sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
+ incrementalState = env->NewGlobalRef(incrementalState);
+ return datasource->SetIncrementalState(incrementalState);
+}
+
const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{"nativeCreate", "(Landroid/tracing/perfetto/DataSource;Ljava/lang/String;)J",
@@ -425,13 +430,16 @@
{"nativePerfettoDsTraceIterateBegin", "(J)Z", (void*)nativePerfettoDsTraceIterateBegin},
{"nativePerfettoDsTraceIterateNext", "(J)Z", (void*)nativePerfettoDsTraceIterateNext},
- {"nativePerfettoDsTraceIterateBreak", "(J)V", (void*)nativePerfettoDsTraceIterateBreak}};
+ {"nativePerfettoDsTraceIterateBreak", "(J)V", (void*)nativePerfettoDsTraceIterateBreak},
+ {"nativeGetPerfettoDsInstanceIndex", "(J)I", (void*)nativeGetPerfettoDsInstanceIndex}};
const JNINativeMethod gMethodsTracingContext[] = {
/* name, signature, funcPtr */
{"nativeFlush", "(J[[B)V", (void*)nativeFlush},
{"nativeGetCustomTls", "(J)Ljava/lang/Object;", (void*)nativeGetCustomTls},
{"nativeGetIncrementalState", "(J)Ljava/lang/Object;", (void*)nativeGetIncrementalState},
+ {"nativeSetCustomTls", "(JLjava/lang/Object;)V", (void*)nativeSetCustomTls},
+ {"nativeSetIncrementalState", "(JLjava/lang/Object;)V", (void*)nativeSetIncrementalState},
};
int register_android_tracing_PerfettoDataSource(JNIEnv* env) {
diff --git a/core/jni/android_tracing_PerfettoDataSource.h b/core/jni/android_tracing_PerfettoDataSource.h
index 209de29..fe15184 100644
--- a/core/jni/android_tracing_PerfettoDataSource.h
+++ b/core/jni/android_tracing_PerfettoDataSource.h
@@ -44,16 +44,16 @@
jobject newInstance(JNIEnv* env, void* ds_config, size_t ds_config_size,
PerfettoDsInstanceIndex inst_id);
- jobject createTlsStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id);
- jobject createIncrementalStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id);
-
bool TraceIterateBegin();
bool TraceIterateNext();
void TraceIterateBreak();
+ PerfettoDsInstanceIndex GetInstanceIndex();
void WritePackets(JNIEnv* env, jobjectArray packets);
jobject GetCustomTls();
+ void SetCustomTls(jobject);
jobject GetIncrementalState();
+ void SetIncrementalState(jobject);
void flushAll();
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index b801a69..3e3af40 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -28,6 +28,8 @@
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedUtfChars.h>
+#include <sstream>
+
#include "android_os_Parcel.h"
#include "android_util_Binder.h"
#include "core_jni_helpers.h"
@@ -598,8 +600,8 @@
// ----------------- @CriticalNative ------------------------------
-static jlong android_view_MotionEvent_nativeCopy(jlong destNativePtr, jlong sourceNativePtr,
- jboolean keepHistory) {
+static jlong android_view_MotionEvent_nativeCopy(CRITICAL_JNI_PARAMS_COMMA jlong destNativePtr,
+ jlong sourceNativePtr, jboolean keepHistory) {
MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
if (!destEvent) {
destEvent = new MotionEvent();
@@ -609,8 +611,8 @@
return reinterpret_cast<jlong>(destEvent);
}
-static jlong android_view_MotionEvent_nativeSplit(jlong destNativePtr, jlong sourceNativePtr,
- jint idBits) {
+static jlong android_view_MotionEvent_nativeSplit(CRITICAL_JNI_PARAMS_COMMA jlong destNativePtr,
+ jlong sourceNativePtr, jint idBits) {
MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
if (!destEvent) {
destEvent = new MotionEvent();
@@ -621,168 +623,192 @@
return reinterpret_cast<jlong>(destEvent);
}
-static jint android_view_MotionEvent_nativeGetId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getId();
}
-static jint android_view_MotionEvent_nativeGetDeviceId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetDeviceId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getDeviceId();
}
-static jint android_view_MotionEvent_nativeGetSource(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetSource(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getSource();
}
-static void android_view_MotionEvent_nativeSetSource(jlong nativePtr, jint source) {
+static void android_view_MotionEvent_nativeSetSource(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint source) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setSource(source);
}
-static jint android_view_MotionEvent_nativeGetDisplayId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getDisplayId();
}
-static void android_view_MotionEvent_nativeSetDisplayId(jlong nativePtr, jint displayId) {
+static void android_view_MotionEvent_nativeSetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint displayId) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->setDisplayId(displayId);
}
-static jint android_view_MotionEvent_nativeGetAction(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getAction();
}
-static void android_view_MotionEvent_nativeSetAction(jlong nativePtr, jint action) {
+static void android_view_MotionEvent_nativeSetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint action) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setAction(action);
}
-static int android_view_MotionEvent_nativeGetActionButton(jlong nativePtr) {
+static int android_view_MotionEvent_nativeGetActionButton(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getActionButton();
}
-static void android_view_MotionEvent_nativeSetActionButton(jlong nativePtr, jint button) {
+static void android_view_MotionEvent_nativeSetActionButton(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jint button) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setActionButton(button);
}
-static jboolean android_view_MotionEvent_nativeIsTouchEvent(jlong nativePtr) {
+static jboolean android_view_MotionEvent_nativeIsTouchEvent(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->isTouchEvent();
}
-static jint android_view_MotionEvent_nativeGetFlags(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getFlags();
}
-static void android_view_MotionEvent_nativeSetFlags(jlong nativePtr, jint flags) {
+static void android_view_MotionEvent_nativeSetFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint flags) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setFlags(flags);
}
-static jint android_view_MotionEvent_nativeGetEdgeFlags(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetEdgeFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getEdgeFlags();
}
-static void android_view_MotionEvent_nativeSetEdgeFlags(jlong nativePtr, jint edgeFlags) {
+static void android_view_MotionEvent_nativeSetEdgeFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint edgeFlags) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setEdgeFlags(edgeFlags);
}
-static jint android_view_MotionEvent_nativeGetMetaState(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetMetaState(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getMetaState();
}
-static jint android_view_MotionEvent_nativeGetButtonState(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetButtonState(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getButtonState();
}
-static void android_view_MotionEvent_nativeSetButtonState(jlong nativePtr, jint buttonState) {
+static void android_view_MotionEvent_nativeSetButtonState(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint buttonState) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setButtonState(buttonState);
}
-static jint android_view_MotionEvent_nativeGetClassification(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetClassification(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return static_cast<jint>(event->getClassification());
}
-static void android_view_MotionEvent_nativeOffsetLocation(jlong nativePtr, jfloat deltaX,
- jfloat deltaY) {
+static void android_view_MotionEvent_nativeOffsetLocation(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jfloat deltaX, jfloat deltaY) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->offsetLocation(deltaX, deltaY);
}
-static jfloat android_view_MotionEvent_nativeGetRawXOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawXOffset(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getRawXOffset();
}
-static jfloat android_view_MotionEvent_nativeGetRawYOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawYOffset(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getRawYOffset();
}
-static jfloat android_view_MotionEvent_nativeGetXPrecision(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetXPrecision(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getXPrecision();
}
-static jfloat android_view_MotionEvent_nativeGetYPrecision(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetYPrecision(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getYPrecision();
}
-static jfloat android_view_MotionEvent_nativeGetXCursorPosition(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetXCursorPosition(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getXCursorPosition();
}
-static jfloat android_view_MotionEvent_nativeGetYCursorPosition(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetYCursorPosition(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getYCursorPosition();
}
-static void android_view_MotionEvent_nativeSetCursorPosition(jlong nativePtr, jfloat x, jfloat y) {
+static void android_view_MotionEvent_nativeSetCursorPosition(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jfloat x, jfloat y) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setCursorPosition(x, y);
}
-static jlong android_view_MotionEvent_nativeGetDownTimeNanos(jlong nativePtr) {
+static jlong android_view_MotionEvent_nativeGetDownTimeNanos(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getDownTime();
}
-static void android_view_MotionEvent_nativeSetDownTimeNanos(jlong nativePtr, jlong downTimeNanos) {
+static void android_view_MotionEvent_nativeSetDownTimeNanos(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jlong downTimeNanos) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setDownTime(downTimeNanos);
}
-static jint android_view_MotionEvent_nativeGetPointerCount(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetPointerCount(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return jint(event->getPointerCount());
}
-static jint android_view_MotionEvent_nativeFindPointerIndex(jlong nativePtr, jint pointerId) {
+static jint android_view_MotionEvent_nativeFindPointerIndex(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jint pointerId) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return jint(event->findPointerIndex(pointerId));
}
-static jint android_view_MotionEvent_nativeGetHistorySize(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetHistorySize(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return jint(event->getHistorySize());
}
-static void android_view_MotionEvent_nativeScale(jlong nativePtr, jfloat scale) {
+static void android_view_MotionEvent_nativeScale(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jfloat scale) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->scale(scale);
}
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 869b53d..ac6298d 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -27,15 +27,19 @@
#include <android_runtime/android_graphics_SurfaceTexture.h>
#include <android_runtime/android_view_Surface.h>
#include <android_runtime/Log.h>
+#ifdef __ANDROID__
#include <private/android/AHardwareBufferHelpers.h>
#include "android_os_Parcel.h"
#include <binder/Parcel.h>
#include <gui/BLASTBufferQueue.h>
+#endif
#include <gui/Surface.h>
+#ifdef __ANDROID__
#include <gui/SurfaceControl.h>
#include <gui/view/Surface.h>
+#endif
#include <ui/GraphicBuffer.h>
#include <ui/Rect.h>
@@ -67,6 +71,7 @@
jfieldID bottom;
} gRectClassInfo;
+#ifdef __ANDROID__
class JNamedColorSpace {
public:
// ColorSpace.Named.SRGB.ordinal() = 0;
@@ -84,6 +89,7 @@
return ui::Dataspace::V0_SRGB;
}
}
+#endif
// ----------------------------------------------------------------------------
@@ -144,6 +150,7 @@
// ----------------------------------------------------------------------------
+#ifdef __ANDROID__
static jlong nativeCreateFromSurfaceTexture(JNIEnv* env, jclass clazz,
jobject surfaceTextureObj) {
sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(env, surfaceTextureObj));
@@ -162,6 +169,7 @@
surface->incStrong(&sRefBaseOwner);
return jlong(surface.get());
}
+#endif
static void nativeRelease(JNIEnv* env, jclass clazz, jlong nativeObject) {
sp<Surface> sur(reinterpret_cast<Surface *>(nativeObject));
@@ -269,7 +277,7 @@
}
// ----------------------------------------------------------------------------
-
+#ifdef __ANDROID__
static jlong nativeCreateFromSurfaceControl(JNIEnv* env, jclass clazz,
jlong surfaceControlNativeObj) {
sp<SurfaceControl> ctrl(reinterpret_cast<SurfaceControl *>(surfaceControlNativeObj));
@@ -380,6 +388,7 @@
// to the Parcel
surfaceShim.writeToParcel(parcel, /*nameAlreadyWritten*/true);
}
+#endif
static jint nativeGetWidth(JNIEnv* env, jclass clazz, jlong nativeObject) {
Surface* surface = reinterpret_cast<Surface*>(nativeObject);
@@ -412,6 +421,7 @@
return surface->disconnect(-1, IGraphicBufferProducer::DisconnectMode::AllLocal);
}
+#ifdef __ANDROID__
static jint nativeAttachAndQueueBufferWithColorSpace(JNIEnv* env, jclass clazz, jlong nativeObject,
jobject hardwareBuffer, jint colorSpaceId) {
Surface* surface = reinterpret_cast<Surface*>(nativeObject);
@@ -422,6 +432,7 @@
fromNamedColorSpaceValueToDataspace(colorSpaceId));
return err;
}
+#endif
static jint nativeSetSharedBufferModeEnabled(JNIEnv* env, jclass clazz, jlong nativeObject,
jboolean enabled) {
@@ -457,8 +468,10 @@
// ----------------------------------------------------------------------------
static const JNINativeMethod gSurfaceMethods[] = {
+#ifdef __ANDROID__
{"nativeCreateFromSurfaceTexture", "(Landroid/graphics/SurfaceTexture;)J",
(void*)nativeCreateFromSurfaceTexture},
+#endif
{"nativeRelease", "(J)V", (void*)nativeRelease},
{"nativeIsValid", "(J)Z", (void*)nativeIsValid},
{"nativeIsConsumerRunningBehind", "(J)Z", (void*)nativeIsConsumerRunningBehind},
@@ -467,21 +480,27 @@
{"nativeUnlockCanvasAndPost", "(JLandroid/graphics/Canvas;)V",
(void*)nativeUnlockCanvasAndPost},
{"nativeAllocateBuffers", "(J)V", (void*)nativeAllocateBuffers},
+#ifdef __ANDROID__
{"nativeCreateFromSurfaceControl", "(J)J", (void*)nativeCreateFromSurfaceControl},
{"nativeGetFromSurfaceControl", "(JJ)J", (void*)nativeGetFromSurfaceControl},
{"nativeReadFromParcel", "(JLandroid/os/Parcel;)J", (void*)nativeReadFromParcel},
{"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+#endif
{"nativeGetWidth", "(J)I", (void*)nativeGetWidth},
{"nativeGetHeight", "(J)I", (void*)nativeGetHeight},
{"nativeGetNextFrameNumber", "(J)J", (void*)nativeGetNextFrameNumber},
{"nativeSetScalingMode", "(JI)I", (void*)nativeSetScalingMode},
{"nativeForceScopedDisconnect", "(J)I", (void*)nativeForceScopedDisconnect},
+#ifdef __ANDROID__
{"nativeAttachAndQueueBufferWithColorSpace", "(JLandroid/hardware/HardwareBuffer;I)I",
(void*)nativeAttachAndQueueBufferWithColorSpace},
+#endif
{"nativeSetSharedBufferModeEnabled", "(JZ)I", (void*)nativeSetSharedBufferModeEnabled},
{"nativeSetAutoRefreshEnabled", "(JZ)I", (void*)nativeSetAutoRefreshEnabled},
{"nativeSetFrameRate", "(JFII)I", (void*)nativeSetFrameRate},
+#ifdef __ANDROID__
{"nativeGetFromBlastBufferQueue", "(JJ)J", (void*)nativeGetFromBlastBufferQueue},
+#endif
{"nativeDestroy", "(J)V", (void*)nativeDestroy},
};
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.cpp b/core/jni/com_android_internal_content_FileSystemUtils.cpp
index 4bd2d72..01920de 100644
--- a/core/jni/com_android_internal_content_FileSystemUtils.cpp
+++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp
@@ -42,7 +42,11 @@
bool punchHoles(const char *filePath, const uint64_t offset,
const std::vector<Elf64_Phdr> &programHeaders) {
struct stat64 beforePunch;
- lstat64(filePath, &beforePunch);
+ if (int result = lstat64(filePath, &beforePunch); result != 0) {
+ ALOGE("lstat64 failed for filePath %s, error:%d", filePath, errno);
+ return false;
+ }
+
uint64_t blockSize = beforePunch.st_blksize;
IF_ALOGD() {
ALOGD("Total number of LOAD segments %zu", programHeaders.size());
@@ -152,7 +156,10 @@
IF_ALOGD() {
struct stat64 afterPunch;
- lstat64(filePath, &afterPunch);
+ if (int result = lstat64(filePath, &afterPunch); result != 0) {
+ ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno);
+ return false;
+ }
ALOGD("Size after punching holes st_blocks: %" PRIu64 ", st_blksize: %ld, st_size: %" PRIu64
"",
afterPunch.st_blocks, afterPunch.st_blksize,
@@ -177,7 +184,7 @@
// only consider elf64 for punching holes
if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) {
- ALOGE("Provided file is not ELF64");
+ ALOGW("Provided file is not ELF64");
return false;
}
@@ -215,4 +222,108 @@
return punchHoles(filePath, offset, programHeaders);
}
+bool punchHolesInZip(const char *filePath, uint64_t offset, uint16_t extraFieldLen) {
+ android::base::unique_fd fd(open(filePath, O_RDWR | O_CLOEXEC));
+ if (!fd.ok()) {
+ ALOGE("Can't open file to punch %s", filePath);
+ return false;
+ }
+
+ struct stat64 beforePunch;
+ if (int result = lstat64(filePath, &beforePunch); result != 0) {
+ ALOGE("lstat64 failed for filePath %s, error:%d", filePath, errno);
+ return false;
+ }
+
+ uint64_t blockSize = beforePunch.st_blksize;
+ IF_ALOGD() {
+ ALOGD("Extra field length: %hu, Size before punching holes st_blocks: %" PRIu64
+ ", st_blksize: %ld, st_size: %" PRIu64 "",
+ extraFieldLen, beforePunch.st_blocks, beforePunch.st_blksize,
+ static_cast<uint64_t>(beforePunch.st_size));
+ }
+
+ if (extraFieldLen < blockSize) {
+ ALOGD("Skipping punching apk as extra field length is less than block size");
+ return false;
+ }
+
+ // content is preceded by extra field. Zip offset is offset of exact content.
+ // move back by extraFieldLen so that scan can be started at start of extra field.
+ uint64_t extraFieldStart;
+ if (__builtin_sub_overflow(offset, extraFieldLen, &extraFieldStart)) {
+ ALOGE("Overflow occurred when calculating start of extra field");
+ return false;
+ }
+
+ constexpr uint64_t kMaxSize = 64 * 1024;
+ // Use malloc to gracefully handle any oom conditions
+ std::unique_ptr<uint8_t, decltype(&free)> buffer(static_cast<uint8_t *>(malloc(kMaxSize)),
+ &free);
+ if (buffer == nullptr) {
+ ALOGE("Failed to allocate read buffer");
+ return false;
+ }
+
+ // Read the entire extra fields at once and punch file according to zero stretches.
+ if (!ReadFullyAtOffset(fd, buffer.get(), extraFieldLen, extraFieldStart)) {
+ ALOGE("Failed to read extra field content");
+ return false;
+ }
+
+ IF_ALOGD() {
+ ALOGD("Extra field length: %hu content near offset: %s", extraFieldLen,
+ HexString(buffer.get(), extraFieldLen).c_str());
+ }
+
+ uint64_t currentSize = 0;
+ while (currentSize < extraFieldLen) {
+ uint64_t end = currentSize;
+ // find zero ranges
+ while (end < extraFieldLen && *(buffer.get() + end) == 0) {
+ ++end;
+ }
+
+ uint64_t punchLen;
+ if (__builtin_sub_overflow(end, currentSize, &punchLen)) {
+ ALOGW("Overflow occurred when calculating punching length");
+ return false;
+ }
+
+ // Don't punch for every stretch of zero which is found
+ if (punchLen > blockSize) {
+ uint64_t punchOffset;
+ if (__builtin_add_overflow(extraFieldStart, currentSize, &punchOffset)) {
+ ALOGW("Overflow occurred when calculating punch start offset");
+ return false;
+ }
+
+ ALOGD("Punching hole in apk start: %" PRIu64 " len:%" PRIu64 "", punchOffset, punchLen);
+
+ // Punch hole for this entire stretch.
+ int result = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, punchOffset,
+ punchLen);
+ if (result < 0) {
+ ALOGE("fallocate failed to punch hole inside apk, error:%d", errno);
+ return false;
+ }
+ }
+ currentSize = end;
+ ++currentSize;
+ }
+
+ IF_ALOGD() {
+ struct stat64 afterPunch;
+ if (int result = lstat64(filePath, &afterPunch); result != 0) {
+ ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno);
+ return false;
+ }
+ ALOGD("punchHolesInApk:: Size after punching holes st_blocks: %" PRIu64
+ ", st_blksize: %ld, st_size: %" PRIu64 "",
+ afterPunch.st_blocks, afterPunch.st_blksize,
+ static_cast<uint64_t>(afterPunch.st_size));
+ }
+ return true;
+}
+
}; // namespace android
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.h b/core/jni/com_android_internal_content_FileSystemUtils.h
index a6b145c..52445e2 100644
--- a/core/jni/com_android_internal_content_FileSystemUtils.h
+++ b/core/jni/com_android_internal_content_FileSystemUtils.h
@@ -28,4 +28,11 @@
*/
bool punchHolesInElf64(const char* filePath, uint64_t offset);
+/*
+ * This function punches holes in zero segments of Apk file which are introduced during the
+ * alignment. Alignment tools add padding inside of extra field in local file header. punch holes in
+ * extra field for zero stretches till the actual file content.
+ */
+bool punchHolesInZip(const char* filePath, uint64_t offset, uint16_t extraFieldLen);
+
} // namespace android
\ No newline at end of file
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index faa83f8..9b8dab7 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -28,6 +28,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/statfs.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
@@ -145,8 +146,9 @@
uint16_t method;
off64_t offset;
-
- if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc)) {
+ uint16_t extraFieldLength;
+ if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc,
+ &extraFieldLength)) {
ALOGE("Couldn't read zip entry info\n");
return INSTALL_FAILED_INVALID_APK;
}
@@ -177,6 +179,12 @@
"%" PRIu64 "",
fileName, zipFile->getZipFileName(), offset);
}
+
+ // if extra field for this zip file is present with some length, possibility is that it is
+ // padding added for zip alignment. Punch holes there too.
+ if (!punchHolesInZip(zipFile->getZipFileName(), offset, extraFieldLength)) {
+ ALOGW("Failed to punch apk : %s at extra field", zipFile->getZipFileName());
+ }
#endif // ENABLE_PUNCH_HOLES
return INSTALL_SUCCEEDED;
@@ -279,6 +287,25 @@
return INSTALL_FAILED_CONTAINER_ERROR;
}
+#ifdef ENABLE_PUNCH_HOLES
+ // punch extracted elf files as well. This will fail where compression is on (like f2fs) but it
+ // will be useful for ext4 based systems
+ struct statfs64 fsInfo;
+ int result = statfs64(localFileName, &fsInfo);
+ if (result < 0) {
+ ALOGW("Failed to stat file :%s", localFileName);
+ }
+
+ if (result == 0 && fsInfo.f_type == EXT4_SUPER_MAGIC) {
+ ALOGD("Punching extracted elf file %s on fs:%" PRIu64 "", fileName,
+ static_cast<uint64_t>(fsInfo.f_type));
+ if (!punchHolesInElf64(localFileName, 0)) {
+ ALOGW("Failed to punch extracted elf file :%s from apk : %s", fileName,
+ zipFile->getZipFileName());
+ }
+ }
+#endif // ENABLE_PUNCH_HOLES
+
ALOGV("Successfully moved %s to %s\n", localTmpFileName, localFileName);
return INSTALL_SUCCEEDED;
diff --git a/core/jni/include/android_runtime/android_view_Surface.h b/core/jni/include/android_runtime/android_view_Surface.h
index 637b823..23cedb8 100644
--- a/core/jni/include/android_runtime/android_view_Surface.h
+++ b/core/jni/include/android_runtime/android_view_Surface.h
@@ -19,6 +19,7 @@
#include <android/native_window.h>
#include <ui/PublicFormat.h>
+#include <utils/StrongPointer.h>
#include "jni.h"
diff --git a/core/jni/platform/OWNERS b/core/jni/platform/OWNERS
new file mode 100644
index 0000000..10ce5cf
--- /dev/null
+++ b/core/jni/platform/OWNERS
@@ -0,0 +1,4 @@
+include /graphics/java/android/graphics/OWNERS
+
[email protected]
[email protected]
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/platform/host/HostRuntime.cpp
similarity index 94%
rename from core/jni/LayoutlibLoader.cpp
rename to core/jni/platform/host/HostRuntime.cpp
index 83b6afa..0433855 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -80,7 +80,7 @@
namespace android {
-extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
+extern int register_android_animation_PropertyValuesHolder(JNIEnv* env);
extern int register_android_content_AssetManager(JNIEnv* env);
extern int register_android_content_StringBlock(JNIEnv* env);
extern int register_android_content_XmlBlock(JNIEnv* env);
@@ -106,15 +106,17 @@
extern int register_android_view_ThreadedRenderer(JNIEnv* env);
extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
extern int register_android_view_VelocityTracker(JNIEnv* env);
-extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
+extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv* env);
-#define REG_JNI(name) { name }
+#define REG_JNI(name) \
+ { name }
struct RegJNIRec {
int (*mProc)(JNIEnv*);
};
-// Map of all possible class names to register to their corresponding JNI registration function pointer
-// The actual list of registered classes will be determined at runtime via the 'native_classes' System property
+// Map of all possible class names to register to their corresponding JNI registration function
+// pointer The actual list of registered classes will be determined at runtime via the
+// 'native_classes' System property
static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.animation.PropertyValuesHolder",
REG_JNI(register_android_animation_PropertyValuesHolder)},
@@ -154,8 +156,7 @@
};
static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>& jniRegMap,
- const vector<string>& classesToRegister, JNIEnv* env) {
-
+ const vector<string>& classesToRegister, JNIEnv* env) {
for (const string& className : classesToRegister) {
if (jniRegMap.at(className).mProc(env) < 0) {
return -1;
@@ -169,15 +170,14 @@
return 0;
}
-int AndroidRuntime::registerNativeMethods(JNIEnv* env,
- const char* className, const JNINativeMethod* gMethods, int numMethods) {
+int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className,
+ const JNINativeMethod* gMethods, int numMethods) {
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
JNIEnv* AndroidRuntime::getJNIEnv() {
JNIEnv* env;
- if (javaVM->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK)
- return nullptr;
+ if (javaVM->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) return nullptr;
return env;
}
@@ -186,11 +186,10 @@
}
static vector<string> parseCsv(const string& csvString) {
- vector<string> result;
+ vector<string> result;
istringstream stream(csvString);
string segment;
- while(getline(stream, segment, ','))
- {
+ while (getline(stream, segment, ',')) {
result.push_back(segment);
}
return result;
@@ -274,7 +273,9 @@
}
struct CloseHandleWrapper {
- void operator()(HANDLE h) { CloseHandle(h); }
+ void operator()(HANDLE h) {
+ CloseHandle(h);
+ }
};
std::unique_ptr<void, CloseHandleWrapper> mmapHandle(
CreateFileMapping(file, nullptr, PAGE_READONLY, 0, 0, nullptr));
@@ -384,8 +385,9 @@
// Configuration is stored as java System properties.
// Get a reference to System.getProperty
jclass system = FindClassOrDie(env, "java/lang/System");
- jmethodID getPropertyMethod = GetStaticMethodIDOrDie(env, system, "getProperty",
- "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
+ jmethodID getPropertyMethod =
+ GetStaticMethodIDOrDie(env, system, "getProperty",
+ "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
// Java system properties that contain LayoutLib config. The initial values in the map
// are the default values if the property is not specified.
diff --git a/core/jni/platform/host/native_window_jni.cpp b/core/jni/platform/host/native_window_jni.cpp
new file mode 100644
index 0000000..c17c480
--- /dev/null
+++ b/core/jni/platform/host/native_window_jni.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+#include <android_runtime/android_view_Surface.h>
+#include <system/window.h>
+#include <utils/StrongPointer.h>
+
+using namespace android;
+
+ANativeWindow* ANativeWindow_fromSurface(JNIEnv* env, jobject surface) {
+ sp<ANativeWindow> win = android_view_Surface_getNativeWindow(env, surface);
+ if (win != NULL) {
+ ANativeWindow_acquire(win.get());
+ }
+ return win.get();
+}
diff --git a/core/proto/android/app/profilerinfo.proto b/core/proto/android/app/profilerinfo.proto
index 86261ec..9941b83 100644
--- a/core/proto/android/app/profilerinfo.proto
+++ b/core/proto/android/app/profilerinfo.proto
@@ -36,4 +36,5 @@
// Denotes an agent (and its parameters) to attach for profiling.
optional string agent = 6;
optional int32 clock_type = 7;
+ optional int32 profiler_output_version = 8;
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 5ae365c..f5bbbb4 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -103,6 +103,7 @@
optional SettingProto accessibility_pinch_to_zoom_anywhere_enabled = 55 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_single_finger_panning_enabled = 56 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_floating_menu_targets = 57 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto display_daltonizer_saturation_level = 58 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c694426..76d7a41 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -581,6 +581,7 @@
<protected-broadcast android:name="android.app.action.KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED" />
<protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
+ <protected-broadcast android:name="com.android.server.notification.TimeToLiveHelper" />
<protected-broadcast android:name="NotificationHistoryDatabase.CLEANUP" />
<protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
<protected-broadcast android:name="EventConditionProvider.EVALUATE" />
@@ -5889,7 +5890,7 @@
<!-- Allows an application to subscribe to notifications about the nearby devices' presence
status change base on the UUIDs.
<p>Not for use by third-party applications.</p>
- @FlaggedApi("android.companion.flags.device_presence")
+ @FlaggedApi("android.companion.device_presence")
-->
<permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE"
android:protectionLevel="signature|privileged" />
@@ -8181,6 +8182,15 @@
<permission android:name="android.permission.SCREEN_TIMEOUT_OVERRIDE"
android:protectionLevel="signature" />
+ <!-- @SystemApi
+ @FlaggedApi("android.security.fsverity_api")
+ Allows app to setup fs-verity through FileIntegrityManager.
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.SETUP_FSVERITY"
+ android:protectionLevel="signature|privileged"/>
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
@@ -8414,9 +8424,11 @@
android:process=":ui">
</activity>
+ <!-- BlockedAppStreamingActivity is launched as the system user. -->
<activity android:name="com.android.internal.app.BlockedAppStreamingActivity"
android:theme="@style/Theme.Dialog.Confirmation"
android:excludeFromRecents="true"
+ android:showForAllUsers="true"
android:process=":ui">
</activity>
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index f3acab0..822aa22 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -14,185 +14,101 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
+ <!-- space -->
<path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0">
- <aapt:attr name="android:fillColor">
- <gradient
- android:startX="256"
- android:startY="21.81"
- android:endX="256"
- android:endY="350.42"
- android:type="linear">
- <item android:offset="0" android:color="#FF073042"/>
- <item android:offset="1" android:color="#FF073042"/>
- </gradient>
- </aapt:attr>
- </path>
+ android:pathData="M256,446.36C229.63,446.36 212.19,428.06 194.8,397.94C177.41,367.82 91.54,219.08 74.14,188.96C56.75,158.84 49.63,134.59 62.81,111.75C76,88.91 100.56,82.95 135.35,82.95C170.13,82.95 341.87,82.95 376.65,82.95C411.44,82.95 436,88.91 449.19,111.75C462.37,134.59 455.25,158.84 437.86,188.96C420.46,219.08 334.59,367.82 317.2,397.94C299.81,428.06 282.37,446.36 256,446.36H256Z"
+ android:fillColor="#202124"/>
<group>
<clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M256,446.36C229.63,446.36 212.19,428.06 194.8,397.94C177.41,367.82 91.54,219.08 74.14,188.96C56.75,158.84 49.63,134.59 62.81,111.75C76,88.91 100.56,82.95 135.35,82.95C170.13,82.95 341.87,82.95 376.65,82.95C411.44,82.95 436,88.91 449.19,111.75C462.37,134.59 455.25,158.84 437.86,188.96C420.46,219.08 334.59,367.82 317.2,397.94C299.81,428.06 282.37,446.36 256,446.36H256Z"/>
+ <!-- thrust plume -->
<path
- android:pathData="m195.27,187.64l2.25,-6.69c13.91,78.13 50.84,284.39 50.84,50.33 0,-0.97 0.72,-1.81 1.62,-1.81h12.69c0.9,0 1.62,0.83 1.62,1.8 -0.2,409.91 -69.03,-43.64 -69.03,-43.64Z"
- android:fillColor="#3ddc84"/>
+ android:pathData="M253,153C249.82,187.48 225.67,262.17 167.98,285.04C110.3,307.92 73.96,318.12 63,320.36L256,399L449,320.36C438.04,318.12 401.7,307.92 344.02,285.04C286.33,262.17 262.18,187.48 259,153H256H253Z"
+ android:fillColor="#C6FF00"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M253,153C251.5,187.42 241.7,261.98 214.5,284.82C187.3,307.65 170.17,317.84 165,320.08L256,398.58L347,320.08C341.83,317.84 324.7,307.65 297.5,284.82C270.3,261.98 260.5,187.42 259,153H256H253Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M256,153m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
+ android:fillColor="#ffffff"/>
+ <!-- android head and body -->
+ <path
+ android:pathData="M151,350h199v104h-199z"
+ android:fillColor="#5F6368"/>
+ <path
+ android:pathData="M358.42,350.44C358.36,350.02 358.29,349.6 358.22,349.18C357.8,346.6 357.27,344.04 356.65,341.52C355.57,337.12 354.21,332.82 352.59,328.66C351.22,325.13 349.66,321.7 347.93,318.38C345.7,314.11 343.18,310.01 340.41,306.11C337.01,301.34 333.21,296.86 329.06,292.74C327.32,291.01 325.52,289.34 323.65,287.73C319.62,284.26 315.32,281.09 310.78,278.26C310.82,278.19 310.85,278.12 310.89,278.05C312.97,274.46 315.05,270.88 317.13,267.29C319.17,263.78 321.2,260.28 323.23,256.77C324.69,254.26 326.15,251.74 327.61,249.22C327.95,248.62 328.22,248.01 328.43,247.37C329,245.61 329.02,243.76 328.57,242.03C328.45,241.61 328.31,241.19 328.14,240.78C327.97,240.38 327.77,239.98 327.54,239.6C326.76,238.29 325.65,237.16 324.26,236.33C323.02,235.6 321.64,235.16 320.23,235.03C319.64,234.98 319.04,234.99 318.45,235.05C317.96,235.1 317.47,235.19 316.99,235.32C315.26,235.77 313.67,236.72 312.42,238.08C311.98,238.57 311.58,239.12 311.23,239.71C309.77,242.23 308.31,244.75 306.85,247.27L300.76,257.78C298.68,261.37 296.6,264.96 294.52,268.55C294.29,268.94 294.06,269.33 293.83,269.73C293.52,269.6 293.21,269.48 292.89,269.36C281.43,264.99 269,262.6 256.01,262.6C255.65,262.6 255.3,262.6 254.94,262.6C243.39,262.72 232.29,264.73 221.93,268.33C220.73,268.75 219.55,269.19 218.38,269.65C218.16,269.29 217.95,268.92 217.74,268.55C215.66,264.96 213.58,261.38 211.5,257.79C209.47,254.28 207.43,250.78 205.4,247.27C203.94,244.76 202.48,242.23 201.02,239.72C200.68,239.12 200.28,238.58 199.83,238.09C198.59,236.72 196.99,235.78 195.27,235.32C194.79,235.2 194.3,235.1 193.81,235.05C193.22,234.99 192.62,234.99 192.03,235.04C190.61,235.16 189.23,235.6 188,236.34C186.6,237.16 185.5,238.3 184.71,239.6C184.49,239.99 184.29,240.38 184.12,240.79C183.95,241.2 183.8,241.61 183.69,242.04C183.23,243.76 183.26,245.62 183.82,247.38C184.03,248.01 184.3,248.63 184.65,249.23C186.11,251.74 187.57,254.26 189.02,256.78C191.06,260.28 193.09,263.79 195.12,267.29C197.2,270.88 199.28,274.47 201.36,278.06C201.38,278.09 201.4,278.12 201.41,278.15C197.22,280.76 193.23,283.64 189.47,286.8C187.21,288.69 185.04,290.68 182.96,292.75C178.81,296.87 175.01,301.35 171.6,306.12C168.82,310.02 166.31,314.11 164.09,318.39C162.35,321.71 160.79,325.14 159.42,328.67C157.8,332.83 156.44,337.13 155.36,341.53C154.75,344.05 154.22,346.6 153.79,349.19C153.72,349.61 153.66,350.03 153.59,350.45C153.36,351.95 153.16,353.46 153,354.98L359,354.98C358.84,353.46 358.64,351.95 358.41,350.45L358.42,350.44Z"
+ android:fillColor="#5F6368"/>
</group>
+ <!-- stars -->
<group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
<path
- android:pathData="m158.77,180.68l-33.17,57.45c-1.9,3.3 -0.77,7.52 2.53,9.42 3.3,1.9 7.52,0.77 9.42,-2.53l33.59,-58.17c54.27,24.33 116.34,24.33 170.61,0l33.59,58.17c1.97,3.26 6.21,4.3 9.47,2.33 3.17,-1.91 4.26,-5.99 2.47,-9.23l-33.16,-57.45c56.95,-30.97 95.91,-88.64 101.61,-156.76H57.17c5.7,68.12 44.65,125.79 101.61,156.76Z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M131.04,134.34H127V138.38H131.04V134.34Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M250.26,187.66L262.17,187.66A2.13,2.13 0,0 1,264.3 189.78L264.3,217.85A2.13,2.13 0,0 1,262.17 219.98L250.26,219.98A2.13,2.13 0,0 1,248.14 217.85L248.14,189.78A2.13,2.13 0,0 1,250.26 187.66z"
- android:fillColor="#3ddc84"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M167.04,256H163V260.04H167.04V256Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M250.12,170.29L262.32,170.29A1.98,1.98 0,0 1,264.3 172.26L264.3,176.39A1.98,1.98 0,0 1,262.32 178.37L250.12,178.37A1.98,1.98 0,0 1,248.14 176.39L248.14,172.26A1.98,1.98 0,0 1,250.12 170.29z"
- android:fillColor="#3ddc84"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M373.49,127H369.45V131.04H373.49V127Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M171.92,216.82h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M292.04,226H288V230.04H292.04V226Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M319.04,186.91H315V190.95H319.04V186.91Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M369.04,337.63h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M355.04,222H351V226.04H355.04V222Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M192.04,136H188V140.04H192.04V136Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M336.08,196H328V204.08H336.08V196Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M222.04,212H218V216.04H222.04V212Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M330.82,273.31h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M163.08,175H155V183.08H163.08V175Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M211.08,143H203V151.08H211.08V143Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M220.14,238.94h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M369.08,204H361V212.08H369.08V204Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M169.21,204.34H161.13V212.42H169.21V204.34Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M293.34,349.25h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M383.04,160.07H374.95V168.15H383.04V160.07Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M161.05,254.24h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
+ android:pathData="M192.08,183H184V191.08H192.08V183Z"
+ android:fillColor="#ffffff"/>
</group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M378.92,192h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M137.87,323.7h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
+ <!-- patch frame -->
<path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"
- android:strokeWidth="56.561"
+ android:pathData="M256,446.36C229.63,446.36 212.19,428.06 194.8,397.94C177.41,367.82 91.54,219.08 74.14,188.96C56.75,158.84 49.63,134.59 62.81,111.75C76,88.91 100.56,82.95 135.35,82.95C170.13,82.95 341.87,82.95 376.65,82.95C411.44,82.95 436,88.91 449.19,111.75C462.37,134.59 455.25,158.84 437.86,188.96C420.46,219.08 334.59,367.82 317.2,397.94C299.81,428.06 282.37,446.36 256,446.36H256Z"
+ android:strokeWidth="55"
android:fillColor="#00000000"
- android:strokeColor="#f86734"/>
+ android:strokeColor="#34A853"/>
+ <!-- text: ANDROID -->
<path
- android:pathData="m256.22,126.57c-6.69,0 -12.12,5.27 -12.12,11.77v14.52c0,1.25 1.02,2.27 2.27,2.27h0c1.25,0 2.27,-1.02 2.27,-2.27v-3.91c0,-2.51 2.04,-4.55 4.55,-4.55h6.06c2.51,0 4.55,2.04 4.55,4.55v3.91c0,1.25 1.02,2.27 2.27,2.27s2.27,-1.02 2.27,-2.27v-14.52c0,-6.5 -5.43,-11.77 -12.12,-11.77Z"
- android:fillColor="#3ddc84"/>
+ android:pathData="M170.11,92.71C170.97,94.9 171.41,96 171.41,96C171.41,96 170.37,96 168.29,96C166.22,96 165.18,96 165.18,96C165.18,96 164.93,95.27 164.42,93.82L159.71,80.63L158.82,77.75H158.61L157.82,80.63L153.28,93.82C152.82,95.27 152.6,96 152.6,96C152.6,96 151.54,96 149.43,96C147.33,96 146.28,96 146.28,96C146.28,96 146.7,94.89 147.56,92.67L155.21,72.87C155.89,71.07 156.23,70.17 156.23,70.17C156.23,70.17 157.06,70.17 158.72,70.17C160.52,70.17 161.42,70.17 161.42,70.17C161.42,70.17 161.76,71.07 162.46,72.87L170.11,92.71ZM166.04,91.12H158.64V86.91H164.42L166.04,91.12ZM158.64,91.12H151.08L152.63,86.91H158.64V91.12ZM203.85,93.46C203.85,95.15 203.85,96 203.85,96C203.85,96 202.95,96 201.15,96C199.38,96 198.5,96 198.5,96C198.5,96 198.03,95.26 197.08,93.79L188.08,79.75H187.88L188.01,83.45V93.25C188.01,95.08 188.01,96 188.01,96C188.01,96 187.03,96 185.09,96C183.15,96 182.17,96 182.17,96C182.17,96 182.17,95.08 182.17,93.25L182.16,73.9C182.16,71.45 182.16,70.22 182.16,70.22C182.16,70.22 183.19,70.22 185.25,70.22C187.24,70.22 188.24,70.22 188.24,70.22C188.24,70.22 188.7,70.96 189.63,72.42L198.16,85.85H198.36L198.21,82.09V72.89C198.21,71.11 198.21,70.22 198.21,70.22C198.21,70.22 199.15,70.22 201.04,70.22C202.91,70.22 203.85,70.22 203.85,70.22C203.85,70.22 203.85,71.11 203.85,72.89V93.46ZM226.52,96H220.17C218.24,96 217.27,96 217.27,96C217.27,96 217.27,95.02 217.27,93.05V73.19C217.27,71.21 217.27,70.22 217.27,70.22C217.27,70.22 218.24,70.22 220.17,70.22H226.52C230.46,70.22 233.63,71.41 236.03,73.77C238.43,76.12 239.63,79.23 239.63,83.09C239.63,86.98 238.43,90.11 236.03,92.47C233.63,94.82 230.46,96 226.52,96ZM223.17,75.64V90.74H226.18C228.46,90.77 230.27,90.11 231.62,88.78C232.96,87.44 233.63,85.57 233.63,83.17C233.63,80.78 232.96,78.93 231.62,77.62C230.28,76.3 228.47,75.64 226.18,75.64H223.17ZM257.51,93.23C257.51,95.08 257.51,96 257.51,96C257.51,96 256.54,96 254.6,96C252.66,96 251.7,96 251.7,96C251.7,96 251.7,95.09 251.7,93.26V73.19C251.7,71.21 251.7,70.22 251.7,70.22C251.7,70.22 252.66,70.22 254.6,70.22H261.89C264.44,70.22 266.6,70.98 268.35,72.49C270.1,74 270.98,76.03 270.98,78.56C270.98,80.9 270.14,82.83 268.47,84.35C266.81,85.87 264.65,86.62 262.01,86.62H254.02V82.41H261.33C262.4,82.41 263.29,82.08 264.01,81.42C264.73,80.75 265.09,79.88 265.09,78.81C265.09,77.84 264.74,77.03 264.05,76.38C263.35,75.72 262.49,75.39 261.47,75.39H257.51V93.23ZM264.77,93.82L258.66,84.46L264.8,84.25L271.23,93.62C272.37,95.21 272.94,96 272.94,96C272.94,96 271.82,96 269.57,96C267.3,96 266.17,96 266.17,96C266.17,96 265.7,95.27 264.77,93.82ZM296.04,96.58C292.33,96.58 289.16,95.33 286.52,92.85C283.89,90.37 282.58,87.11 282.58,83.09C282.58,79.07 283.9,75.83 286.54,73.36C289.19,70.88 292.36,69.65 296.04,69.65C299.71,69.65 302.87,70.9 305.51,73.41C308.16,75.91 309.49,79.13 309.49,83.09C309.49,87.03 308.17,90.26 305.53,92.8C302.9,95.32 299.74,96.58 296.04,96.58ZM296.04,90.83C298.19,90.83 299.98,90.1 301.41,88.64C302.85,87.17 303.57,85.33 303.57,83.09C303.57,80.84 302.84,78.99 301.39,77.55C299.95,76.11 298.17,75.39 296.04,75.39C293.92,75.39 292.13,76.12 290.68,77.57C289.24,79.01 288.52,80.85 288.52,83.09C288.52,85.35 289.23,87.2 290.66,88.66C292.1,90.11 293.89,90.83 296.04,90.83ZM327.64,93.05C327.64,95.02 327.64,96 327.64,96C327.64,96 326.63,96 324.61,96C322.59,96 321.57,96 321.57,96C321.57,96 321.57,95.02 321.57,93.05V73.18C321.57,71.21 321.57,70.22 321.57,70.22C321.57,70.22 322.58,70.22 324.6,70.22C326.63,70.22 327.64,70.22 327.64,70.22C327.64,70.22 327.64,71.21 327.64,73.18V93.05ZM350.31,96H343.96C342.03,96 341.06,96 341.06,96C341.06,96 341.06,95.02 341.06,93.05V73.19C341.06,71.21 341.06,70.22 341.06,70.22C341.06,70.22 342.03,70.22 343.96,70.22H350.31C354.25,70.22 357.42,71.41 359.82,73.77C362.22,76.12 363.42,79.23 363.42,83.09C363.42,86.98 362.22,90.11 359.82,92.47C357.42,94.82 354.25,96 350.31,96ZM346.96,75.64V90.74H349.97C352.25,90.77 354.06,90.11 355.41,88.78C356.75,87.44 357.42,85.57 357.42,83.17C357.42,80.78 356.75,78.93 355.41,77.62C354.07,76.3 352.26,75.64 349.97,75.64H346.96Z"
+ android:fillColor="#E9F3EB"/>
+ <!-- text: 15 -->
<path
- android:pathData="m93.34,116.36l3.85,-4.36 29.64,9.76 -4.44,5.03 -6.23,-2.1 -7.86,8.91 2.86,5.92 -4.43,5.03 -13.39,-28.18ZM110.43,122.76l-8.86,-3.02 4.11,8.41 4.76,-5.39Z"
- android:fillColor="#fff"/>
+ android:pathData="M236.59,363.25C236.59,365.75 236.59,367 236.59,367C236.59,367 235.32,367 232.79,367C230.32,367 229.09,367 229.09,367C229.09,367 229.09,365.75 229.09,363.25V305.85L216.64,314.75C215,315.92 214.19,316.5 214.19,316.5C214.19,316.5 213.54,315.5 212.24,313.5C210.97,311.6 210.34,310.65 210.34,310.65C210.34,310.62 211.2,309.98 212.94,308.75L227.64,298.2C230.3,296.23 231.64,295.25 231.64,295.25C231.64,295.25 232.1,295.25 233.04,295.25C235.4,295.25 236.59,295.25 236.59,295.25C236.59,295.25 236.59,296.47 236.59,298.9V363.25ZM247.09,330L251.19,299C251.52,296.6 251.69,295.4 251.69,295.4C251.69,295.4 252.77,295.4 254.94,295.4H284.54C286.97,295.4 288.19,295.4 288.19,295.4C288.19,295.4 288.19,296.58 288.19,298.95C288.19,301.48 288.19,302.75 288.19,302.75C288.19,302.75 286.97,302.75 284.54,302.75H257.49L254.19,327.45L254.39,327.5C256.09,325.77 258.27,324.38 260.94,323.35C263.61,322.28 266.65,321.75 270.09,321.75C276.55,321.75 281.99,323.97 286.39,328.4C290.79,332.8 292.99,338.32 292.99,344.95C292.99,351.75 290.8,357.4 286.44,361.9C282.11,366.37 276.42,368.6 269.39,368.6C263.09,368.6 257.77,367 253.44,363.8C249.1,360.6 246.26,356.85 244.89,352.55C244.26,350.32 243.94,349.2 243.94,349.2C243.94,349.2 245.09,348.77 247.39,347.9C249.79,347 250.99,346.55 250.99,346.55C250.99,346.55 251.3,347.73 251.94,350.1C252.8,352.73 254.71,355.27 257.64,357.7C260.61,360.13 264.44,361.35 269.14,361.35C274.27,361.35 278.24,359.88 281.04,356.95C283.84,353.98 285.24,350.03 285.24,345.1C285.24,340.37 283.67,336.52 280.54,333.55C277.44,330.58 273.4,329.1 268.44,329.1C265.47,329.1 262.95,329.52 260.89,330.35C258.82,331.15 257.09,332.28 255.69,333.75C254.39,335.25 253.74,336 253.74,336C253.74,336 252.55,335.52 250.19,334.55C247.85,333.62 246.69,333.15 246.69,333.15C246.69,333.15 246.82,332.1 247.09,330Z"
+ android:fillColor="#E9F3EB"/>
+ <!-- spacecraft -->
<path
- android:pathData="m153.62,100.85l-21.71,-6.2 10.38,14.38 -5.21,3.76 -16.78,-23.26 4.49,-3.24 21.65,6.19 -10.35,-14.35 5.24,-3.78 16.78,23.26 -4.49,3.24Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m161.46,63.15l8.99,-3.84c7.43,-3.18 15.96,0.12 19.09,7.44 3.13,7.32 -0.38,15.76 -7.81,18.94l-8.99,3.84 -11.28,-26.38ZM179.41,80.26c4.46,-1.91 5.96,-6.72 4.15,-10.96 -1.81,-4.24 -6.33,-6.48 -10.79,-4.57l-3.08,1.32 6.64,15.53 3.08,-1.32Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m204.23,47.57l11.1,-2.2c5.31,-1.05 9.47,2.08 10.4,6.76 0.72,3.65 -0.76,6.37 -4.07,8.34l12.4,10.44 -7.57,1.5 -11.65,-9.76 -1.03,0.2 2.3,11.61 -6.3,1.25 -5.57,-28.14ZM216.78,56.7c1.86,-0.37 3,-1.71 2.68,-3.33 -0.34,-1.7 -1.88,-2.43 -3.74,-2.06l-4.04,0.8 1.07,5.39 4.04,-0.8Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m244.29,55.6c0.13,-8.16 6.86,-14.72 15.06,-14.58 8.16,0.13 14.72,6.9 14.58,15.06s-6.91,14.72 -15.06,14.58c-8.2,-0.13 -14.71,-6.9 -14.58,-15.06ZM267.44,55.98c0.08,-4.64 -3.54,-8.66 -8.18,-8.74 -4.68,-0.08 -8.42,3.82 -8.5,8.47 -0.08,4.65 3.54,8.66 8.22,8.74 4.64,0.08 8.39,-3.82 8.46,-8.47Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m294.39,44.84l6.31,1.23 -5.49,28.16 -6.31,-1.23 5.49,-28.16Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m321.94,51.41l9.14,3.48c7.55,2.88 11.39,11.17 8.56,18.61 -2.83,7.44 -11.22,11.07 -18.77,8.19l-9.14,-3.48 10.22,-26.8ZM322.96,76.19c4.53,1.73 8.95,-0.69 10.6,-5 1.64,-4.3 -0.05,-9.06 -4.58,-10.78l-3.13,-1.19 -6.01,15.78 3.13,1.19Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m381.41,89.24l-4.21,-3.21 3.65,-4.78 9.06,6.91 -17.4,22.81 -4.85,-3.7 13.75,-18.02Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m397.96,126.37l-9.56,-10.26 3.61,-3.36 22.8,-1.25 3.74,4.02 -12.35,11.51 2.51,2.69 -4.08,3.8 -2.51,-2.69 -4.55,4.24 -4.16,-4.46 4.55,-4.24ZM407.83,117.17l-10.28,0.58 4.49,4.82 5.79,-5.4Z"
- android:fillColor="#fff"/>
+ android:pathData="M256.12,121C249.43,121 244,126.27 244,132.77V147.29C244,148.54 245.02,149.56 246.27,149.56C247.53,149.56 248.55,148.55 248.55,147.29V143.38C248.55,140.87 250.58,138.83 253.09,138.83H259.15C261.66,138.83 263.7,140.87 263.7,143.38V147.29C263.7,148.54 264.71,149.56 265.97,149.56C267.23,149.56 268.24,148.55 268.24,147.29V132.77C268.24,126.27 262.82,121 256.12,121H256.12Z"
+ android:fillColor="#E9F3EB"/>
</vector>
-
diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml
index 53a6836..8ae2a9b 100644
--- a/core/res/res/drawable-nodpi/stat_sys_adb.xml
+++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2023 The Android Open Source Project
+Copyright (C) 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -19,36 +19,20 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
+ <group>
+ <clip-path
+ android:pathData="M12,20.923C10.764,20.923 9.946,20.065 9.131,18.653C8.316,17.241 4.291,10.269 3.476,8.857C2.66,7.445 2.326,6.309 2.944,5.238C3.563,4.167 4.714,3.888 6.344,3.888C7.975,3.888 16.025,3.888 17.656,3.888C19.286,3.888 20.437,4.167 21.056,5.238C21.674,6.309 21.34,7.445 20.524,8.857C19.709,10.269 15.684,17.241 14.869,18.653C14.054,20.065 13.236,20.923 12,20.923H12Z"/>
<path
- android:name="ring"
- android:pathData="M 12 21 C 16.971 21 21 16.971 21 12 C 21 7.029 16.971 3 12 3 C 7.029 3 3 7.029 3 12 C 3 16.971 7.029 21 12 21 Z"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeWidth="2"/>
- <group android:name="group">
- <clip-path
- android:pathData="M 20.5 12 C 20.5 16.694 16.694 20.5 12 20.5 C 7.306 20.5 3.5 16.694 3.5 12 C 3.5 7.306 7.306 3.5 12 3.5 C 16.694 3.5 20.5 7.306 20.5 12 Z"/>
- <path
- android:pathData="M 14.812 9.023 C 13.014 9.707 11.027 9.707 9.229 9.023 L 8.265 10.693 C 8.06 11.048 7.605 11.17 7.25 10.964 C 6.895 10.759 6.773 10.305 6.978 9.949 L 7.899 8.355 C 5.988 7.137 4.702 5.084 4.502 2.695 L 4.456 2.153 L 19.584 2.153 L 19.539 2.695 C 19.339 5.084 18.052 7.137 16.142 8.355 L 17.067 9.958 C 17.259 10.307 17.142 10.746 16.801 10.952 C 16.45 11.165 15.993 11.052 15.781 10.701 L 15.775 10.693 L 14.812 9.023 Z"
- android:fillColor="#ffffff"/>
- <group android:name="stars">
- <path android:pathData="
- M 7,14 h1v1h-1z
- M 13,15 h1v1h-1z
- M 14,11 h1v1h-1z
-
- M 11,17 h0.5v0.5h-0.5z
- M 10,15 h0.5v0.5h-0.5z
- M 13,18 h0.5v0.5h-0.5z
- M 17,15 h0.5v0.5h-0.5z
- M 15,14 h0.5v0.5h-0.5z
- M 18,12 h0.5v0.5h-0.5z
- M 5,13 h0.5v0.5h-0.5z
- M 5,10 h0.5v0.5h-0.5z
- M 9,11 h0.5v0.5h-0.5z
- M 8,17 h0.5v0.5h-0.5z
- M 12,12 h0.5v0.5h-0.5z
- " android:fillColor="#ffffff"/>
- </group>
- </group>
+ android:pathData="M5,14.978h14v9.8h-14z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M18.722,15.576C18.717,15.548 18.713,15.521 18.708,15.493C18.68,15.324 18.646,15.156 18.605,14.991C18.534,14.701 18.445,14.42 18.339,14.146C18.249,13.915 18.146,13.69 18.033,13.472C17.886,13.192 17.722,12.923 17.539,12.667C17.316,12.354 17.067,12.06 16.795,11.789C16.68,11.676 16.562,11.566 16.44,11.461C16.175,11.233 15.893,11.025 15.595,10.839C15.598,10.834 15.6,10.83 15.602,10.825C15.739,10.59 15.875,10.355 16.012,10.119C16.145,9.889 16.279,9.659 16.412,9.429C16.508,9.264 16.604,9.098 16.699,8.933C16.722,8.894 16.74,8.854 16.753,8.812C16.791,8.696 16.792,8.575 16.762,8.462C16.754,8.434 16.745,8.406 16.734,8.38C16.723,8.353 16.71,8.327 16.695,8.302C16.644,8.216 16.571,8.142 16.479,8.087C16.399,8.039 16.308,8.011 16.215,8.002C16.176,7.999 16.137,7.999 16.098,8.003C16.066,8.007 16.034,8.013 16.002,8.021C15.889,8.051 15.785,8.113 15.703,8.202C15.674,8.235 15.647,8.27 15.624,8.309C15.529,8.475 15.433,8.64 15.337,8.805L14.937,9.495C14.801,9.731 14.664,9.966 14.528,10.202C14.513,10.227 14.498,10.253 14.483,10.279C14.462,10.271 14.442,10.263 14.421,10.255C13.669,9.968 12.853,9.811 12,9.811C11.977,9.811 11.954,9.811 11.931,9.811C11.172,9.819 10.444,9.951 9.764,10.188C9.686,10.215 9.608,10.244 9.531,10.274C9.517,10.25 9.503,10.226 9.489,10.202C9.353,9.966 9.216,9.731 9.08,9.495C8.946,9.265 8.813,9.035 8.679,8.805C8.584,8.64 8.488,8.475 8.392,8.31C8.37,8.271 8.343,8.235 8.314,8.203C8.232,8.113 8.127,8.051 8.014,8.021C7.983,8.013 7.951,8.007 7.919,8.004C7.88,8 7.841,7.999 7.802,8.003C7.709,8.011 7.618,8.039 7.537,8.088C7.446,8.142 7.373,8.217 7.322,8.302C7.307,8.327 7.294,8.353 7.283,8.38C7.271,8.407 7.262,8.434 7.255,8.462C7.225,8.575 7.226,8.697 7.264,8.812C7.277,8.854 7.295,8.894 7.318,8.934C7.413,9.099 7.509,9.264 7.605,9.429C7.738,9.659 7.872,9.889 8.005,10.119C8.141,10.355 8.278,10.59 8.414,10.826C8.415,10.828 8.417,10.83 8.418,10.832C8.143,11.003 7.881,11.192 7.634,11.4C7.486,11.524 7.343,11.654 7.207,11.79C6.935,12.061 6.685,12.354 6.462,12.668C6.279,12.923 6.114,13.192 5.968,13.472C5.855,13.691 5.752,13.915 5.662,14.147C5.556,14.42 5.467,14.702 5.396,14.991C5.355,15.157 5.321,15.324 5.293,15.494C5.288,15.521 5.284,15.549 5.279,15.576C5.264,15.675 5.251,15.774 5.241,15.874L18.759,15.874C18.749,15.774 18.736,15.675 18.72,15.576L18.722,15.576Z"
+ android:fillColor="#ffffff"/>
+ </group>
+ <path
+ android:pathData="M12,20.923C10.764,20.923 9.946,20.065 9.131,18.653C8.316,17.241 4.291,10.269 3.476,8.857C2.66,7.445 2.326,6.309 2.944,5.238C3.563,4.167 4.714,3.888 6.344,3.888C7.975,3.888 16.025,3.888 17.656,3.888C19.286,3.888 20.437,4.167 21.056,5.238C21.674,6.309 21.34,7.445 20.524,8.857C19.709,10.269 15.684,17.241 14.869,18.653C14.054,20.065 13.236,20.923 12,20.923H12Z"
+ android:strokeWidth="2"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
</vector>
+
diff --git a/core/res/res/drawable/ic_private_profile_badge.xml b/core/res/res/drawable/ic_private_profile_badge.xml
index b042c39..8f7bbb5 100644
--- a/core/res/res/drawable/ic_private_profile_badge.xml
+++ b/core/res/res/drawable/ic_private_profile_badge.xml
@@ -20,6 +20,10 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:pathData="M5,3H19C20.1,3 21,3.9 21,5V19C21,20.1 20.1,21 19,21H5C3.9,21 3,20.1 3,19V5C3,3.9 3.9,3 5,3ZM13.5,15.501L12.93,12.271C13.57,11.941 14,11.271 14,10.501C14,9.401 13.1,8.501 12,8.501C10.9,8.501 10,9.401 10,10.501C10,11.271 10.43,11.941 11.07,12.271L10.5,15.501H13.5Z"
+ android:pathData="M12,2L4,5V11.09C4,16.14 7.41,20.85 12,22C16.59,20.85 20,16.14 20,11.09V5L12,2ZM15,15V17H13V18H11V12.84C9.56,12.41 8.5,11.09 8.5,9.5C8.5,7.57 10.07,6 12,6C13.93,6 15.5,7.57 15.5,9.5C15.5,11.08 14.44,12.41 13,12.84V15H15Z"
+ android:fillColor="#3C4043"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M12,11C12.828,11 13.5,10.328 13.5,9.5C13.5,8.672 12.828,8 12,8C11.172,8 10.5,8.672 10.5,9.5C10.5,10.328 11.172,11 12,11Z"
android:fillColor="#3C4043"/>
</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_private_profile_icon_badge.xml b/core/res/res/drawable/ic_private_profile_icon_badge.xml
index 5f1f1b7..4143c3b 100644
--- a/core/res/res/drawable/ic_private_profile_icon_badge.xml
+++ b/core/res/res/drawable/ic_private_profile_icon_badge.xml
@@ -24,8 +24,12 @@
android:scaleY=".66"
android:translateX="42"
android:translateY="42">
- <path
- android:pathData="M5,3H19C20.1,3 21,3.9 21,5V19C21,20.1 20.1,21 19,21H5C3.9,21 3,20.1 3,19V5C3,3.9 3.9,3 5,3ZM13.5,15.501L12.93,12.271C13.57,11.941 14,11.271 14,10.501C14,9.401 13.1,8.501 12,8.501C10.9,8.501 10,9.401 10,10.501C10,11.271 10.43,11.941 11.07,12.271L10.5,15.501H13.5Z"
- android:fillColor="#3C4043"/>
+ <path
+ android:pathData="M12,2L4,5V11.09C4,16.14 7.41,20.85 12,22C16.59,20.85 20,16.14 20,11.09V5L12,2ZM15,15V17H13V18H11V12.84C9.56,12.41 8.5,11.09 8.5,9.5C8.5,7.57 10.07,6 12,6C13.93,6 15.5,7.57 15.5,9.5C15.5,11.08 14.44,12.41 13,12.84V15H15Z"
+ android:fillColor="#3C4043"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M12,11C12.828,11 13.5,10.328 13.5,9.5C13.5,8.672 12.828,8 12,8C11.172,8 10.5,8.672 10.5,9.5C10.5,10.328 11.172,11 12,11Z"
+ android:fillColor="#3C4043"/>
</group>
</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/stat_sys_private_profile_status.xml b/core/res/res/drawable/stat_sys_private_profile_status.xml
index 429070e..958a4d7 100644
--- a/core/res/res/drawable/stat_sys_private_profile_status.xml
+++ b/core/res/res/drawable/stat_sys_private_profile_status.xml
@@ -20,6 +20,10 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
+ android:pathData="M12,2L4,5V11.09C4,16.14 7.41,20.85 12,22C16.59,20.85 20,16.14 20,11.09V5L12,2ZM15,15V17H13V18H11V12.84C9.56,12.41 8.5,11.09 8.5,9.5C8.5,7.57 10.07,6 12,6C13.93,6 15.5,7.57 15.5,9.5C15.5,11.08 14.44,12.41 13,12.84V15H15Z"
android:fillColor="@android:color/white"
- android:pathData="M5,3H19C20.1,3 21,3.9 21,5V19C21,20.1 20.1,21 19,21H5C3.9,21 3,20.1 3,19V5C3,3.9 3.9,3 5,3ZM13.5,15.501L12.93,12.271C13.57,11.941 14,11.271 14,10.501C14,9.401 13.1,8.501 12,8.501C10.9,8.501 10,9.401 10,10.501C10,11.271 10.43,11.941 11.07,12.271L10.5,15.501H13.5Z"/>
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M12,11C12.828,11 13.5,10.328 13.5,9.5C13.5,8.672 12.828,8 12,8C11.172,8 10.5,8.672 10.5,9.5C10.5,10.328 11.172,11 12,11Z"
+ android:fillColor="@android:color/white"/>
</vector>
\ No newline at end of file
diff --git a/core/res/res/layout/list_menu_item_icon.xml b/core/res/res/layout/list_menu_item_icon.xml
index d851460..a30be6a 100644
--- a/core/res/res/layout/list_menu_item_icon.xml
+++ b/core/res/res/layout/list_menu_item_icon.xml
@@ -20,7 +20,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dip"
- android:layout_marginEnd="8dip"
+ android:layout_marginEnd="-8dip"
android:layout_marginTop="8dip"
android:layout_marginBottom="8dip"
android:scaleType="centerInside"
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index be1c939..6f06d80 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -80,6 +80,7 @@
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:importantForAccessibility="no"
+ android:focusable="false"
/>
<include layout="@layout/notification_expand_button"
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index 710a70a..64227d8 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -55,6 +55,7 @@
android:layout_height="match_parent"
android:layout_gravity="start"
android:importantForAccessibility="no"
+ android:focusable="false"
/>
<LinearLayout
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index df32d30..8a94c48 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -54,6 +54,7 @@
android:layout_height="match_parent"
android:layout_gravity="start"
android:importantForAccessibility="no"
+ android:focusable="false"
/>
<LinearLayout
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
index 3e82bd1..a83d923 100644
--- a/core/res/res/layout/notification_template_material_messaging.xml
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -67,6 +67,7 @@
android:layout_height="match_parent"
android:layout_gravity="start"
android:importantForAccessibility="no"
+ android:focusable="false"
/>
<!--
diff --git a/core/res/res/layout/popup_menu_item_layout_material.xml b/core/res/res/layout/popup_menu_item_layout_material.xml
deleted file mode 100644
index e20ead6..0000000
--- a/core/res/res/layout/popup_menu_item_layout_material.xml
+++ /dev/null
@@ -1,91 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2024 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- Forked from the popup_menu_item_layout.xml for material support. When you edit this file, you
- may also need to update that file.
--->
-
-<com.android.internal.view.menu.ListMenuItemView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minWidth="196dip"
- android:orientation="vertical" >
-
- <ImageView
- android:id="@+id/group_divider"
- android:layout_width="match_parent"
- android:layout_height="1dip"
- android:layout_marginTop="4dip"
- android:layout_marginBottom="4dip"
- android:background="@drawable/list_divider_material" />
-
- <LinearLayout
- android:id="@+id/content"
- android:layout_width="match_parent"
- android:layout_height="?attr/dropdownListPreferredItemHeight"
- android:paddingEnd="16dip"
- android:duplicateParentState="true" >
-
- <!-- Icon will be inserted here. -->
-
- <!-- The title and summary have some gap between them,
- and this 'group' should be centered vertically. -->
- <RelativeLayout
- android:layout_width="0dip"
- android:layout_weight="1"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:duplicateParentState="true">
-
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_alignParentStart="true"
- android:textAppearance="?attr/textAppearanceLargePopupMenu"
- android:singleLine="true"
- android:duplicateParentState="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:textAlignment="viewStart" />
-
- <TextView
- android:id="@+id/shortcut"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/title"
- android:layout_alignParentStart="true"
- android:textAppearance="?attr/textAppearanceSmallPopupMenu"
- android:singleLine="true"
- android:duplicateParentState="true"
- android:textAlignment="viewStart" />
-
- </RelativeLayout>
-
- <ImageView
- android:id="@+id/submenuarrow"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginStart="8dp"
- android:scaleType="center"
- android:visibility="gone" />
-
- <!-- Checkbox, and/or radio button will be inserted here. -->
-
- </LinearLayout>
-
-</com.android.internal.view.menu.ListMenuItemView>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 58ec95a..185c3c6 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -195,10 +195,10 @@
<string name="low_memory" product="default" msgid="2539532364144025569">"مساحة تخزين الهاتف ممتلئة. احذف بعض الملفات لإخلاء مساحة."</string>
<string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{تم تثبيت مرجع التصديق.}zero{تم تثبيت مراجع التصديق.}two{تم تثبيت مرجعَي التصديق.}few{تم تثبيت مراجع التصديق.}many{تم تثبيت مراجع التصديق.}other{تم تثبيت مراجع التصديق.}}"</string>
<string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"بواسطة جهة خارجية غير معلومة"</string>
- <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"بواسطة مشرف الملف الشخصي للعمل"</string>
+ <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"بواسطة مشرف ملف العمل"</string>
<string name="ssl_ca_cert_noti_managed" msgid="217337232273211674">"بواسطة <xliff:g id="MANAGING_DOMAIN">%s</xliff:g>"</string>
- <string name="work_profile_deleted" msgid="5891181538182009328">"تم حذف الملف الشخصي للعمل."</string>
- <string name="work_profile_deleted_details" msgid="3773706828364418016">"تطبيق المشرف للملف الشخصي للعمل مفقود أو تالف لذا تم حذف الملف الشخصي للعمل والبيانات ذات الصلة. اتصل بالمشرف للحصول على المساعدة."</string>
+ <string name="work_profile_deleted" msgid="5891181538182009328">"تم حذف ملف العمل."</string>
+ <string name="work_profile_deleted_details" msgid="3773706828364418016">"تطبيق المشرف لملف العمل مفقود أو تالف لذا تم حذف ملف العمل والبيانات ذات الصلة. اتصل بالمشرف للحصول على المساعدة."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"لم يعد ملفك الشخصي للعمل متاحًا على هذا الجهاز"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"تم إجراء محاولات كثيرة جدًا لإدخال كلمة المرور"</string>
<string name="device_ownership_relinquished" msgid="4080886992183195724">"تنازل المشرف عن الجهاز للاستخدام الشخصي"</string>
@@ -218,9 +218,9 @@
<string name="factory_reset_warning" msgid="6858705527798047809">"سيتم محو بيانات جهازك."</string>
<string name="factory_reset_message" msgid="2657049595153992213">"تعذّر استخدام تطبيق المشرف. سيتم محو بيانات جهازك الآن.\n\nإذا كانت لديك أسئلة، اتصل بمشرف مؤسستك."</string>
<string name="printing_disabled_by" msgid="3517499806528864633">"تم إيقاف الطباعة بواسطة <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
- <string name="personal_apps_suspension_title" msgid="7561416677884286600">"تفعيل الملف الشخصي للعمل"</string>
+ <string name="personal_apps_suspension_title" msgid="7561416677884286600">"تفعيل ملف العمل"</string>
<string name="personal_apps_suspension_text" msgid="6115455688932935597">"تم حظر تطبيقاتك الشخصية إلى أن تفعِّل ملفك الشخصي للعمل."</string>
- <string name="personal_apps_suspension_soon_text" msgid="8123898693479590">"سيتم حظر التطبيقات الشخصية في <xliff:g id="DATE">%1$s</xliff:g> في <xliff:g id="TIME">%2$s</xliff:g>. لا يسمح مشرف تكنولوجيا المعلومات في مؤسستك بإيقاف الملف الشخصي للعمل أكثر من <xliff:g id="NUMBER">%3$d</xliff:g> يوم."</string>
+ <string name="personal_apps_suspension_soon_text" msgid="8123898693479590">"سيتم حظر التطبيقات الشخصية في <xliff:g id="DATE">%1$s</xliff:g> في <xliff:g id="TIME">%2$s</xliff:g>. لا يسمح مشرف تكنولوجيا المعلومات في مؤسستك بإيقاف ملف العمل أكثر من <xliff:g id="NUMBER">%3$d</xliff:g> يوم."</string>
<string name="personal_apps_suspended_turn_profile_on" msgid="2758012869627513689">"تفعيل"</string>
<string name="work_profile_telephony_paused_title" msgid="7690804479291839519">"المكالمات والرسائل غير مفعّلة"</string>
<string name="work_profile_telephony_paused_text" msgid="8065762301100978221">"لقد أوقفت تطبيقات العمل مؤقتًا. لن تتلقّى مكالمات هاتفية أو رسائل نصية."</string>
@@ -312,9 +312,9 @@
<string name="safeMode" msgid="8974401416068943888">"الوضع الآمن"</string>
<string name="android_system_label" msgid="5974767339591067210">"نظام Android"</string>
<string name="user_owner_label" msgid="8628726904184471211">"التبديل إلى الملف الشخصي"</string>
- <string name="managed_profile_label" msgid="7316778766973512382">"التبديل إلى الملف الشخصي للعمل"</string>
+ <string name="managed_profile_label" msgid="7316778766973512382">"التبديل إلى ملف العمل"</string>
<string name="user_owner_app_label" msgid="1553595155465750298">"التبديل إلى تطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" في الملف الشخصي"</string>
- <string name="managed_profile_app_label" msgid="367401088383965725">"التبديل إلى تطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" في الملف الشخصي للعمل"</string>
+ <string name="managed_profile_app_label" msgid="367401088383965725">"التبديل إلى تطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" في ملف العمل"</string>
<string name="permgrouplab_contacts" msgid="4254143639307316920">"جهات الاتصال"</string>
<string name="permgroupdesc_contacts" msgid="9163927941244182567">"الوصول إلى جهات اتصالك"</string>
<string name="permgrouplab_location" msgid="1858277002233964394">"الموقع الجغرافي"</string>
@@ -708,10 +708,10 @@
<string name="face_acquired_too_dark" msgid="8539853432479385326">"الإضاءة غير كافية"</string>
<string name="face_acquired_too_close" msgid="4453646176196302462">"يُرجى إبعاد الهاتف عنك."</string>
<string name="face_acquired_too_far" msgid="2922278214231064859">"يُرجى تقريب الهاتف منك"</string>
- <string name="face_acquired_too_high" msgid="8278815780046368576">"يُرجى رفع الهاتف للأعلى"</string>
- <string name="face_acquired_too_low" msgid="4075391872960840081">"يُرجى خفض الهاتف للأسفل"</string>
- <string name="face_acquired_too_right" msgid="6245286514593540859">"يُرجى تحريك الهاتف لجهة اليسار"</string>
- <string name="face_acquired_too_left" msgid="9201762240918405486">"يُرجى تحريك الهاتف لجهة اليمين"</string>
+ <string name="face_acquired_too_high" msgid="8278815780046368576">"ارفع الهاتف للأعلى"</string>
+ <string name="face_acquired_too_low" msgid="4075391872960840081">"خفّض الهاتف للأسفل"</string>
+ <string name="face_acquired_too_right" msgid="6245286514593540859">"حرِّك الهاتف لجهة اليسار"</string>
+ <string name="face_acquired_too_left" msgid="9201762240918405486">"حرِّك الهاتف لجهة اليمين"</string>
<string name="face_acquired_poor_gaze" msgid="4427153558773628020">"يُرجى النظر إلى جهازك مباشرة أكثر."</string>
<string name="face_acquired_not_detected" msgid="1057966913397548150">"ارفع هاتفك إلى مستوى العينَين لأنّه تتعذّر رؤية وجهك"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"حركة أكثر من اللازم. يُرجى حمل الهاتف بثبات."</string>
@@ -1898,7 +1898,7 @@
<string name="clone_profile_label_badge" msgid="1871997694718793964">"نسخة طبق الأصل عن \"<xliff:g id="LABEL">%1$s</xliff:g>\""</string>
<string name="private_profile_label_badge" msgid="1712086003787839183">"ملف شخصي خاص على <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"طلب إدخال رقم التعريف الشخصي قبل إزالة التثبيت"</string>
- <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"طلب إدخال النقش الخاص بإلغاء القفل قبل إزالة التثبيت"</string>
+ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"طلب إدخال نقش فتح القفل قبل إزالة التثبيت"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"طلب إدخال كلمة المرور قبل إزالة التثبيت"</string>
<string name="package_installed_device_owner" msgid="7035926868974878525">"تم التثبيت بواسطة المشرف"</string>
<string name="package_updated_device_owner" msgid="7560272363805506941">"تم التحديث بواسطة المشرف"</string>
@@ -1948,7 +1948,7 @@
<string name="stk_cc_ss_to_ussd" msgid="8417905193112944760">"تم تغيير طلب SS إلى طلب USSD."</string>
<string name="stk_cc_ss_to_ss" msgid="132040645206514450">"تم التغيير إلى طلب SS الجديد."</string>
<string name="notification_phishing_alert_content_description" msgid="494227305355958790">"تنبيه بشأن تصيّد احتيالي"</string>
- <string name="notification_work_profile_content_description" msgid="5296477955677725799">"الملف الشخصي للعمل"</string>
+ <string name="notification_work_profile_content_description" msgid="5296477955677725799">"ملف العمل"</string>
<string name="notification_alerted_content_description" msgid="6139691253611265992">"تمّ تفعيل التنبيه"</string>
<string name="notification_verified_content_description" msgid="6401483602782359391">"تم التحقّق من المتّصل"</string>
<string name="expand_button_content_description_collapsed" msgid="3873368935659010279">"توسيع"</string>
@@ -2027,8 +2027,8 @@
<string name="new_sms_notification_title" msgid="6528758221319927107">"لديك رسائل جديدة"</string>
<string name="new_sms_notification_content" msgid="3197949934153460639">"فتح تطبيق الرسائل القصيرة SMS للعرض"</string>
<string name="profile_encrypted_title" msgid="9001208667521266472">"قد تكون بعض الوظائف مُقيّدة."</string>
- <string name="profile_encrypted_detail" msgid="5279730442756849055">"تم قفل الملف الشخصي للعمل."</string>
- <string name="profile_encrypted_message" msgid="1128512616293157802">"انقر لإلغاء قفل الملف الشخصي للعمل"</string>
+ <string name="profile_encrypted_detail" msgid="5279730442756849055">"تم قفل ملف العمل."</string>
+ <string name="profile_encrypted_message" msgid="1128512616293157802">"انقر لإلغاء قفل ملف العمل"</string>
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"تم الاتصال بـ <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"انقر لعرض الملفات"</string>
<string name="pin_target" msgid="8036028973110156895">"تثبيت"</string>
@@ -2213,7 +2213,7 @@
<string name="resolver_no_personal_apps_available" msgid="6284837227019594881">"ما مِن تطبيقات شخصية."</string>
<string name="miniresolver_open_work" msgid="6286176185835401931">"هل تريد فتح تطبيق \"<xliff:g id="APP">%s</xliff:g>\" في ملفك الشخصي للعمل؟"</string>
<string name="miniresolver_open_in_personal" msgid="807427577794490375">"هل تريد فتح المحتوى في تطبيق \"<xliff:g id="APP">%s</xliff:g>\" في الملف الشخصي؟"</string>
- <string name="miniresolver_open_in_work" msgid="941341494673509916">"هل تريد فتح المحتوى في تطبيق \"<xliff:g id="APP">%s</xliff:g>\" في الملف الشخصي للعمل؟"</string>
+ <string name="miniresolver_open_in_work" msgid="941341494673509916">"هل تريد فتح المحتوى في تطبيق \"<xliff:g id="APP">%s</xliff:g>\" في ملف العمل؟"</string>
<string name="miniresolver_call_in_work" msgid="528779988307529039">"هل تريد الاتصال من تطبيق العمل؟"</string>
<string name="miniresolver_switch_to_work" msgid="1042640606122638596">"هل تريد الانتقال إلى تطبيق العمل؟"</string>
<string name="miniresolver_call_information" msgid="6739417525304184083">"تسمح لك مؤسستك بإجراء المكالمات من تطبيقات العمل فقط."</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 1c01cc7..59940a3 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -706,7 +706,7 @@
<string name="face_acquired_too_far" msgid="2922278214231064859">"Bewege das Smartphone näher heran"</string>
<string name="face_acquired_too_high" msgid="8278815780046368576">"Bewege das Smartphone nach oben"</string>
<string name="face_acquired_too_low" msgid="4075391872960840081">"Bewege das Smartphone nach unten"</string>
- <string name="face_acquired_too_right" msgid="6245286514593540859">"Bewege das Smartphone nach links"</string>
+ <string name="face_acquired_too_right" msgid="6245286514593540859">"Bewege das Smartphone nach links"</string>
<string name="face_acquired_too_left" msgid="9201762240918405486">"Bewege das Smartphone nach rechts"</string>
<string name="face_acquired_poor_gaze" msgid="4427153558773628020">"Bitte sieh direkt auf dein Gerät."</string>
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Gesicht nicht erkannt. Smartphone auf Augenhöhe halten."</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 796e5ea..d2aa32c 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -710,7 +710,7 @@
<string name="face_acquired_too_right" msgid="6245286514593540859">"Mueve el teléfono hacia la izquierda"</string>
<string name="face_acquired_too_left" msgid="9201762240918405486">"Mueve el teléfono hacia la derecha"</string>
<string name="face_acquired_poor_gaze" msgid="4427153558773628020">"Mira de forma más directa al dispositivo."</string>
- <string name="face_acquired_not_detected" msgid="1057966913397548150">"No se detecta tu cara. Sujeta el teléfono a la altura de los ojos."</string>
+ <string name="face_acquired_not_detected" msgid="1057966913397548150">"No se puede detectar tu cara. Sujeta el teléfono a la altura de los ojos."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"El teléfono se mueve demasiado. Mantenlo quieto."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Vuelve a registrar tu cara."</string>
<string name="face_acquired_too_different" msgid="4505278456634706967">"Cara no reconocida. Inténtalo de nuevo."</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index ac070c7..c2b36b7 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -452,7 +452,7 @@
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"इससे ऐप्लिकेशन, \"specialUse\" टाइप वाली फ़ोरग्राउंड सेवाओं का इस्तेमाल कर पाता है"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"पता करें कि ऐप मेमोरी में कितनी जगह है"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"ऐप को उसका कोड, डेटा, और कैश मेमोरी के आकारों को फिर से पाने देता है"</string>
- <string name="permlab_writeSettings" msgid="8057285063719277394">"सिस्टम सेटिंग बदलें"</string>
+ <string name="permlab_writeSettings" msgid="8057285063719277394">"सिस्टम की सेटिंग में बदलाव करे"</string>
<string name="permdesc_writeSettings" msgid="8293047411196067188">"ऐप्लिकेशन को सिस्टम सेटिंग डेटा में बदलाव करने देता है. नुकसान पहुंचाने वाले ऐप्लिकेशन आपके सिस्टम के कॉन्फ़िगरेशन को खराब सकते हैं."</string>
<string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"प्रारंभ होने पर चलाएं"</string>
<string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"ऐप्लिकेशन को सिस्टम से बूटिंग पूरी करते ही अपने आप शुरू करने देता है. इससे टैबलेट को शुरू होने में ज़्यादा समय लग सकता है और ऐप्लिकेशन निरंतर चलाकर संपूर्ण टैबलेट को धीमा करने देता है."</string>
@@ -831,7 +831,7 @@
<string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"स्क्रीन को अनलॉक करते समय गलत लिखे गए पासवर्ड की संख्या पर निगरानी करें, और बहुत ज़्यादा बार गलत पासवर्ड लिखे जाने पर टैबलेट लॉक करें या टैबलेट का संपूर्ण डेटा मिटाएं."</string>
<string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"स्क्रीन को अनलॉक करते समय ध्यान रखें कि कितनी बार गलत पासवर्ड डाला गया है. अगर बहुत ज़्यादा बार गलत पासवर्ड डाला गया है, तो अपने Android TV डिवाइस को तुरंत लॉक करें या इसका सभी डेटा मिटाएं."</string>
<string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"स्क्रीन को अनलॉक करते समय ध्यान रखें कि कितनी बार गलत पासवर्ड डाला गया है. अगर बहुत ज़्यादा बार गलत पासवर्ड डाला गया है, तो सूचना और मनोरंजन की सुविधा देने वाले डिवाइस को लॉक करें या इस डिवाइस का सारा डेटा मिटाएं."</string>
- <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"स्क्रीन को अनलॉक करते समय ध्यान रखें कि कितनी बार गलत पासवर्ड डाला गया है. अगर बहुत ज़्यादा बार गलत पासवर्ड डाला गया है, तो अपने फ़ोन को तुरंत लॉक करें या फ़ोन का सारा डेटा मिटा दें."</string>
+ <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"स्क्रीन को अनलॉक करते समय ध्यान रखेगा कि कितनी बार गलत पासवर्ड डाला गया है. अगर बहुत ज़्यादा बार गलत पासवर्ड डाला जाएगा, तो फ़ोन को तुरंत लॉक करेगा या फ़ोन का सारा डेटा मिटा देगा."</string>
<string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"स्क्रीन का लॉक खोलते समय गलत तरीके से लिखे गए पासवर्ड पर नज़र रखें, और अगर बार-बार ज़्यादा पासवर्ड लिखे जाते हैं तो टैबलेट को लॉक करें या इस उपयोगकर्ता का सभी डेटा मिटा दें."</string>
<string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"स्क्रीन को अनलॉक करते समय ध्यान रखें कि कितनी बार गलत पासवर्ड डाला गया है. अगर बहुत ज़्यादा बार गलत पासवर्ड डाला गया है, तो अपने Android TV डिवाइस को तुरंत लॉक करें या इस उपयोगकर्ता का सभी डेटा मिटाएं."</string>
<string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"स्क्रीन को अनलॉक करते समय ध्यान रखें कि कितनी बार गलत पासवर्ड डाला गया है. अगर बहुत ज़्यादा बार गलत पासवर्ड डाला गया है, तो सूचना और मनोरंजन की सुविधा देने वाले डिवाइस को लॉक करें या इस प्रोफ़ाइल का सारा डेटा मिटाएं."</string>
@@ -844,7 +844,7 @@
<string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"फ़ैक्टरी डेटा रीसेट करके चेतावनी दिए बिना फ़ोन का डेटा मिटाना."</string>
<string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"फ़ैक्ट्री डेटा रीसेट करके अपने Android TV डिवाइस का डेटा बिना चेतावनी दिए मिटाएं."</string>
<string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"फ़ैक्ट्री डेटा रीसेट करके, बिना किसी चेतावनी के सूचना और मनोरंजन की सुविधा देने वाले डिवाइस में सेव डेटा को हमेशा के लिए मिटाएं."</string>
- <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"इससे फ़ैक्टरी डेटा रीसेट करके, चेतावनी दिए बिना फ़ोन का डेटा मिट जाता है."</string>
+ <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"फ़ैक्टरी डेटा रीसेट करके, चेतावनी दिए बिना फ़ोन का डेटा मिटा देगा."</string>
<string name="policylab_wipeData_secondaryUser" product="automotive" msgid="115034358520328373">"प्रोफ़ाइल का डेटा मिटाना"</string>
<string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"उपयोगकर्ता डेटा मिटाएं"</string>
<string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="2336676480090926470">"इस टैबलेट पर मौजूद इस उपयोगकर्ता का डेटा बिना चेतावनी के मिटा दें."</string>
@@ -1655,7 +1655,7 @@
<string name="wireless_display_route_description" msgid="8297563323032966831">"वायरलेस डिसप्ले"</string>
<string name="media_route_button_content_description" msgid="2299223698196869956">"कास्ट करें"</string>
<string name="media_route_chooser_title" msgid="6646594924991269208">"डिवाइस से कनेक्ट करें"</string>
- <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"स्क्रीन को डिवाइस में कास्ट करें"</string>
+ <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"स्क्रीन को डिवाइस पर कास्ट करें"</string>
<string name="media_route_chooser_searching" msgid="6119673534251329535">"डिवाइस खोजे जा रहे हैं…"</string>
<string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"सेटिंग"</string>
<string name="media_route_controller_disconnect" msgid="7362617572732576959">"डिसकनेक्ट करें"</string>
@@ -1722,14 +1722,14 @@
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"चालू न करें"</string>
<string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"चालू है"</string>
<string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"बंद है"</string>
- <string name="accessibility_enable_service_title" msgid="3931558336268541484">"<xliff:g id="SERVICE">%1$s</xliff:g> को अपना डिवाइस पूरी तरह कंट्रोल करने की मंज़ूरी दें?"</string>
+ <string name="accessibility_enable_service_title" msgid="3931558336268541484">"<xliff:g id="SERVICE">%1$s</xliff:g> को अपना डिवाइस पूरी तरह कंट्रोल करने की अनुमति देनी है?"</string>
<string name="accessibility_service_warning_description" msgid="291674995220940133">"पूरी तरह कंट्रोल करने की अनुमति उन ऐप्लिकेशन के लिए ठीक है जो सुलभता से जुड़ी ज़रूरतों के लिए बने हैं, लेकिन ज़्यादातर ऐप्लिकेशन के लिए यह ठीक नहीं है."</string>
<string name="accessibility_service_screen_control_title" msgid="190017412626919776">"स्क्रीन को देखें और कंट्रोल करें"</string>
<string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"यह स्क्रीन पर दिखने वाले कॉन्टेंट को पढ़ सकता है और उसे दूसरे ऐप्लिकेशन के ऊपर दिखा सकता है."</string>
<string name="accessibility_service_action_perform_title" msgid="779670378951658160">"देखें और कार्रवाई करें"</string>
<string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"यह आपके और किसी ऐप्लिकेशन या हार्डवेयर सेंसर के बीच होने वाले इंटरैक्शन को ट्रैक कर सकता है और आपकी तरफ़ से ऐप्लिकेशन के साथ इंटरैक्ट कर सकता है."</string>
<string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"अनुमति दें"</string>
- <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"इंकार करें"</string>
+ <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"न दें"</string>
<string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"अनइंस्टॉल करें"</string>
<string name="accessibility_dialog_touch_filtered_warning" msgid="3741940116597822451">"ऐप्लिकेशन की वजह से, अनुमति का अनुरोध समझने में परेशानी हो रही है. इसलिए, आपके जवाब की पुष्टि नहीं की जा सकी."</string>
<string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"किसी सुविधा का इस्तेमाल करने के लिए, उस पर टैप करें:"</string>
@@ -1761,7 +1761,7 @@
<string name="owner_name" msgid="8713560351570795743">"मालिक"</string>
<string name="guest_name" msgid="8502103277839834324">"मेहमान"</string>
<string name="error_message_title" msgid="4082495589294631966">"गड़बड़ी"</string>
- <string name="error_message_change_not_allowed" msgid="843159705042381454">"आपका व्यवस्थापक इस बदलाव की अनुमति नहीं देता"</string>
+ <string name="error_message_change_not_allowed" msgid="843159705042381454">"आपका एडमिन इस बदलाव की अनुमति नहीं देता"</string>
<string name="app_not_found" msgid="3429506115332341800">"इस कार्यवाही को प्रबंधित करने के लिए कोई ऐप्स नहीं मिला"</string>
<string name="revoke" msgid="5526857743819590458">"रद्द करें"</string>
<string name="mediasize_iso_a0" msgid="7039061159929977973">"ISO A0"</string>
@@ -1902,7 +1902,7 @@
<string name="confirm_battery_saver" msgid="5247976246208245754">"ठीक है"</string>
<string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"बैटरी सेवर, गहरे रंग वाली थीम को चालू करता है. साथ ही, इस मोड में बैकग्राउंड की गतिविधि, कुछ विज़ुअल इफ़ेक्ट, और कुछ खास सुविधाएं कम या बंद हो जाती हैं. कुछ इंटरनेट कनेक्शन भी पूरी तरह काम नहीं करते."</string>
<string name="battery_saver_description" msgid="8518809702138617167">"बैटरी सेवर, गहरे रंग वाली थीम को चालू करता है. साथ ही, इस मोड में बैकग्राउंड की गतिविधि, कुछ विज़ुअल इफ़ेक्ट, और कुछ सुविधाएं सीमित या बंद हो जाती हैं. कुछ इंटरनेट कनेक्शन भी पूरी तरह काम नहीं करते."</string>
- <string name="data_saver_description" msgid="4995164271550590517">"डेटा खर्च को कम करने के लिए, डेटा बचाने की सेटिंग कुछ ऐप्लिकेशन को बैकग्राउंड में डेटा भेजने या डेटा पाने से रोकती है. फ़िलहाल, जिस ऐप्लिकेशन का इस्तेमाल किया जा रहा है वह डेटा ऐक्सेस कर सकता है, लेकिन ऐसा कभी-कभी ही हो पाएगा. उदाहरण के लिए, इमेज तब तक नहीं दिखेंगी, जब तक उन पर टैप नहीं किया जाएगा."</string>
+ <string name="data_saver_description" msgid="4995164271550590517">"डेटा खर्च को कम करने के लिए, डेटा बचाने की सेटिंग बैकग्राउंड में चलने वाले कुछ ऐप्लिकेशन को डेटा भेजने या पाने से रोकती है. हालांकि, फ़िलहाल इस्तेमाल किया जा रहा ऐप्लिकेशन, डेटा को ऐक्सेस कर सकता है, लेकिन वह अक्सर ऐसा नहीं कर पाएगा. उदाहरण के लिए, ऐसा हो सकता है कि इमेज तब तक न दिखें, जब तक आप उन पर टैप न करें."</string>
<string name="data_saver_enable_title" msgid="7080620065745260137">"डेटा बचाने की सेटिंग चालू करनी है?"</string>
<string name="data_saver_enable_button" msgid="4399405762586419726">"चालू करें"</string>
<string name="zen_mode_duration_minutes_summary" msgid="4555514757230849789">"{count,plural, =1{एक मिनट के लिए ({formattedTime} तक)}one{# मिनट के लिए ({formattedTime} तक)}other{# मिनट के लिए ({formattedTime} तक)}}"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 9c8eb56..518e851 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1894,7 +1894,7 @@
<string name="clone_profile_label_badge" msgid="1871997694718793964">"<xliff:g id="LABEL">%1$s</xliff:g> klónozása"</string>
<string name="private_profile_label_badge" msgid="1712086003787839183">"Privát <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"PIN-kód kérése a kitűzés feloldásához"</string>
- <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Feloldási minta kérése a rögzítés feloldásához"</string>
+ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Feloldási minta kérése a kitűzés feloldásához"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Jelszó kérése a rögzítés feloldásához"</string>
<string name="package_installed_device_owner" msgid="7035926868974878525">"A rendszergazda által telepítve"</string>
<string name="package_updated_device_owner" msgid="7560272363805506941">"A rendszergazda által frissítve"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 63edfce..74d3751 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -647,17 +647,17 @@
<string name="biometric_error_generic" msgid="6784371929985434439">"Չհաջողվեց նույնականացնել"</string>
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Էկրանի կողպում"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Շարունակելու համար ապակողպեք էկրանը"</string>
- <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Մատը ուժեղ սեղմեք սկաների վրա"</string>
+ <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Մատը ուժեղ սեղմեք սկաներին"</string>
<string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Մատնահետքը չի ճանաչվել։ Նորից փորձեք։"</string>
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Մաքրեք մատնահետքերի սկաները և նորից փորձեք"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Մաքրեք սկաները և նորից փորձեք"</string>
- <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Մատը ուժեղ սեղմեք սկաների վրա"</string>
+ <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Մատը ուժեղ սեղմեք սկաներին"</string>
<string name="fingerprint_acquired_too_slow" msgid="6683510291554497580">"Շատ դանդաղ անցկացրիք մատը: Փորձեք նորից:"</string>
<string name="fingerprint_acquired_already_enrolled" msgid="2285166003936206785">"Փորձեք մեկ այլ մատնահետք"</string>
<string name="fingerprint_acquired_too_bright" msgid="3863560181670915607">"Շատ լուսավոր է"</string>
<string name="fingerprint_acquired_power_press" msgid="3107864151278434961">"Հայտնաբերվել է սնուցման կոճակի սեղմում"</string>
<string name="fingerprint_acquired_try_adjusting" msgid="3667006071003809364">"Փորձեք փոխել մատի դիրքը"</string>
- <string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"Թեթևակի փոխեք մատի դիրքն ամեն անգամ"</string>
+ <string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"Ամեն անգամ թեթևակի փոխեք մատի դիրքը"</string>
<string-array name="fingerprint_acquired_vendor">
</string-array>
<string name="fingerprint_error_not_match" msgid="4599441812893438961">"Մատնահետքը չի ճանաչվել"</string>
@@ -704,8 +704,8 @@
<string name="face_acquired_too_dark" msgid="8539853432479385326">"Թույլ լուսավորություն"</string>
<string name="face_acquired_too_close" msgid="4453646176196302462">"Փոքր-ինչ հեռու պահեք հեռախոսը"</string>
<string name="face_acquired_too_far" msgid="2922278214231064859">"Մոտեցրեք հեռախոսը"</string>
- <string name="face_acquired_too_high" msgid="8278815780046368576">"Պահեք հեռախոսն ավելի վերև"</string>
- <string name="face_acquired_too_low" msgid="4075391872960840081">"Պահեք հեռախոսն ավելի ներքև"</string>
+ <string name="face_acquired_too_high" msgid="8278815780046368576">"Հեռախոսը շարժեք ավելի վերև"</string>
+ <string name="face_acquired_too_low" msgid="4075391872960840081">"Հեռախոսը շարժեք ավելի ներքև"</string>
<string name="face_acquired_too_right" msgid="6245286514593540859">"Հեռախոսը շարժեք դեպի ձախ"</string>
<string name="face_acquired_too_left" msgid="9201762240918405486">"Հեռախոսը շարժեք դեպի աջ"</string>
<string name="face_acquired_poor_gaze" msgid="4427153558773628020">"Նայեք ուղիղ էկրանին։"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 996c177..caa801d 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -306,7 +306,7 @@
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Ýttu til að fá upplýsingar um rafhlöðu- og gagnanotkun"</string>
<string name="foreground_service_multiple_separator" msgid="5002287361849863168">"<xliff:g id="LEFT_SIDE">%1$s</xliff:g>, <xliff:g id="RIGHT_SIDE">%2$s</xliff:g>"</string>
<string name="safeMode" msgid="8974401416068943888">"Örugg stilling"</string>
- <string name="android_system_label" msgid="5974767339591067210">"Android kerfið"</string>
+ <string name="android_system_label" msgid="5974767339591067210">"Android-kerfið"</string>
<string name="user_owner_label" msgid="8628726904184471211">"Skipta yfir í einkasnið"</string>
<string name="managed_profile_label" msgid="7316778766973512382">"Skipta yfir í vinnusnið"</string>
<string name="user_owner_app_label" msgid="1553595155465750298">"Skipta yfir í einkasnið <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
@@ -1723,9 +1723,9 @@
<string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"KVEIKT"</string>
<string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"SLÖKKT"</string>
<string name="accessibility_enable_service_title" msgid="3931558336268541484">"Viltu leyfa „<xliff:g id="SERVICE">%1$s</xliff:g>“ að hafa fulla stjórn yfir tækinu þínu?"</string>
- <string name="accessibility_service_warning_description" msgid="291674995220940133">"Full stjórnun er viðeigandi fyrir forrit sem hjálpa þér ef þú hefur ekki aðgang, en ekki fyrir flest forrit."</string>
+ <string name="accessibility_service_warning_description" msgid="291674995220940133">"Full stjórn er viðeigandi fyrir forrit sem hjálpa þér ef þú hefur ekki aðgang, en ekki fyrir flest forrit."</string>
<string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Skoða og stjórna skjá"</string>
- <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Það getur lesið allt efni á skjánum og birt efni yfir öðrum forritum."</string>
+ <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Getur lesið allt efni á skjánum og birt efni yfir öðrum forritum."</string>
<string name="accessibility_service_action_perform_title" msgid="779670378951658160">"Skoða og framkvæma aðgerðir"</string>
<string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Það getur fylgst með samskiptum þínum við forrit eða skynjara vélbúnaðar og haft samskipti við forrit fyrir þína hönd."</string>
<string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Leyfa"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index e8dee3c..4eb0ced6 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -196,7 +196,7 @@
<string name="work_profile_deleted" msgid="5891181538182009328">"仕事用プロファイルが削除されました"</string>
<string name="work_profile_deleted_details" msgid="3773706828364418016">"仕事用プロファイルの管理アプリがないか、破損しています。そのため仕事用プロファイルと関連データが削除されました。管理者にサポートをご依頼ください。"</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"お使いの仕事用プロファイルはこのデバイスで使用できなくなりました"</string>
- <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"パスワード入力回数が上限を超えました"</string>
+ <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"パスワード入力回数が上限に達しました"</string>
<string name="device_ownership_relinquished" msgid="4080886992183195724">"管理者により、デバイスの個人使用が許可されました"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"管理対象のデバイス"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"このデバイスは組織によって管理され、ネットワーク トラフィックが監視される場合があります。詳しくはタップしてください。"</string>
@@ -671,8 +671,8 @@
<string name="fingerprint_error_timeout" msgid="7361192266621252164">"指紋の設定がタイムアウトしました。もう一度お試しください。"</string>
<string name="fingerprint_error_canceled" msgid="5541771463159727513">"指紋認証操作がキャンセルされました"</string>
<string name="fingerprint_error_user_canceled" msgid="2017941773466506863">"指紋認証操作がユーザーによりキャンセルされました"</string>
- <string name="fingerprint_error_lockout" msgid="6626753679019351368">"試行回数が上限を超えました。代わりに画面ロックを使用してください。"</string>
- <string name="fingerprint_error_lockout_permanent" msgid="9060651300306264843">"試行回数が上限を超えました。代わりに画面ロックを使用してください。"</string>
+ <string name="fingerprint_error_lockout" msgid="6626753679019351368">"試行回数が上限に達しました。代わりに画面ロックを使用してください。"</string>
+ <string name="fingerprint_error_lockout_permanent" msgid="9060651300306264843">"試行回数が上限に達しました。代わりに画面ロックを使用してください。"</string>
<string name="fingerprint_error_unable_to_process" msgid="2446280592818621224">"指紋を処理できません。もう一度お試しください。"</string>
<string name="fingerprint_error_no_fingerprints" msgid="3144806556204061862">"指紋が登録されていません"</string>
<string name="fingerprint_error_hw_not_present" msgid="5898827259419366359">"このデバイスには指紋認証センサーがありません"</string>
@@ -734,8 +734,8 @@
<string name="face_error_canceled" msgid="2164434737103802131">"顔の操作をキャンセルしました。"</string>
<string name="face_error_user_canceled" msgid="5766472033202928373">"顔認証はユーザーによりキャンセルされました"</string>
<string name="face_error_lockout" msgid="7864408714994529437">"試行回数の上限です。後でもう一度お試しください。"</string>
- <string name="face_error_lockout_permanent" msgid="8533257333130473422">"試行回数が上限を超えました。顔認証を利用できません。"</string>
- <string name="face_error_lockout_screen_lock" msgid="5062609811636860928">"試行回数が上限を超えました。代わりに画面ロック解除を入力してください。"</string>
+ <string name="face_error_lockout_permanent" msgid="8533257333130473422">"試行回数が上限に達しました。顔認証を利用できません。"</string>
+ <string name="face_error_lockout_screen_lock" msgid="5062609811636860928">"試行回数が上限に達しました。代わりに画面ロック解除を入力してください。"</string>
<string name="face_error_unable_to_process" msgid="5723292697366130070">"顔を確認できません。もう一度お試しください。"</string>
<string name="face_error_not_enrolled" msgid="1134739108536328412">"顔認証を設定していません"</string>
<string name="face_error_hw_not_present" msgid="7940978724978763011">"このデバイスは顔認証に対応していません"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 7a2528d..d1da74e 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -995,7 +995,7 @@
<string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Дұрыс!"</string>
<string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Қайталап көріңіз"</string>
<string name="lockscreen_password_wrong" msgid="8605355913868947490">"Қайталап көріңіз"</string>
- <string name="lockscreen_storage_locked" msgid="634993789186443380">"Мүмкіндіктер мен деректер үшін құлыпты ашыңыз"</string>
+ <string name="lockscreen_storage_locked" msgid="634993789186443380">"Барлық функция мен дерек үшін құлыпты ашыңыз"</string>
<string name="faceunlock_multiple_failures" msgid="681991538434031708">"Бет тану арқылы ашу әрекеттері анықталған шегінен асып кетті"</string>
<string name="lockscreen_missing_sim_message_short" msgid="1229301273156907613">"SIM картасы жоқ."</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="3986843848305639161">"Планшетте SIM картасы жоқ."</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index a0afccf..9b2b6ab 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -995,7 +995,7 @@
<string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Точно!"</string>
<string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Обидете се повторно"</string>
<string name="lockscreen_password_wrong" msgid="8605355913868947490">"Обидете се повторно"</string>
- <string name="lockscreen_storage_locked" msgid="634993789186443380">"Отклучи за сите функции и податоци"</string>
+ <string name="lockscreen_storage_locked" msgid="634993789186443380">"Отклучете за пристап до сите функции и податоци"</string>
<string name="faceunlock_multiple_failures" msgid="681991538434031708">"Максималниот број обиди на отклучување со лик е надминат"</string>
<string name="lockscreen_missing_sim_message_short" msgid="1229301273156907613">"Нема SIM-картичка"</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="3986843848305639161">"Нема SIM-картичка во таблетот."</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index e8b1b88..9d72fcd 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -615,7 +615,7 @@
<string name="permdesc_disableKeyguard" msgid="3223710003098573038">"कीलॉक आणि कोणतीही संबद्ध पासवर्ड सुरक्षितता अक्षम करण्यासाठी अॅप ला अनुमती देते. उदाहरणार्थ, येणारा फोन कॉल प्राप्त करताना फोन कीलॉक अक्षम करतो, नंतर जेव्हा कॉल समाप्त होतो तेव्हा तो कीलॉक पुन्हा-सक्षम करतो."</string>
<string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"स्क्रीन लॉक क्लिष्टतेची विनंती करा"</string>
<string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"अॅपला स्क्रीन लॉक क्लिष्टता पातळी (उच्च, मध्यम, खालची किंवा काहीही नाही) जाणून घेऊ देते, जी लांबीची संभाव्य रेंज आणि स्क्रीन लॉकचा प्रकार सूचित करते. अॅप वापरकर्त्यांना असेदेखील सुचवू शकते की त्यांनी स्क्रीन लॉक ठरावीक पातळीपर्यंत अपडेट करावे, परंतु वापरकर्ते त्याकडे मोकळेपणाने दुर्लक्ष करू शकतात आणि तेथून नेव्हिगेट करू शकतात. स्क्रीन लॉक प्लेनटेक्स्टमध्ये स्टोअर केले जात नसल्यामुळे अॅपला नेमका पासवर्ड माहीत नसतो याची नोंद घ्या."</string>
- <string name="permlab_postNotification" msgid="4875401198597803658">"सूचना दाखवा"</string>
+ <string name="permlab_postNotification" msgid="4875401198597803658">"नोटिफिकेशन दाखवा"</string>
<string name="permdesc_postNotification" msgid="5974977162462877075">"ॲपला सूचना दाखवू देते"</string>
<string name="permlab_turnScreenOn" msgid="219344053664171492">"स्क्रीन सुरू करा"</string>
<string name="permdesc_turnScreenOn" msgid="4394606875897601559">"अॅपला स्क्रीन सुरू करण्याची परवानगी देते."</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index add32bb..674eac3 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -706,10 +706,10 @@
<string name="face_acquired_too_far" msgid="2922278214231064859">"Dekatkan telefon"</string>
<string name="face_acquired_too_high" msgid="8278815780046368576">"Tinggikan lagi telefon"</string>
<string name="face_acquired_too_low" msgid="4075391872960840081">"Rendahkan lagi telefon"</string>
- <string name="face_acquired_too_right" msgid="6245286514593540859">"Gerakkan telefon ke kiri anda"</string>
- <string name="face_acquired_too_left" msgid="9201762240918405486">"Gerakkan telefon ke kanan anda"</string>
+ <string name="face_acquired_too_right" msgid="6245286514593540859">"Gerakkan telefon ke kiri"</string>
+ <string name="face_acquired_too_left" msgid="9201762240918405486">"Gerakkan telefon ke kanan"</string>
<string name="face_acquired_poor_gaze" msgid="4427153558773628020">"Sila lihat terus pada peranti anda."</string>
- <string name="face_acquired_not_detected" msgid="1057966913397548150">"Wajah tidak kelihatan. Pegang telefon pada paras mata."</string>
+ <string name="face_acquired_not_detected" msgid="1057966913397548150">"Wajah tidak kelihatan. Pegang telefon pada aras mata."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Terlalu bnyk gerakan. Pegang telefon dgn stabil."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Sila daftarkan semula wajah anda."</string>
<string name="face_acquired_too_different" msgid="4505278456634706967">"Wajah tidak dikenali. Cuba lagi."</string>
@@ -1030,7 +1030,7 @@
<string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="8682445539263683414">"Anda telah mencuba untuk membuka kunci tablet secara salah sebanyak <xliff:g id="NUMBER">%d</xliff:g> kali. Tablet kini akan ditetapkan semula ke tetapan lalai kilang."</string>
<string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"Anda telah cuba membuka peranti Android TV secara salah sebanyak <xliff:g id="NUMBER">%d</xliff:g> kali. Peranti Android TV anda kini akan ditetapkan semula kepada tetapan lalai kilang."</string>
<string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"Anda telah mencuba untuk membuka kunci telefon secara salah sebanyak <xliff:g id="NUMBER">%d</xliff:g> kali. Telefon kini akan ditetapkan semula kepada tetapan lalai kilang."</string>
- <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"Cuba lagi dalam <xliff:g id="NUMBER">%d</xliff:g> saat."</string>
+ <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"Cuba lagi selepas <xliff:g id="NUMBER">%d</xliff:g> saat."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Lupa corak?"</string>
<string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"Buka kunci akaun"</string>
<string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"Terlalu banyak percubaan melukis corak"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 3de8fb5..030633b 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1894,7 +1894,7 @@
<string name="clone_profile_label_badge" msgid="1871997694718793964">"<xliff:g id="LABEL">%1$s</xliff:g> ပုံတူပွား"</string>
<string name="private_profile_label_badge" msgid="1712086003787839183">"သီးသန့် <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"ပင်မဖြုတ်မီမှာ PIN ကို မေးကြည့်ရန်"</string>
- <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ပင်မဖြုတ်မီမှာ သော့ဖွင့် ရေးဆွဲမှုပုံစံကို မေးကြည့်ရန်"</string>
+ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ပင်မဖြုတ်မီ လော့ခ်ဖွင့်ပုံစံကို မေးရန်"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ပင်မဖြုတ်မီမှာ စကားဝှက်ကို မေးကြည့်ရန်"</string>
<string name="package_installed_device_owner" msgid="7035926868974878525">"သင်၏ စီမံခန့်ခွဲသူက ထည့်သွင်းထားသည်"</string>
<string name="package_updated_device_owner" msgid="7560272363805506941">"သင်၏ စီမံခန့်ခွဲသူက အပ်ဒိတ်လုပ်ထားသည်"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 0981623..d484405 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -725,7 +725,7 @@
<skip />
<string name="face_acquired_recalibrate_alt" msgid="5702674220280332115">"Kan gezichtsmodel niet maken. Probeer het opnieuw."</string>
<string name="face_acquired_dark_glasses_detected_alt" msgid="4052123776406041972">"Donkere bril waargenomen. Je gezicht moet helemaal zichtbaar zijn."</string>
- <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"Gezichtsbedekking waargenomen. Je hele gezicht moet zichtbaar zijn."</string>
+ <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"Gezichtsbedekking waargenomen. Je gezicht moet helemaal zichtbaar zijn."</string>
<string-array name="face_acquired_vendor">
</string-array>
<string name="face_error_hw_not_available" msgid="5085202213036026288">"Kan gezicht niet verifiëren. Hardware niet beschikbaar."</string>
@@ -1894,7 +1894,7 @@
<string name="clone_profile_label_badge" msgid="1871997694718793964">"Kloon van <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="private_profile_label_badge" msgid="1712086003787839183">"Privé <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"Vraag pin voor losmaken"</string>
- <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Vraag om ontgrendelingspatroon voor losmaken"</string>
+ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Ontgrendelingspatroon vragen om app los te maken"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Vraag wachtwoord voor losmaken"</string>
<string name="package_installed_device_owner" msgid="7035926868974878525">"Geïnstalleerd door je beheerder"</string>
<string name="package_updated_device_owner" msgid="7560272363805506941">"Geüpdatet door je beheerder"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 8b49e6c..df78cc9 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -995,7 +995,7 @@
<string name="lockscreen_pattern_correct" msgid="8050630103651508582">"ଠିକ୍!"</string>
<string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
<string name="lockscreen_password_wrong" msgid="8605355913868947490">"ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
- <string name="lockscreen_storage_locked" msgid="634993789186443380">"ସମସ୍ତ ସୁବିଧା ତଥା ଡାଟା ପାଇଁ ଅନଲକ୍ କରନ୍ତୁ"</string>
+ <string name="lockscreen_storage_locked" msgid="634993789186443380">"ସବୁ ଫିଚର ଓ ଡାଟା ପାଇଁ ଅନଲକ କରନ୍ତୁ"</string>
<string name="faceunlock_multiple_failures" msgid="681991538434031708">"ସର୍ବାଧିକ ଫେସ୍ ଅନଲକ୍ ପ୍ରଚେଷ୍ଟା ଅତିକ୍ରମ କରିଛି"</string>
<string name="lockscreen_missing_sim_message_short" msgid="1229301273156907613">"କୌଣସି SIM ନାହିଁ"</string>
<string name="lockscreen_missing_sim_message" product="tablet" msgid="3986843848305639161">"ଟାବଲେଟରେ କୌଣସି SIM ନାହିଁ।"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 4701915..f80e832 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -649,11 +649,11 @@
<string name="biometric_error_generic" msgid="6784371929985434439">"Podczas uwierzytelniania wystąpił błąd"</string>
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Używaj blokady ekranu"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Użyj blokady ekranu, aby kontynuować"</string>
- <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Mocno naciśnij czujnik"</string>
+ <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Mocno naciśnij czytnik"</string>
<string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Nie rozpoznano odcisku palca. Spróbuj ponownie."</string>
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Wyczyść czytnik linii papilarnych i spróbuj ponownie"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Wyczyść czujnik i spróbuj ponownie"</string>
- <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Mocno naciśnij czujnik"</string>
+ <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Mocno naciśnij czytnik"</string>
<string name="fingerprint_acquired_too_slow" msgid="6683510291554497580">"Palec został obrócony zbyt wolno. Spróbuj ponownie."</string>
<string name="fingerprint_acquired_already_enrolled" msgid="2285166003936206785">"Użyj odcisku innego palca"</string>
<string name="fingerprint_acquired_too_bright" msgid="3863560181670915607">"Zbyt jasno"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 73121ce..3dcd619 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -726,7 +726,7 @@
<skip />
<string name="face_acquired_recalibrate_alt" msgid="5702674220280332115">"Falha ao criar o modelo de rosto. Tente de novo."</string>
<string name="face_acquired_dark_glasses_detected_alt" msgid="4052123776406041972">"Óculos escuros detectados. Seu rosto precisa estar completamente visível."</string>
- <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"Máscara detectada. Seu rosto precisa estar visível."</string>
+ <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"Tem algo cobrindo seu rosto. Ele precisa estar visível."</string>
<string-array name="face_acquired_vendor">
</string-array>
<string name="face_error_hw_not_available" msgid="5085202213036026288">"Impossível verificar rosto. Hardware indisponível."</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 73121ce..3dcd619 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -726,7 +726,7 @@
<skip />
<string name="face_acquired_recalibrate_alt" msgid="5702674220280332115">"Falha ao criar o modelo de rosto. Tente de novo."</string>
<string name="face_acquired_dark_glasses_detected_alt" msgid="4052123776406041972">"Óculos escuros detectados. Seu rosto precisa estar completamente visível."</string>
- <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"Máscara detectada. Seu rosto precisa estar visível."</string>
+ <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"Tem algo cobrindo seu rosto. Ele precisa estar visível."</string>
<string-array name="face_acquired_vendor">
</string-array>
<string name="face_error_hw_not_available" msgid="5085202213036026288">"Impossível verificar rosto. Hardware indisponível."</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index a92a580..a1da51e 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -653,7 +653,7 @@
<string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Отпечаток пальца не распознан. Повторите попытку."</string>
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Очистите сканер отпечатков пальцев и повторите попытку."</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Очистите сканер и повторите попытку."</string>
- <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Плотно прижмите палец к сканеру."</string>
+ <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Плотно прижмите палец к сканеру"</string>
<string name="fingerprint_acquired_too_slow" msgid="6683510291554497580">"Вы перемещали палец слишком медленно. Повторите попытку."</string>
<string name="fingerprint_acquired_already_enrolled" msgid="2285166003936206785">"Попробуйте сохранить отпечаток другого пальца."</string>
<string name="fingerprint_acquired_too_bright" msgid="3863560181670915607">"Слишком светло."</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 8dc68ca..4735866 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -727,7 +727,7 @@
<skip />
<string name="face_acquired_recalibrate_alt" msgid="5702674220280332115">"Model tváre sa nedá vytvoriť. Skúste to znova."</string>
<string name="face_acquired_dark_glasses_detected_alt" msgid="4052123776406041972">"Boli rozpoznané tmavé okuliare. Musí vám byť vidieť celú tvár."</string>
- <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"Bolo rozpoznané rúško. Musí vám byť vidieť celú tvár."</string>
+ <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"Máte čiastočne zakrytú tvár. Musí byť viditeľná celá."</string>
<string-array name="face_acquired_vendor">
</string-array>
<string name="face_error_hw_not_available" msgid="5085202213036026288">"Tvár sa nedá overiť. Hardvér nie je k dispozícii."</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 870f52e..02e06fa 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -725,7 +725,7 @@
<skip />
<string name="face_acquired_recalibrate_alt" msgid="5702674220280332115">"முகத் தோற்றம் பதிவாகவில்லை. மீண்டும் முயலவும்."</string>
<string name="face_acquired_dark_glasses_detected_alt" msgid="4052123776406041972">"அடர் நிறக் கண்ணாடிகள் கண்டறியப்பட்டுள்ளது. உங்கள் முகத்தை முழுமையாகக் காட்டவும்."</string>
- <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"முகம் மறைக்கப்பட்டுள்ளது. உங்கள் முகத்தை முழுமையாகக் காட்டவும்."</string>
+ <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"முகத்தை மறைக்காமல் முழுமையாகக் காட்டவும்."</string>
<string-array name="face_acquired_vendor">
</string-array>
<string name="face_error_hw_not_available" msgid="5085202213036026288">"முகத்தைச் சரிபார்க்க இயலவில்லை. வன்பொருள் இல்லை."</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 3547870..a6a6b50 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -709,7 +709,7 @@
<string name="face_acquired_too_right" msgid="6245286514593540859">"Iusog pakaliwa ang telepono mo"</string>
<string name="face_acquired_too_left" msgid="9201762240918405486">"Iusog pakanan ang telepono mo"</string>
<string name="face_acquired_poor_gaze" msgid="4427153558773628020">"Tumingin nang mas direkta sa iyong device."</string>
- <string name="face_acquired_not_detected" msgid="1057966913397548150">"Hindi makita ang mukha mo. Hawakan ang telepono kapantay ng mata."</string>
+ <string name="face_acquired_not_detected" msgid="1057966913397548150">"Hindi makita ang mukha mo. Ipantay ang telepono sa mata."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Masyadong magalaw. Hawakang mabuti ang telepono."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Paki-enroll muli ang iyong mukha."</string>
<string name="face_acquired_too_different" msgid="4505278456634706967">"Hindi nakilala ang mukha. Subukan ulit."</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 2019249..9336c17 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1894,7 +1894,7 @@
<string name="clone_profile_label_badge" msgid="1871997694718793964">"<xliff:g id="LABEL">%1$s</xliff:g>克隆"</string>
<string name="private_profile_label_badge" msgid="1712086003787839183">"私人“<xliff:g id="LABEL">%1$s</xliff:g>”"</string>
<string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"取消时要求输入PIN码"</string>
- <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"取消时要求绘制解锁图案"</string>
+ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"取消固定前要求绘制解锁图案"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"取消时要求输入密码"</string>
<string name="package_installed_device_owner" msgid="7035926868974878525">"已由您的管理员安装"</string>
<string name="package_updated_device_owner" msgid="7560272363805506941">"已由您的管理员更新"</string>
@@ -1977,7 +1977,7 @@
<string name="language_selection_title" msgid="52674936078683285">"添加语言"</string>
<string name="country_selection_title" msgid="5221495687299014379">"地区偏好设置"</string>
<string name="search_language_hint" msgid="7004225294308793583">"输入语言名称"</string>
- <string name="language_picker_section_suggested" msgid="6556199184638990447">"建议语言"</string>
+ <string name="language_picker_section_suggested" msgid="6556199184638990447">"建议的语言"</string>
<string name="language_picker_regions_section_suggested" msgid="6080131515268225316">"推荐地区"</string>
<string name="language_picker_section_suggested_bilingual" msgid="5932198319583556613">"建议的语言"</string>
<string name="region_picker_section_suggested_bilingual" msgid="704607569328224133">"建议的地区"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index b262ebd..0df49dc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2300,7 +2300,7 @@
ensure that the status bar has enough contrast with the contents of this app, and set
an appropriate effective bar background accordingly.
See: {@link android.R.attr#enforceStatusBarContrast}
- <p>If the app targets
+ <p>If the window belongs to an app targeting
{@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
this attribute is ignored.
@deprecated Draw proper background behind
@@ -2320,7 +2320,7 @@
ensure that the navigation bar has enough contrast with the contents of this app, and
set an appropriate effective bar background accordingly.
See: {@link android.R.attr#enforceNavigationBarContrast}
- <p>If the app targets
+ <p>If the window belongs to an app targeting
{@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
this attribute is ignored.
@deprecated Draw proper background behind
@@ -2335,7 +2335,7 @@
have been requested to be translucent with
{@link android.R.attr#windowTranslucentNavigation}.
Corresponds to {@link android.view.Window#setNavigationBarDividerColor(int)}.
- <p>If the app targets
+ <p>If the window belongs to an app targeting
{@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
this attribute is ignored.
@deprecated Draw proper background behind
@@ -2431,7 +2431,9 @@
<!-- Controls how the window is laid out if there is a {@code DisplayCutout}.
<p>
- Defaults to {@code default}.
+ Defaults to {@code default}. But if the window fills the screen, and it belongs to an app
+ targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or
+ above, the behavior will be the same as specifying {@code always} regardless.
<p>
See also
{@link android.view.WindowManager.LayoutParams#layoutInDisplayCutoutMode
@@ -2528,8 +2530,8 @@
<!-- Flag indicating whether this window would opt-out the edge-to-edge enforcement.
- <p>If this is false, the edge-to-edge enforcement will be applied to the window if its
- app targets
+ <p>If this is false, the edge-to-edge enforcement will be applied to the window if it
+ belongs to an app targeting
{@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above.
The affected behaviors are:
<ul>
@@ -2537,9 +2539,8 @@
through the {@link android.view.WindowInsets} to the content view, as if calling
{@link android.view.Window#setDecorFitsSystemWindows(boolean)} with false.
<li>{@link android.view.WindowManager.LayoutParams#layoutInDisplayCutoutMode} of
- the non-floating windows will be set to {@link
+ the fill-screen windows will behave as specifying {@link
android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS}.
- Changing it to other values will cause {@link lang.IllegalArgumentException}.
<li>The framework will set {@link android.R.attr#statusBarColor},
{@link android.R.attr#navigationBarColor}, and
{@link android.R.attr#navigationBarDividerColor} to transparent.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a622d36..1a61870 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3009,6 +3009,10 @@
<!-- Maximum number of users we allow to be running at a time -->
<integer name="config_multiuserMaxRunningUsers">3</integer>
+ <!-- Number of seconds of uptime after a full user enters the background before we attempt to
+ stop it due to inactivity. Set to -1 to disable scheduling stopping background users. -->
+ <integer name="config_backgroundUserScheduledStopTimeSecs">1800</integer> <!-- 30 minutes -->
+
<!-- Whether to delay user data locking for background user.
If false, user switched-out from user switching will still be in running state until
config_multiuserMaxRunningUsers is reached. Once config_multiuserMaxRunningUsers is
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 7c9f2ef..a248ede 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -235,6 +235,12 @@
<integer name="config_esim_bootstrap_data_limit_bytes">-1</integer>
<java-symbol type="integer" name="config_esim_bootstrap_data_limit_bytes" />
+ <!-- Reevaluate existing data networks for bootstrap sim data usage at mentioned intervals
+ during esim bootstrap activation. If the value set is 0 or -1, default interval of
+ 60000 millis will be set. -->
+ <integer name="config_reevaluate_bootstrap_sim_data_usage_millis">60000</integer>
+ <java-symbol type="integer" name="config_reevaluate_bootstrap_sim_data_usage_millis" />
+
<!-- Telephony config for the PLMNs of all satellite providers. This is used by satellite modem
to identify providers that should be ignored if the carrier config
carrier_supported_satellite_services_per_provider_bundle does not support them.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f3aa27f..4c941fd 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1888,7 +1888,7 @@
<!-- Message shown when UDFPS fails to match -->
<string name="fingerprint_udfps_error_not_match">Fingerprint not recognized</string>
<!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
- <string name="fingerprint_dialog_use_fingerprint_instead">Can\u2019t recognize face. Use fingerprint instead.</string>
+ <string name="fingerprint_dialog_use_fingerprint_instead">Face not recognized. Use fingerprint instead.</string>
<!-- Accessibility message announced when a fingerprint has been authenticated [CHAR LIMIT=NONE] -->
<string name="fingerprint_authenticated">Fingerprint authenticated</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c4033f2d..ead5827 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -488,6 +488,7 @@
<java-symbol type="integer" name="config_lockSoundVolumeDb" />
<java-symbol type="integer" name="config_multiuserMaximumUsers" />
<java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
+ <java-symbol type="integer" name="config_backgroundUserScheduledStopTimeSecs" />
<java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
<java-symbol type="bool" name="config_multiuserVisibleBackgroundUsers" />
<java-symbol type="bool" name="config_multiuserVisibleBackgroundUsersOnDefaultDisplay" />
@@ -1527,7 +1528,6 @@
<java-symbol type="layout" name="number_picker" />
<java-symbol type="layout" name="permissions_package_list_item" />
<java-symbol type="layout" name="popup_menu_item_layout" />
- <java-symbol type="layout" name="popup_menu_item_layout_material" />
<java-symbol type="layout" name="popup_menu_header_item_layout" />
<java-symbol type="layout" name="remote_views_adapter_default_loading_view" />
<java-symbol type="layout" name="search_bar" />
diff --git a/core/tests/FileSystemUtilsTest/TEST_MAPPING b/core/tests/FileSystemUtilsTest/TEST_MAPPING
new file mode 100644
index 0000000..d41e981
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "FileSystemUtilsTests"
+ }
+ ]
+}
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 404e873..04e90ba 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -154,6 +154,12 @@
"android.test.runner",
"org.apache.http.legacy",
],
+ uses_libs: [
+ "android.test.runner",
+ ],
+ optional_uses_libs: [
+ "org.apache.http.legacy",
+ ],
sdk_version: "core_platform",
}
diff --git a/core/tests/coretests/src/android/app/ActivityManagerTest.java b/core/tests/coretests/src/android/app/ActivityManagerTest.java
index d930e4d..3c042ba 100644
--- a/core/tests/coretests/src/android/app/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/ActivityManagerTest.java
@@ -78,6 +78,6 @@
public void testRestrictionLevel() throws Exception {
// For the moment mostly want to confirm we don't crash
assertNotNull(ActivityManager.restrictionLevelToName(
- ActivityManager.RESTRICTION_LEVEL_HIBERNATION));
+ ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED));
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 8506905..f8c2d6a 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -23,15 +23,19 @@
import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.app.Activity;
+import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -45,6 +49,7 @@
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.DisplayInfo;
import android.window.ActivityWindowInfo;
+import android.window.WindowTokenClient;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -55,6 +60,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -180,4 +186,36 @@
verify(mActivityWindowInfoListener, never()).accept(any(), any());
}
+
+ @Test
+ public void testWindowTokenClient_onConfigurationChanged() {
+ doNothing().when(mController).onContextConfigurationPreChanged(any());
+ doNothing().when(mController).onContextConfigurationPostChanged(any());
+
+ final WindowTokenClient windowTokenClient = spy(new WindowTokenClient());
+ final Context context = mock(Context.class);
+ windowTokenClient.attachContext(context);
+
+ doReturn(mController).when(windowTokenClient).getClientTransactionListenerController();
+ doNothing().when(windowTokenClient).onConfigurationChangedInner(any(), any(), anyInt(),
+ anyBoolean());
+
+ // Not trigger when shouldReportConfigChange is false.
+ windowTokenClient.onConfigurationChanged(mConfiguration, 123 /* newDisplayId */,
+ false /* shouldReportConfigChange*/);
+
+ verify(mController, never()).onContextConfigurationPreChanged(any());
+ verify(mController, never()).onContextConfigurationPostChanged(any());
+
+ // Trigger in order when shouldReportConfigChange is true.
+ clearInvocations(windowTokenClient);
+ final InOrder inOrder = inOrder(mController, windowTokenClient);
+ windowTokenClient.onConfigurationChanged(mConfiguration, 123 /* newDisplayId */,
+ true /* shouldReportConfigChange*/);
+
+ inOrder.verify(mController).onContextConfigurationPreChanged(context);
+ inOrder.verify(windowTokenClient).onConfigurationChangedInner(context, mConfiguration,
+ 123 /* newDisplayId */, true /* shouldReportConfigChange*/);
+ inOrder.verify(mController).onContextConfigurationPostChanged(context);
+ }
}
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 3a872b5..5bf88da 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -28,7 +28,9 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.eq;
+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;
@@ -97,7 +99,7 @@
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
- when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+ when(mContext.getMainThreadHandler()).thenReturn(mHandler);
when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME);
when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG);
when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
@@ -210,6 +212,39 @@
verify(mFaceDetectionCallback).onDetectionError(anyInt());
}
+ @Test
+ public void authenticate_onErrorCanceled() throws RemoteException {
+ final FaceManager.AuthenticationCallback authenticationCallback1 = mock(
+ FaceManager.AuthenticationCallback.class);
+ final FaceManager.AuthenticationCallback authenticationCallback2 = mock(
+ FaceManager.AuthenticationCallback.class);
+
+ final ArgumentCaptor<IFaceServiceReceiver> faceServiceReceiverArgumentCaptor =
+ ArgumentCaptor.forClass(IFaceServiceReceiver.class);
+
+ mFaceManager.authenticate(null, new CancellationSignal(),
+ authenticationCallback1, mHandler,
+ new FaceAuthenticateOptions.Builder().build());
+ mFaceManager.authenticate(null, new CancellationSignal(),
+ authenticationCallback2, mHandler,
+ new FaceAuthenticateOptions.Builder().build());
+
+ verify(mService, times(2)).authenticate(any(IBinder.class), eq(0L),
+ faceServiceReceiverArgumentCaptor.capture(), any());
+
+ final List<IFaceServiceReceiver> faceServiceReceivers =
+ faceServiceReceiverArgumentCaptor.getAllValues();
+ faceServiceReceivers.get(0).onError(5 /* error */, 0 /* vendorCode */);
+ mLooper.dispatchAll();
+
+ verify(authenticationCallback1).onAuthenticationError(eq(5), anyString());
+ verify(authenticationCallback2, never()).onAuthenticationError(anyInt(), anyString());
+
+ faceServiceReceivers.get(1).onError(5 /* error */, 0 /* vendorCode */);
+ mLooper.dispatchAll();
+ verify(authenticationCallback2).onAuthenticationError(eq(5), anyString());
+ }
+
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 ce7d6a9..c3ea7d3 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
@@ -26,7 +26,9 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+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;
@@ -93,6 +95,7 @@
mHandler = new Handler(mLooper.getLooper());
when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+ when(mContext.getMainThreadHandler()).thenReturn(mHandler);
when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME);
when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG);
when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
@@ -187,4 +190,38 @@
verify(mFingerprintDetectionCallback).onDetectionError(anyInt());
}
+
+ @Test
+ public void authenticate_onErrorCanceled() throws RemoteException {
+ final FingerprintManager.AuthenticationCallback authenticationCallback1 = mock(
+ FingerprintManager.AuthenticationCallback.class);
+ final FingerprintManager.AuthenticationCallback authenticationCallback2 = mock(
+ FingerprintManager.AuthenticationCallback.class);
+
+ final ArgumentCaptor<IFingerprintServiceReceiver> fingerprintServiceReceiverArgumentCaptor =
+ ArgumentCaptor.forClass(IFingerprintServiceReceiver.class);
+
+ mFingerprintManager.authenticate(null, new CancellationSignal(),
+ authenticationCallback1, mHandler,
+ new FingerprintAuthenticateOptions.Builder().build());
+ mFingerprintManager.authenticate(null, new CancellationSignal(),
+ authenticationCallback2, mHandler,
+ new FingerprintAuthenticateOptions.Builder().build());
+
+ verify(mService, times(2)).authenticate(any(IBinder.class), eq(0L),
+ fingerprintServiceReceiverArgumentCaptor.capture(), any());
+
+ final List<IFingerprintServiceReceiver> fingerprintServiceReceivers =
+ fingerprintServiceReceiverArgumentCaptor.getAllValues();
+ fingerprintServiceReceivers.get(0).onError(5 /* error */, 0 /* vendorCode */);
+ mLooper.dispatchAll();
+
+ verify(authenticationCallback1).onAuthenticationError(eq(5), anyString());
+ verify(authenticationCallback2, never()).onAuthenticationError(anyInt(), anyString());
+
+ fingerprintServiceReceivers.get(1).onError(5 /* error */, 0 /* vendorCode */);
+ mLooper.dispatchAll();
+
+ verify(authenticationCallback2).onAuthenticationError(eq(5), anyString());
+ }
}
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index 28343f1..0bf9a4c 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -203,7 +203,9 @@
mActivityRule.runOnUiThread(() -> {
mMovingView.setFrameContentVelocity(1_000_000_000f);
mMovingView.invalidate();
- runAfterDraw(() -> assertEquals(140f, mViewRoot.getLastPreferredFrameRate(), 0f));
+ runAfterDraw(() -> {
+ assertEquals(140f, mViewRoot.getLastPreferredFrameRate(), 0f);
+ });
});
waitForAfterDraw();
}
@@ -411,6 +413,26 @@
waitForAfterDraw();
}
+ @Test
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_VELOCITY_MAPPING_READ_ONLY
+ })
+ public void frameRateAndCategory() throws Throwable {
+ waitForFrameRateCategoryToSettle();
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW);
+ mMovingView.setFrameContentVelocity(1f);
+ mMovingView.invalidate();
+ runAfterDraw(() -> {
+ assertEquals(FRAME_RATE_CATEGORY_LOW,
+ mViewRoot.getLastPreferredFrameRateCategory());
+ assertEquals(60f, mViewRoot.getLastPreferredFrameRate());
+ });
+ });
+ waitForAfterDraw();
+ }
+
private void runAfterDraw(@NonNull Runnable runnable) {
Handler handler = new Handler(Looper.getMainLooper());
mAfterDrawLatch = new CountDownLatch(1);
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index e6e7e8c..ccebd03 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -22,6 +22,7 @@
import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY;
import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY;
import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
@@ -51,6 +52,7 @@
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -495,13 +497,13 @@
@UiThreadTest
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_getDefaultValues() {
ViewRootImpl viewRootImpl = new ViewRootImpl(sContext,
sContext.getDisplayNoVerify());
- assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
- assertEquals(viewRootImpl.getLastPreferredFrameRate(), 0, 0.1);
+ assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+ viewRootImpl.getLastPreferredFrameRateCategory());
+ assertEquals(0, viewRootImpl.getLastPreferredFrameRate(), 0.1);
}
/**
@@ -513,7 +515,7 @@
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_voteFrameRateCategory_visibility_bySize() throws Throwable {
mView = new View(sContext);
attachViewToWindow(mView);
@@ -538,7 +540,7 @@
sInstrumentation.waitForIdleSync();
sInstrumentation.runOnMainSync(() -> {
- assertEquals(mViewRootImpl.getIsFrameRateBoosting(), true);
+ assertTrue(mViewRootImpl.getIsFrameRateBoosting());
});
}
@@ -550,7 +552,7 @@
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_voteFrameRateCategory_smallSize_bySize() throws Throwable {
mView = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
@@ -568,8 +570,8 @@
waitForFrameRateCategoryToSettle(mView);
sInstrumentation.runOnMainSync(() -> {
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_LOW));
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_LOW,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
}
@@ -582,7 +584,7 @@
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_voteFrameRateCategory_normalSize_bySize() throws Throwable {
mView = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
@@ -655,7 +657,7 @@
sInstrumentation.waitForIdleSync();
sInstrumentation.runOnMainSync(() -> {
- assertEquals(mViewRootImpl.getIsFrameRateBoosting(), true);
+ assertTrue(mViewRootImpl.getIsFrameRateBoosting());
});
waitForFrameRateCategoryToSettle(mView);
@@ -750,14 +752,14 @@
*/
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_voteFrameRateCategory_aggregate() {
View mView1 = new View(sContext);
attachViewToWindow(mView1);
ViewRootImpl viewRootImpl = mView1.getViewRootImpl();
sInstrumentation.runOnMainSync(() -> {
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+ viewRootImpl.getPreferredFrameRateCategory());
});
// reset the frame rate category counts
@@ -771,20 +773,20 @@
sInstrumentation.runOnMainSync(() -> {
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW, 0, null);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
+ assertEquals(FRAME_RATE_CATEGORY_LOW, viewRootImpl.getPreferredFrameRateCategory());
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL, 0, null);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ assertEquals(FRAME_RATE_CATEGORY_NORMAL, viewRootImpl.getPreferredFrameRateCategory());
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT, 0, null);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_HIGH_HINT);
+ assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT,
+ viewRootImpl.getPreferredFrameRateCategory());
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH, 0, null);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ assertEquals(FRAME_RATE_CATEGORY_HIGH, viewRootImpl.getPreferredFrameRateCategory());
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT, 0, null);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ assertEquals(FRAME_RATE_CATEGORY_HIGH, viewRootImpl.getPreferredFrameRateCategory());
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL, 0, null);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ assertEquals(FRAME_RATE_CATEGORY_HIGH, viewRootImpl.getPreferredFrameRateCategory());
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW, 0, null);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ assertEquals(FRAME_RATE_CATEGORY_HIGH, viewRootImpl.getPreferredFrameRateCategory());
});
}
@@ -796,55 +798,67 @@
*/
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_voteFrameRate_aggregate() {
mView = new View(sContext);
attachViewToWindow(mView);
mViewRootImpl = mView.getViewRootImpl();
sInstrumentation.runOnMainSync(() -> {
- assertEquals(mViewRootImpl.getPreferredFrameRate(), 0, 0.1);
+ assertEquals(0, mViewRootImpl.getPreferredFrameRate(), 0.1);
assertEquals(mViewRootImpl.getFrameRateCompatibility(),
FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
+ assertFalse(mViewRootImpl.isFrameRateConflicted());
mViewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_GTE);
- assertEquals(mViewRootImpl.getPreferredFrameRate(), 24, 0.1);
- assertEquals(mViewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_GTE);
- assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
+ if (toolkitFrameRateVelocityMappingReadOnly()) {
+ assertEquals(24, mViewRootImpl.getPreferredFrameRate(), 0.1);
+ assertEquals(FRAME_RATE_COMPATIBILITY_GTE,
+ mViewRootImpl.getFrameRateCompatibility());
+ assertFalse(mViewRootImpl.isFrameRateConflicted());
+ } else {
+ assertEquals(FRAME_RATE_CATEGORY_HIGH,
+ mViewRootImpl.getPreferredFrameRateCategory());
+ }
mViewRootImpl.votePreferredFrameRate(30, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- assertEquals(mViewRootImpl.getPreferredFrameRate(), 30, 0.1);
+ assertEquals(30, mViewRootImpl.getPreferredFrameRate(), 0.1);
// If there is a conflict, then set compatibility to
// FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
- assertEquals(mViewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- // Should be true since there is a conflict between 24 and 30.
- assertEquals(mViewRootImpl.isFrameRateConflicted(), true);
+ assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ mViewRootImpl.getFrameRateCompatibility());
+ if (toolkitFrameRateVelocityMappingReadOnly()) {
+ // Should be true since there is a conflict between 24 and 30.
+ assertTrue(mViewRootImpl.isFrameRateConflicted());
+ }
+
mView.invalidate();
});
sInstrumentation.waitForIdleSync();
sInstrumentation.runOnMainSync(() -> {
- assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
+ assertFalse(mViewRootImpl.isFrameRateConflicted());
mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE);
- assertEquals(mViewRootImpl.getPreferredFrameRate(), 60, 0.1);
- assertEquals(mViewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_GTE);
- assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
+ if (toolkitFrameRateVelocityMappingReadOnly()) {
+ assertEquals(60, mViewRootImpl.getPreferredFrameRate(), 0.1);
+ assertEquals(FRAME_RATE_COMPATIBILITY_GTE,
+ mViewRootImpl.getFrameRateCompatibility());
+ } else {
+ assertEquals(FRAME_RATE_CATEGORY_HIGH,
+ mViewRootImpl.getPreferredFrameRateCategory());
+ }
+ assertFalse(mViewRootImpl.isFrameRateConflicted());
mViewRootImpl.votePreferredFrameRate(120, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- assertEquals(mViewRootImpl.getPreferredFrameRate(), 120, 0.1);
- assertEquals(mViewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ assertEquals(120, mViewRootImpl.getPreferredFrameRate(), 0.1);
+ assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ mViewRootImpl.getFrameRateCompatibility());
// Should be false since 60 is a divisor of 120.
- assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
+ assertFalse(mViewRootImpl.isFrameRateConflicted());
mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE);
- assertEquals(mViewRootImpl.getPreferredFrameRate(), 120, 0.1);
+ assertEquals(120, mViewRootImpl.getPreferredFrameRate(), 0.1);
// compatibility should be remained the same (FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
// since the frame rate 60 is smaller than 120.
- assertEquals(mViewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ mViewRootImpl.getFrameRateCompatibility());
// Should be false since 60 is a divisor of 120.
- assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
-
+ assertFalse(mViewRootImpl.isFrameRateConflicted());
});
}
@@ -864,14 +878,14 @@
mViewRootImpl = mView.getViewRootImpl();
sInstrumentation.runOnMainSync(() -> {
- assertEquals(mViewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+ mViewRootImpl.getPreferredFrameRateCategory());
});
// reset the frame rate category counts
for (int i = 0; i < 5; i++) {
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
mView.invalidate();
});
sInstrumentation.waitForIdleSync();
@@ -880,24 +894,24 @@
waitForFrameRateCategoryToSettle(mView);
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_LOW);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW);
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_LOW));
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_LOW,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NORMAL);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NORMAL);
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NORMAL));
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_HIGH);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_HIGH);
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_HIGH));
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_HIGH,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
}
@@ -928,22 +942,23 @@
waitForFrameRateCategoryToSettle(mView);
sInstrumentation.runOnMainSync(() -> {
- assertEquals(mViewRootImpl.getPreferredFrameRate(), 0, 0.1);
+ assertEquals(0, mViewRootImpl.getPreferredFrameRate(), 0.1);
mView.setFrameContentVelocity(100);
mView.invalidate();
- runAfterDraw(() -> assertTrue(mViewRootImpl.getLastPreferredFrameRate() > 0));
+ runAfterDraw(() -> {
+ if (toolkitFrameRateVelocityMappingReadOnly()) {
+ assertEquals(FRAME_RATE_CATEGORY_LOW,
+ mViewRootImpl.getLastPreferredFrameRateCategory());
+ assertTrue(mViewRootImpl.getLastPreferredFrameRate() >= 60f);
+ } else {
+ assertEquals(FRAME_RATE_CATEGORY_HIGH,
+ mViewRootImpl.getLastPreferredFrameRateCategory());
+ assertEquals(0, mViewRootImpl.getLastPreferredFrameRate(), 0.1);
+ }
+ });
});
waitForAfterDraw();
sInstrumentation.waitForIdleSync();
- if (toolkitFrameRateVelocityMappingReadOnly()) {
- assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_HIGH);
- assertTrue(mViewRootImpl.getLastPreferredFrameRate() >= 60f);
- } else {
- assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_HIGH);
- assertEquals(mViewRootImpl.getLastPreferredFrameRate(), 0, 0.1);
- }
}
/**
@@ -951,7 +966,7 @@
*/
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_insetsAnimation() {
mView = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
@@ -977,8 +992,8 @@
sInstrumentation.waitForIdleSync();
sInstrumentation.runOnMainSync(() -> {
- assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_HIGH);
+ assertEquals(FRAME_RATE_CATEGORY_HIGH,
+ viewRootImpl.getLastPreferredFrameRateCategory());
});
}
@@ -988,7 +1003,7 @@
*/
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_frameRateBoostOnTouch() {
mView = new View(sContext);
attachViewToWindow(mView);
@@ -996,9 +1011,9 @@
ViewRootImpl viewRootImpl = mView.getViewRootImpl();
final WindowManager.LayoutParams attrs = viewRootImpl.mWindowAttributes;
- assertEquals(attrs.getFrameRateBoostOnTouchEnabled(), true);
- assertEquals(viewRootImpl.getFrameRateBoostOnTouchEnabled(),
- attrs.getFrameRateBoostOnTouchEnabled());
+ assertTrue(attrs.getFrameRateBoostOnTouchEnabled());
+ assertEquals(attrs.getFrameRateBoostOnTouchEnabled(),
+ viewRootImpl.getFrameRateBoostOnTouchEnabled());
sInstrumentation.runOnMainSync(() -> {
attrs.setFrameRateBoostOnTouchEnabled(false);
@@ -1008,9 +1023,9 @@
sInstrumentation.runOnMainSync(() -> {
final WindowManager.LayoutParams newAttrs = viewRootImpl.mWindowAttributes;
- assertEquals(newAttrs.getFrameRateBoostOnTouchEnabled(), false);
- assertEquals(viewRootImpl.getFrameRateBoostOnTouchEnabled(),
- newAttrs.getFrameRateBoostOnTouchEnabled());
+ assertFalse(newAttrs.getFrameRateBoostOnTouchEnabled());
+ assertEquals(newAttrs.getFrameRateBoostOnTouchEnabled(),
+ viewRootImpl.getFrameRateBoostOnTouchEnabled());
});
}
@@ -1021,7 +1036,7 @@
*/
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_voteFrameRateTimeOut() throws InterruptedException {
final long delay = 200L;
@@ -1031,27 +1046,27 @@
ViewRootImpl viewRootImpl = mView.getViewRootImpl();
sInstrumentation.runOnMainSync(() -> {
- assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
- assertEquals(viewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ assertEquals(0, viewRootImpl.getPreferredFrameRate(), 0.1);
+ assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ viewRootImpl.getFrameRateCompatibility());
+ assertFalse(viewRootImpl.isFrameRateConflicted());
viewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1);
- assertEquals(viewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ assertEquals(24, viewRootImpl.getPreferredFrameRate(), 0.1);
+ assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ viewRootImpl.getFrameRateCompatibility());
+ assertFalse(viewRootImpl.isFrameRateConflicted());
mView.invalidate();
- assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1);
- assertEquals(viewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ assertEquals(24, viewRootImpl.getPreferredFrameRate(), 0.1);
+ assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ viewRootImpl.getFrameRateCompatibility());
+ assertFalse(viewRootImpl.isFrameRateConflicted());
});
Thread.sleep(delay);
- assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
- assertEquals(viewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ assertEquals(0, viewRootImpl.getPreferredFrameRate(), 0.1);
+ assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ viewRootImpl.getFrameRateCompatibility());
+ assertFalse(viewRootImpl.isFrameRateConflicted());
}
/**
@@ -1070,15 +1085,16 @@
mViewRootImpl = mView.getViewRootImpl();
waitForFrameRateCategoryToSettle(mView);
sInstrumentation.runOnMainSync(() -> {
- assertEquals(mViewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+ mViewRootImpl.getPreferredFrameRateCategory());
mView.setRequestedFrameRate(frameRate);
mView.invalidate();
runAfterDraw(() -> {
- assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
- assertEquals(mViewRootImpl.getLastPreferredFrameRate(), frameRate, 0.1);
+ int expected = toolkitFrameRateDefaultNormalReadOnly()
+ ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
+ assertEquals(expected, mViewRootImpl.getLastPreferredFrameRateCategory());
+ assertEquals(frameRate, mViewRootImpl.getLastPreferredFrameRate(), 0.1);
});
});
waitForAfterDraw();
@@ -1086,17 +1102,17 @@
// reset the frame rate category counts
for (int i = 0; i < 5; i++) {
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
mView.invalidate();
});
sInstrumentation.waitForIdleSync();
}
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_LOW);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW);
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_LOW));
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_LOW,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
}
@@ -1146,7 +1162,7 @@
// reset the frame rate category counts
for (int i = 0; i < 5; i++) {
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
mView.invalidate();
});
sInstrumentation.waitForIdleSync();
@@ -1155,28 +1171,30 @@
// In transition from frequent update to infrequent update
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE));
+ runAfterDraw(() -> {
+ assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
+ mViewRootImpl.getLastPreferredFrameRateCategory());
+ });
});
waitForAfterDraw();
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE));
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
// Infrequent update
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NORMAL));
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
}
@@ -1186,7 +1204,7 @@
*/
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_isFrameRatePowerSavingsBalanced() {
mView = new View(sContext);
attachViewToWindow(mView);
@@ -1194,9 +1212,9 @@
ViewRootImpl viewRoot = mView.getViewRootImpl();
final WindowManager.LayoutParams attrs = viewRoot.mWindowAttributes;
- assertEquals(attrs.isFrameRatePowerSavingsBalanced(), true);
- assertEquals(viewRoot.isFrameRatePowerSavingsBalanced(),
- attrs.isFrameRatePowerSavingsBalanced());
+ assertTrue(attrs.isFrameRatePowerSavingsBalanced());
+ assertEquals(attrs.isFrameRatePowerSavingsBalanced(),
+ viewRoot.isFrameRatePowerSavingsBalanced());
sInstrumentation.runOnMainSync(() -> {
attrs.setFrameRatePowerSavingsBalanced(false);
@@ -1206,9 +1224,9 @@
sInstrumentation.runOnMainSync(() -> {
final WindowManager.LayoutParams newAttrs = viewRoot.mWindowAttributes;
- assertEquals(newAttrs.isFrameRatePowerSavingsBalanced(), false);
- assertEquals(viewRoot.isFrameRatePowerSavingsBalanced(),
- newAttrs.isFrameRatePowerSavingsBalanced());
+ assertFalse(newAttrs.isFrameRatePowerSavingsBalanced());
+ assertEquals(newAttrs.isFrameRatePowerSavingsBalanced(),
+ viewRoot.isFrameRatePowerSavingsBalanced());
});
}
@@ -1240,18 +1258,6 @@
sInstrumentation.waitForIdleSync();
mViewRootImpl = mView.getViewRootImpl();
- waitForFrameRateCategoryToSettle(mView);
-
- sInstrumentation.runOnMainSync(() -> {
- assertEquals(mViewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
- mView.invalidate();
- int expected = toolkitFrameRateDefaultNormalReadOnly()
- ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
- runAfterDraw(() -> assertEquals(expected,
- mViewRootImpl.getLastPreferredFrameRateCategory()));
- });
- waitForAfterDraw();
waitForFrameRateCategoryToSettle(mView);
@@ -1259,7 +1265,7 @@
for (int i = 0; i < 5; i++) {
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
mView.invalidate();
});
sInstrumentation.waitForIdleSync();
@@ -1267,10 +1273,10 @@
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NORMAL));
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
}
@@ -1403,7 +1409,7 @@
mViewRootImpl.dispatchInputEvent(event);
});
sInstrumentation.waitForIdleSync();
- assertEquals(mKeyReceived, shouldReceiveKey);
+ assertEquals(shouldReceiveKey, mKeyReceived);
}
private void attachViewToWindow(View view) {
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
index 82251b8..4921e4a 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
@@ -20,7 +20,6 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
@@ -82,14 +81,8 @@
createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeUnset);
installDecor();
- if ((mPhoneWindow.getAttributes().privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
- && !mPhoneWindow.isFloating()) {
- assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
- is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS));
- } else {
- assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
- is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT));
- }
+ assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
+ is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT));
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index 8d66cfc..1dbb775 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -29,6 +29,7 @@
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
import android.view.DisplayCutout;
import android.view.View;
import android.view.View.OnApplyWindowInsetsListener;
@@ -49,6 +50,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@Presubmit
public class ActionBarOverlayLayoutTest {
private static final Insets TOP_INSET_5 = Insets.of(0, 5, 0, 0);
@@ -167,10 +169,67 @@
assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, NO_CUTOUT)));
}
+ @Test
+ public void topInset_cutout_noContentOnApplyWindowInsetsListener() {
+ mLayout.setHasContentOnApplyWindowInsetsListener(false);
+ mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, CUTOUT_5));
+
+ assertThat(mContentInsetsListener.captured, nullValue());
+
+ mLayout.measure(EXACTLY_1000, EXACTLY_1000);
+
+ // Action bar height is added to the top inset
+ assertThat(mContentInsetsListener.captured, is(insetsWith(TOP_INSET_25, CUTOUT_5)));
+ }
+
+ @Test
+ public void topInset_cutout__hasContentOnApplyWindowInsetsListener() {
+ mLayout.setHasContentOnApplyWindowInsetsListener(true);
+ mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, CUTOUT_5));
+
+ assertThat(mContentInsetsListener.captured, nullValue());
+
+ mLayout.measure(EXACTLY_1000, EXACTLY_1000);
+
+ assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, NO_CUTOUT)));
+ }
+
+ @Test
+ public void topInset_noCutout_noContentOnApplyWindowInsetsListener() {
+ mLayout.setHasContentOnApplyWindowInsetsListener(false);
+ mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, NO_CUTOUT));
+
+ assertThat(mContentInsetsListener.captured, nullValue());
+
+ mLayout.measure(EXACTLY_1000, EXACTLY_1000);
+
+ // Action bar height is added to the top inset
+ assertThat(mContentInsetsListener.captured, is(insetsWith(TOP_INSET_25, NO_CUTOUT)));
+ }
+
+ @Test
+ public void topInset_noCutout__hasContentOnApplyWindowInsetsListener() {
+ mLayout.setHasContentOnApplyWindowInsetsListener(true);
+ mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, NO_CUTOUT));
+
+ assertThat(mContentInsetsListener.captured, nullValue());
+
+ mLayout.measure(EXACTLY_1000, EXACTLY_1000);
+
+ assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, NO_CUTOUT)));
+ }
+
private WindowInsets insetsWith(Insets content, DisplayCutout cutout) {
- return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null,
- false, 0, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false,
- null, null, 0, 0);
+ final Insets cutoutInsets = cutout != null
+ ? Insets.of(cutout.getSafeInsets())
+ : Insets.NONE;
+ return new WindowInsets.Builder()
+ .setSystemWindowInsets(content)
+ .setDisplayCutout(cutout)
+ .setInsets(WindowInsets.Type.displayCutout(), cutoutInsets)
+ .setInsetsIgnoringVisibility(WindowInsets.Type.displayCutout(), cutoutInsets)
+ .setVisible(WindowInsets.Type.displayCutout(), true)
+ .build();
}
private ViewGroup createViewGroupWithId(int id) {
@@ -181,14 +240,16 @@
static class TestActionBarOverlayLayout extends ActionBarOverlayLayout {
private boolean mStable;
+ private boolean mHasContentOnApplyWindowInsetsListener;
public TestActionBarOverlayLayout(Context context) {
super(context);
+ mHasContentOnApplyWindowInsetsListener = true;
}
@Override
public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) {
- if (mStable) {
+ if (mStable || !hasContentOnApplyWindowInsetsListener()) {
// Emulate the effect of makeOptionalFitsSystemWindows, because we can't do that
// without being attached to a window.
outLocalInsets.setEmpty();
@@ -202,6 +263,15 @@
setSystemUiVisibility(stable ? SYSTEM_UI_FLAG_LAYOUT_STABLE : 0);
}
+ void setHasContentOnApplyWindowInsetsListener(boolean hasListener) {
+ mHasContentOnApplyWindowInsetsListener = hasListener;
+ }
+
+ @Override
+ protected boolean hasContentOnApplyWindowInsetsListener() {
+ return mHasContentOnApplyWindowInsetsListener;
+ }
+
@Override
public int getWindowSystemUiVisibility() {
return getSystemUiVisibility();
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index bca741f..66b47dae 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -52,6 +52,7 @@
<permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.READ_PRECISE_PHONE_STATE"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
<permission name="android.permission.READ_WALLPAPER_INTERNAL"/>
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.REQUEST_NETWORK_SCORES"/>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index fc4277e..82d2381 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -594,6 +594,8 @@
<permission name="android.permission.EMERGENCY_INSTALL_PACKAGES" />
<!-- Permission required for Cts test - CtsSettingsTestCases -->
<permission name="android.permission.PREPARE_FACTORY_RESET" />
+ <!-- Permission required for CTS test - FileIntegrityManagerTest -->
+ <permission name="android.permission.SETUP_FSVERITY" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 2f2215f..d1d7c14 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -16,8 +16,6 @@
package android.security;
-import android.compat.annotation.UnsupportedAppUsage;
-
/**
* This class provides some constants and helper methods related to Android's Keystore service.
* This class was originally much larger, but its functionality was superseded by other classes.
@@ -30,11 +28,4 @@
// Used for UID field to indicate the calling UID.
public static final int UID_SELF = -1;
-
- private static final KeyStore KEY_STORE = new KeyStore();
-
- @UnsupportedAppUsage
- public static KeyStore getInstance() {
- return KEY_STORE;
- }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index b8ac191..0a5a81b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -25,8 +25,8 @@
import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
-import static androidx.window.extensions.embedding.DividerAttributes.RATIO_UNSET;
-import static androidx.window.extensions.embedding.DividerAttributes.WIDTH_UNSET;
+import static androidx.window.extensions.embedding.DividerAttributes.RATIO_SYSTEM_DEFAULT;
+import static androidx.window.extensions.embedding.DividerAttributes.WIDTH_SYSTEM_DEFAULT;
import static androidx.window.extensions.embedding.SplitAttributesHelper.isReversedLayout;
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM;
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_LEFT;
@@ -64,6 +64,9 @@
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.window.extensions.core.util.function.Consumer;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -76,12 +79,13 @@
* Manages the rendering and interaction of the divider.
*/
class DividerPresenter implements View.OnTouchListener {
+ static final float RATIO_EXPANDED_PRIMARY = 1.0f;
+ static final float RATIO_EXPANDED_SECONDARY = 0.0f;
private static final String WINDOW_NAME = "AE Divider";
private static final int VEIL_LAYER = 0;
private static final int DIVIDER_LAYER = 1;
// TODO(b/327067596) Update based on UX guidance.
- private static final Color DEFAULT_DIVIDER_COLOR = Color.valueOf(Color.BLACK);
private static final Color DEFAULT_PRIMARY_VEIL_COLOR = Color.valueOf(Color.BLACK);
private static final Color DEFAULT_SECONDARY_VEIL_COLOR = Color.valueOf(Color.GRAY);
@VisibleForTesting
@@ -162,54 +166,55 @@
return;
}
+ final SplitAttributes splitAttributes = topSplitContainer.getCurrentSplitAttributes();
+ final DividerAttributes dividerAttributes = splitAttributes.getDividerAttributes();
+
// Clean up the decor surface if DividerAttributes is null.
- final DividerAttributes dividerAttributes =
- topSplitContainer.getCurrentSplitAttributes().getDividerAttributes();
if (dividerAttributes == null) {
removeDecorSurfaceAndDivider(wct);
return;
}
- if (topSplitContainer.getCurrentSplitAttributes().getSplitType()
- instanceof SplitAttributes.SplitType.ExpandContainersSplitType) {
- // No divider is needed for ExpandContainersSplitType.
- removeDivider();
- return;
- }
+ // At this point, a divider is required.
- // Skip updating when the TFs have not been updated to match the SplitAttributes.
- if (topSplitContainer.getPrimaryContainer().getLastRequestedBounds().isEmpty()
- || topSplitContainer.getSecondaryContainer().getLastRequestedBounds()
- .isEmpty()) {
- return;
- }
-
+ // Create the decor surface if one is not available yet.
final SurfaceControl decorSurface = parentInfo.getDecorSurface();
if (decorSurface == null) {
// Clean up when the decor surface is currently unavailable.
removeDivider();
// Request to create the decor surface
- createOrMoveDecorSurface(wct, topSplitContainer.getPrimaryContainer());
+ createOrMoveDecorSurfaceLocked(wct, topSplitContainer.getPrimaryContainer());
return;
}
- // make the top primary container the owner of the decor surface.
- if (!Objects.equals(mDecorSurfaceOwner,
- topSplitContainer.getPrimaryContainer().getTaskFragmentToken())) {
- createOrMoveDecorSurface(wct, topSplitContainer.getPrimaryContainer());
+ // Update the decor surface owner if needed.
+ boolean isDraggableExpandType =
+ SplitAttributesHelper.isDraggableExpandType(splitAttributes);
+ final TaskFragmentContainer decorSurfaceOwnerContainer = isDraggableExpandType
+ ? topSplitContainer.getSecondaryContainer()
+ : topSplitContainer.getPrimaryContainer();
+
+ if (!Objects.equals(
+ mDecorSurfaceOwner, decorSurfaceOwnerContainer.getTaskFragmentToken())) {
+ createOrMoveDecorSurfaceLocked(wct, decorSurfaceOwnerContainer);
}
+ final boolean isVerticalSplit = isVerticalSplit(topSplitContainer);
+ final boolean isReversedLayout = isReversedLayout(
+ topSplitContainer.getCurrentSplitAttributes(),
+ parentInfo.getConfiguration());
updateProperties(
new Properties(
parentInfo.getConfiguration(),
dividerAttributes,
decorSurface,
- getInitialDividerPosition(topSplitContainer),
- isVerticalSplit(topSplitContainer),
- isReversedLayout(
- topSplitContainer.getCurrentSplitAttributes(),
- parentInfo.getConfiguration()),
- parentInfo.getDisplayId()));
+ getInitialDividerPosition(
+ topSplitContainer, isVerticalSplit, isReversedLayout),
+ isVerticalSplit,
+ isReversedLayout,
+ parentInfo.getDisplayId(),
+ isDraggableExpandType
+ ));
}
}
@@ -242,14 +247,21 @@
*
* See {@link TaskFragmentOperation#OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE}.
*/
- @GuardedBy("mLock")
- private void createOrMoveDecorSurface(
+ void createOrMoveDecorSurface(
@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) {
+ synchronized (mLock) {
+ createOrMoveDecorSurfaceLocked(wct, container);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void createOrMoveDecorSurfaceLocked(
+ @NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) {
+ mDecorSurfaceOwner = container.getTaskFragmentToken();
final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE)
.build();
- wct.addTaskFragmentOperation(container.getTaskFragmentToken(), operation);
- mDecorSurfaceOwner = container.getTaskFragmentToken();
+ wct.addTaskFragmentOperation(mDecorSurfaceOwner, operation);
}
@GuardedBy("mLock")
@@ -274,15 +286,28 @@
}
@VisibleForTesting
- static int getInitialDividerPosition(@NonNull SplitContainer splitContainer) {
+ static int getInitialDividerPosition(
+ @NonNull SplitContainer splitContainer,
+ boolean isVerticalSplit,
+ boolean isReversedLayout) {
final Rect primaryBounds =
splitContainer.getPrimaryContainer().getLastRequestedBounds();
final Rect secondaryBounds =
splitContainer.getSecondaryContainer().getLastRequestedBounds();
- if (isVerticalSplit(splitContainer)) {
- return Math.min(primaryBounds.right, secondaryBounds.right);
+ final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
+
+ if (SplitAttributesHelper.isDraggableExpandType(splitAttributes)) {
+ // If the container is fully expanded by dragging the divider, we display the divider
+ // on the edge.
+ final int dividerWidth = getDividerWidthPx(splitAttributes.getDividerAttributes());
+ final int fullyExpandedPosition = isVerticalSplit
+ ? primaryBounds.right - dividerWidth
+ : primaryBounds.bottom - dividerWidth;
+ return isReversedLayout ? fullyExpandedPosition : 0;
} else {
- return Math.min(primaryBounds.bottom, secondaryBounds.bottom);
+ return isVerticalSplit
+ ? Math.min(primaryBounds.right, secondaryBounds.right)
+ : Math.min(primaryBounds.bottom, secondaryBounds.bottom);
}
}
@@ -359,14 +384,14 @@
@VisibleForTesting
static int getBoundsOffsetForDivider(
int dividerWidthPx,
- @NonNull SplitAttributes.SplitType splitType,
+ @NonNull SplitType splitType,
@SplitPresenter.ContainerPosition int position) {
- if (splitType instanceof SplitAttributes.SplitType.ExpandContainersSplitType) {
- // No divider is needed for the ExpandContainersSplitType.
+ if (splitType instanceof ExpandContainersSplitType) {
+ // No divider offset is needed for the ExpandContainersSplitType.
return 0;
}
int primaryOffset;
- if (splitType instanceof final SplitAttributes.SplitType.RatioSplitType splitRatio) {
+ if (splitType instanceof final RatioSplitType splitRatio) {
// When a divider is present, both containers shrink by an amount proportional to their
// split ratio and sum to the width of the divider, so that the ending sizing of the
// containers still maintain the same ratio.
@@ -393,7 +418,8 @@
* Sanitizes and sets default values in the {@link DividerAttributes}.
*
* Unset values will be set with system default values. See
- * {@link DividerAttributes#WIDTH_UNSET} and {@link DividerAttributes#RATIO_UNSET}.
+ * {@link DividerAttributes#WIDTH_SYSTEM_DEFAULT} and
+ * {@link DividerAttributes#RATIO_SYSTEM_DEFAULT}.
*
* @param dividerAttributes input {@link DividerAttributes}
* @return a {@link DividerAttributes} that has all values properly set.
@@ -405,7 +431,7 @@
return null;
}
int widthDp = dividerAttributes.getWidthDp();
- if (widthDp == WIDTH_UNSET) {
+ if (widthDp == WIDTH_SYSTEM_DEFAULT) {
widthDp = DEFAULT_DIVIDER_WIDTH_DP;
}
@@ -416,12 +442,12 @@
}
float minRatio = dividerAttributes.getPrimaryMinRatio();
- if (minRatio == RATIO_UNSET) {
+ if (minRatio == RATIO_SYSTEM_DEFAULT) {
minRatio = DEFAULT_MIN_RATIO;
}
float maxRatio = dividerAttributes.getPrimaryMaxRatio();
- if (maxRatio == RATIO_UNSET) {
+ if (maxRatio == RATIO_SYSTEM_DEFAULT) {
maxRatio = DEFAULT_MAX_RATIO;
}
@@ -438,7 +464,7 @@
final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
mDividerPosition = calculateDividerPosition(
event, taskBounds, mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
- mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout);
+ mProperties.mIsVerticalSplit, calculateMinPosition(), calculateMaxPosition());
mRenderer.setDividerPosition(mDividerPosition);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
@@ -456,23 +482,27 @@
}
}
- // Returns false so that the default button click callback is still triggered, i.e. the
- // button UI transitions into the "pressed" state.
- return false;
+ // Returns true to prevent the default button click callback. The button pressed state is
+ // set/unset when starting/finishing dragging.
+ return true;
}
@GuardedBy("mLock")
private void onStartDragging() {
mRenderer.mIsDragging = true;
+ mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
mRenderer.updateSurface(t);
mRenderer.showVeils(t);
- final IBinder decorSurfaceOwner = mDecorSurfaceOwner;
// Callbacks must be executed on the executor to release mLock and prevent deadlocks.
mCallbackExecutor.execute(() -> {
mDragEventCallback.onStartDragging(
- wct -> setDecorSurfaceBoosted(wct, decorSurfaceOwner, true /* boosted */, t));
+ wct -> {
+ synchronized (mLock) {
+ setDecorSurfaceBoosted(wct, mDecorSurfaceOwner, true /* boosted */, t);
+ }
+ });
});
}
@@ -485,18 +515,62 @@
@GuardedBy("mLock")
private void onFinishDragging() {
+ mDividerPosition = adjustDividerPositionForSnapPoints(mDividerPosition);
+ mRenderer.setDividerPosition(mDividerPosition);
+
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
mRenderer.updateSurface(t);
mRenderer.hideVeils(t);
- final IBinder decorSurfaceOwner = mDecorSurfaceOwner;
// Callbacks must be executed on the executor to release mLock and prevent deadlocks.
+ // mDecorSurfaceOwner may change between here and when the callback is executed,
+ // e.g. when the decor surface owner becomes the secondary container when it is expanded to
+ // fullscreen.
mCallbackExecutor.execute(() -> {
mDragEventCallback.onFinishDragging(
mTaskId,
- wct -> setDecorSurfaceBoosted(wct, decorSurfaceOwner, false /* boosted */, t));
+ wct -> {
+ synchronized (mLock) {
+ setDecorSurfaceBoosted(wct, mDecorSurfaceOwner, false /* boosted */, t);
+ }
+ });
});
mRenderer.mIsDragging = false;
+ mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
+ }
+
+ /**
+ * Returns the divider position adjusted for the min max ratio and fullscreen expansion.
+ *
+ * If the dragging position is above the {@link DividerAttributes#getPrimaryMaxRatio()} or below
+ * {@link DividerAttributes#getPrimaryMinRatio()} and
+ * {@link DividerAttributes#isDraggingToFullscreenAllowed} is {@code true}, the system will
+ * choose a snap algorithm to adjust the ending position to either fully expand one container or
+ * move the divider back to the specified min/max ratio.
+ *
+ * TODO(b/327067596) implement snap algorithm
+ *
+ * The adjusted divider position is in the range of [minPosition, maxPosition] for a split, 0
+ * for expanded right (bottom) container, or task width (height) minus the divider width for
+ * expanded left (top) container.
+ */
+ @GuardedBy("mLock")
+ private int adjustDividerPositionForSnapPoints(int dividerPosition) {
+ final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
+ final int minPosition = calculateMinPosition();
+ final int maxPosition = calculateMaxPosition();
+ final int fullyExpandedPosition = mProperties.mIsVerticalSplit
+ ? taskBounds.right - mRenderer.mDividerWidthPx
+ : taskBounds.bottom - mRenderer.mDividerWidthPx;
+ if (isDraggingToFullscreenAllowed(mProperties.mDividerAttributes)) {
+ if (dividerPosition < minPosition) {
+ return 0;
+ }
+ if (dividerPosition > maxPosition) {
+ return fullyExpandedPosition;
+ }
+ }
+ return Math.clamp(dividerPosition, minPosition, maxPosition);
}
private static void setDecorSurfaceBoosted(
@@ -520,7 +594,7 @@
@VisibleForTesting
static int calculateDividerPosition(@NonNull MotionEvent event, @NonNull Rect taskBounds,
int dividerWidthPx, @NonNull DividerAttributes dividerAttributes,
- boolean isVerticalSplit, boolean isReversedLayout) {
+ boolean isVerticalSplit, int minPosition, int maxPosition) {
// The touch event is in display space. Converting it into the task window space.
final int touchPositionInTaskSpace = isVerticalSplit
? (int) (event.getRawX()) - taskBounds.left
@@ -530,15 +604,31 @@
// position is offset by half of the divider width.
int dividerPosition = touchPositionInTaskSpace - dividerWidthPx / 2;
- // Limit the divider position to the min and max ratios set in DividerAttributes.
- // TODO(b/327536303) Handle when the divider is dragged to the edge.
- dividerPosition = Math.max(dividerPosition, calculateMinPosition(
- taskBounds, dividerWidthPx, dividerAttributes, isVerticalSplit, isReversedLayout));
- dividerPosition = Math.min(dividerPosition, calculateMaxPosition(
- taskBounds, dividerWidthPx, dividerAttributes, isVerticalSplit, isReversedLayout));
+ // If dragging to fullscreen is not allowed, limit the divider position to the min and max
+ // ratios set in DividerAttributes. Otherwise, dragging beyond the min and max ratios is
+ // temporarily allowed and the final ratio will be adjusted in onFinishDragging.
+ if (!isDraggingToFullscreenAllowed(dividerAttributes)) {
+ dividerPosition = Math.clamp(dividerPosition, minPosition, maxPosition);
+ }
return dividerPosition;
}
+ @GuardedBy("mLock")
+ private int calculateMinPosition() {
+ return calculateMinPosition(
+ mProperties.mConfiguration.windowConfiguration.getBounds(),
+ mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
+ mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout);
+ }
+
+ @GuardedBy("mLock")
+ private int calculateMaxPosition() {
+ return calculateMaxPosition(
+ mProperties.mConfiguration.windowConfiguration.getBounds(),
+ mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
+ mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout);
+ }
+
/** Calculates the min position of the divider that the user is allowed to drag to. */
@VisibleForTesting
static int calculateMinPosition(@NonNull Rect taskBounds, int dividerWidthPx,
@@ -581,13 +671,24 @@
mProperties.mConfiguration.windowConfiguration.getBounds(),
mRenderer.mDividerWidthPx,
mProperties.mIsVerticalSplit,
- mProperties.mIsReversedLayout);
+ mProperties.mIsReversedLayout,
+ calculateMinPosition(),
+ calculateMaxPosition(),
+ isDraggingToFullscreenAllowed(mProperties.mDividerAttributes));
}
}
+ private static boolean isDraggingToFullscreenAllowed(
+ @NonNull DividerAttributes dividerAttributes) {
+ // TODO(b/293654166) Use DividerAttributes.isDraggingToFullscreenAllowed when extension is
+ // updated.
+ return true;
+ }
+
/**
* Returns the new split ratio of the {@link SplitContainer} based on the current divider
* position.
+ *
* @param topSplitContainer the {@link SplitContainer} for which to compute the split ratio.
* @param dividerPosition the divider position. See {@link #mDividerPosition}.
* @param taskBounds the task bounds
@@ -599,7 +700,9 @@
* bottom-to-top. If {@code false}, the split is not reversed, i.e.
* left-to-right or top-to-bottom. See
* {@link SplitAttributesHelper#isReversedLayout}
- * @return the computed split ratio of the primary container.
+ * @return the computed split ratio of the primary container. If the primary container is fully
+ * expanded, {@link #RATIO_EXPANDED_PRIMARY} is returned. If the secondary container is fully
+ * expanded, {@link #RATIO_EXPANDED_SECONDARY} is returned.
*/
@VisibleForTesting
static float calculateNewSplitRatio(
@@ -608,15 +711,33 @@
@NonNull Rect taskBounds,
int dividerWidthPx,
boolean isVerticalSplit,
- boolean isReversedLayout) {
+ boolean isReversedLayout,
+ int minPosition,
+ int maxPosition,
+ boolean isDraggingToFullscreenAllowed) {
+
+ // Handle the fully expanded cases.
+ if (isDraggingToFullscreenAllowed) {
+ // The divider position is already adjusted by the snap algorithm in onFinishDragging.
+ // If the divider position is not in the range [minPosition, maxPosition], then one of
+ // the containers is fully expanded.
+ if (dividerPosition < minPosition) {
+ return isReversedLayout ? RATIO_EXPANDED_PRIMARY : RATIO_EXPANDED_SECONDARY;
+ }
+ if (dividerPosition > maxPosition) {
+ return isReversedLayout ? RATIO_EXPANDED_SECONDARY : RATIO_EXPANDED_PRIMARY;
+ }
+ } else {
+ dividerPosition = Math.clamp(dividerPosition, minPosition, maxPosition);
+ }
+
+ final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer();
+ final Rect origPrimaryBounds = primaryContainer.getLastRequestedBounds();
final int usableSize = isVerticalSplit
? taskBounds.width() - dividerWidthPx
: taskBounds.height() - dividerWidthPx;
- final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer();
- final Rect origPrimaryBounds = primaryContainer.getLastRequestedBounds();
-
- float newRatio;
+ final float newRatio;
if (isVerticalSplit) {
final int newPrimaryWidth = isReversedLayout
? (origPrimaryBounds.right - (dividerPosition + dividerWidthPx))
@@ -677,6 +798,7 @@
private final int mDisplayId;
private final boolean mIsReversedLayout;
+ private final boolean mIsDraggableExpandType;
@VisibleForTesting
Properties(
@@ -686,7 +808,8 @@
int initialDividerPosition,
boolean isVerticalSplit,
boolean isReversedLayout,
- int displayId) {
+ int displayId,
+ boolean isDraggableExpandType) {
mConfiguration = configuration;
mDividerAttributes = dividerAttributes;
mDecorSurface = decorSurface;
@@ -694,6 +817,7 @@
mIsVerticalSplit = isVerticalSplit;
mIsReversedLayout = isReversedLayout;
mDisplayId = displayId;
+ mIsDraggableExpandType = isDraggableExpandType;
}
/**
@@ -714,7 +838,8 @@
&& a.mInitialDividerPosition == b.mInitialDividerPosition
&& a.mIsVerticalSplit == b.mIsVerticalSplit
&& a.mDisplayId == b.mDisplayId
- && a.mIsReversedLayout == b.mIsReversedLayout;
+ && a.mIsReversedLayout == b.mIsReversedLayout
+ && a.mIsDraggableExpandType == b.mIsDraggableExpandType;
}
private static boolean areSameSurfaces(
@@ -761,6 +886,7 @@
private SurfaceControl mSecondaryVeil;
private boolean mIsDragging;
private int mDividerPosition;
+ private View mDragHandle;
private Renderer(@NonNull Properties properties, @NonNull View.OnTouchListener listener) {
mProperties = properties;
@@ -857,6 +983,7 @@
PixelFormat.TRANSLUCENT);
lp.setTitle(WINDOW_NAME);
mViewHost.setView(mDividerLayout, lp);
+ mViewHost.relayout(lp);
}
/**
@@ -867,7 +994,12 @@
*/
private void updateDivider(@NonNull SurfaceControl.Transaction t) {
mDividerLayout.removeAllViews();
- mDividerLayout.setBackgroundColor(DEFAULT_DIVIDER_COLOR.toArgb());
+ if (mProperties.mIsDraggableExpandType) {
+ // If a container is fully expanded, the divider overlays on the expanded container.
+ mDividerLayout.setBackgroundColor(Color.TRANSPARENT);
+ } else {
+ mDividerLayout.setBackgroundColor(mProperties.mDividerAttributes.getDividerColor());
+ }
if (mProperties.mDividerAttributes.getDividerType()
== DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
createVeils();
@@ -916,6 +1048,7 @@
}
button.setOnTouchListener(mListener);
+ mDragHandle = button;
mDividerLayout.addView(button);
}
@@ -928,7 +1061,7 @@
.setHidden(!visible)
.setCallsite("DividerManager.createChildSurface")
.setBufferSize(bounds.width(), bounds.height())
- .setColorLayer()
+ .setEffectLayer()
.build();
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java
index 042a68a6..4541a84 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java
@@ -20,6 +20,7 @@
import android.view.View;
import androidx.annotation.NonNull;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
/** Helper functions for {@link SplitAttributes} */
class SplitAttributesHelper {
@@ -43,4 +44,17 @@
"Invalid layout direction:" + splitAttributes.getLayoutDirection());
}
}
+
+ /**
+ * Returns whether the {@link SplitAttributes} is an {@link ExpandContainersSplitType} and it
+ * should show a draggable handle that allows the user to drag and restore it into a split.
+ * This state is a result of user dragging the divider to fully expand the secondary container.
+ */
+ static boolean isDraggableExpandType(@NonNull SplitAttributes splitAttributes) {
+ final DividerAttributes dividerAttributes = splitAttributes.getDividerAttributes();
+ return splitAttributes.getSplitType() instanceof ExpandContainersSplitType
+ && dividerAttributes != null
+ && dividerAttributes.getDividerType() == DividerAttributes.DIVIDER_TYPE_DRAGGABLE;
+
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index b9b86f0..46a3e7f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -21,6 +21,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
@@ -115,6 +116,11 @@
static final boolean ENABLE_SHELL_TRANSITIONS =
SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
+ // TODO(b/243518738): Move to WM Extensions if we have requirement of overlay without
+ // association. It's not set in WM Extensions nor Wm Jetpack library currently.
+ private static final String KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY =
+ "androidx.window.extensions.embedding.shouldAssociateWithLaunchingActivity";
+
@VisibleForTesting
@GuardedBy("mLock")
final SplitPresenter mPresenter;
@@ -855,9 +861,9 @@
// container update.
updateDivider(wct, taskContainer);
- // If the last direct activity of the host task is dismissed and the overlay container is
- // the only taskFragment, the overlay container should also be dismissed.
- dismissOverlayContainerIfNeeded(wct, taskContainer);
+ // If the last direct activity of the host task is dismissed and there's an always-on-top
+ // overlay container in the task, the overlay container should also be dismissed.
+ dismissAlwaysOnTopOverlayIfNeeded(wct, taskContainer);
if (!shouldUpdateContainer) {
return;
@@ -1405,9 +1411,27 @@
launchPlaceholderIfNecessary(wct, activity, false /* isOnCreated */);
}
+ @GuardedBy("mLock")
+ private void onActivityPaused(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity) {
+ // Checks if there's any finishing activity in paused state associate with an overlay
+ // container. #OnActivityPostDestroyed is a very late signal, which is called after activity
+ // is not visible and the next activity shows on screen.
+ if (!activity.isFinishing()) {
+ // onPaused is triggered without finishing. Early return.
+ return;
+ }
+ // Check if we should dismiss the overlay container with this finishing activity.
+ final IBinder activityToken = activity.getActivityToken();
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ mTaskContainers.valueAt(i).onFinishingActivityPaused(wct, activityToken);
+ }
+ updateCallbackIfNecessary();
+ }
+
@VisibleForTesting
@GuardedBy("mLock")
- void onActivityDestroyed(@NonNull Activity activity) {
+ void onActivityDestroyed(@NonNull WindowContainerTransaction wct, @NonNull Activity activity) {
if (!activity.isFinishing()) {
// onDestroyed is triggered without finishing. This happens when the activity is
// relaunched. In this case, we don't want to cleanup the record.
@@ -1417,7 +1441,7 @@
// organizer.
final IBinder activityToken = activity.getActivityToken();
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- mTaskContainers.valueAt(i).onActivityDestroyed(activityToken);
+ mTaskContainers.valueAt(i).onActivityDestroyed(wct, activityToken);
}
// We didn't trigger the callback if there were any pending appeared activities, so check
// again after the pending is removed.
@@ -1597,7 +1621,8 @@
@Nullable Activity launchingActivity) {
return createEmptyContainer(wct, intent, taskId,
new ActivityStackAttributes.Builder().build(), launchingActivity,
- null /* overlayTag */, null /* launchOptions */);
+ null /* overlayTag */, null /* launchOptions */,
+ false /* shouldAssociateWithLaunchingActivity */);
}
/**
@@ -1612,7 +1637,7 @@
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@NonNull ActivityStackAttributes activityStackAttributes,
@Nullable Activity launchingActivity, @Nullable String overlayTag,
- @Nullable Bundle launchOptions) {
+ @Nullable Bundle launchOptions, boolean associateLaunchingActivity) {
// We need an activity in the organizer process in the same Task to use as the owner
// activity, as well as to get the Task window info.
final Activity activityInTask;
@@ -1630,7 +1655,7 @@
}
final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */,
intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag,
- launchOptions);
+ launchOptions, associateLaunchingActivity);
final IBinder taskFragmentToken = container.getTaskFragmentToken();
// Note that taskContainer will not exist before calling #newContainer if the container
// is the first embedded TF in the task.
@@ -1722,7 +1747,7 @@
@NonNull Activity activityInTask, int taskId) {
return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */,
- null /* launchOptions */);
+ null /* launchOptions */, false /* associateLaunchingActivity */);
}
@GuardedBy("mLock")
@@ -1730,7 +1755,7 @@
@NonNull Activity activityInTask, int taskId) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */,
- null /* launchOptions */);
+ null /* launchOptions */, false /* associateLaunchingActivity */);
}
@GuardedBy("mLock")
@@ -1739,7 +1764,7 @@
@NonNull TaskFragmentContainer pairedPrimaryContainer) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
activityInTask, taskId, pairedPrimaryContainer, null /* tag */,
- null /* launchOptions */);
+ null /* launchOptions */, false /* associateLaunchingActivity */);
}
/**
@@ -1751,19 +1776,21 @@
* @param activityInTask activity in the same Task so that we can get the Task bounds
* if needed.
* @param taskId parent Task of the new TaskFragment.
- * @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is
+ * @param pairedContainer the paired primary {@link TaskFragmentContainer}. When it is
* set, the new container will be added right above it.
* @param overlayTag The tag for the new created overlay container. It must be
* needed if {@code isOverlay} is {@code true}. Otherwise,
* it should be {@code null}.
* @param launchOptions The launch options bundle to create a container. Must be
* specified for overlay container.
+ * @param associateLaunchingActivity {@code true} to indicate this overlay container
+ * should associate with launching activity.
*/
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
- @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
- @Nullable Bundle launchOptions) {
+ @Nullable TaskFragmentContainer pairedContainer, @Nullable String overlayTag,
+ @Nullable Bundle launchOptions, boolean associateLaunchingActivity) {
if (activityInTask == null) {
throw new IllegalArgumentException("activityInTask must not be null,");
}
@@ -1773,8 +1800,8 @@
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
- pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag,
- launchOptions);
+ pendingAppearedIntent, taskContainer, this, pairedContainer, overlayTag,
+ launchOptions, associateLaunchingActivity ? activityInTask : null);
return container;
}
@@ -1963,7 +1990,7 @@
@NonNull TaskFragmentContainer container) {
final TaskContainer taskContainer = container.getTaskContainer();
- if (dismissOverlayContainerIfNeeded(wct, taskContainer)) {
+ if (dismissAlwaysOnTopOverlayIfNeeded(wct, taskContainer)) {
return;
}
@@ -1987,22 +2014,27 @@
}
}
- /** Dismisses the overlay container in the {@code taskContainer} if needed. */
+ /**
+ * Dismisses {@link TaskFragmentContainer#isAlwaysOnTopOverlay()} in the {@code taskContainer}
+ * if needed.
+ */
@GuardedBy("mLock")
- private boolean dismissOverlayContainerIfNeeded(@NonNull WindowContainerTransaction wct,
- @NonNull TaskContainer taskContainer) {
- final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer();
- if (overlayContainer == null) {
+ private boolean dismissAlwaysOnTopOverlayIfNeeded(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskContainer taskContainer) {
+ // Dismiss always-on-top overlay container if it's the only container in the task and
+ // there's no direct activity in the parent task.
+ final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
+ if (containers.size() != 1 || taskContainer.hasDirectActivity()) {
return false;
}
- // Dismiss the overlay container if it's the only container in the task and there's no
- // direct activity in the parent task.
- if (taskContainer.getTaskFragmentContainers().size() == 1
- && !taskContainer.hasDirectActivity()) {
- mPresenter.cleanupContainer(wct, overlayContainer, false /* shouldFinishDependant */);
- return true;
+
+ final TaskFragmentContainer container = containers.getLast();
+ if (!container.isAlwaysOnTopOverlay()) {
+ return false;
}
- return false;
+
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependant */);
+ return true;
}
/**
@@ -2141,6 +2173,10 @@
return false;
}
+ if (container != null && container.getTaskContainer().isPlaceholderRuleSuppressed()) {
+ return false;
+ }
+
final TaskContainer.TaskProperties taskProperties = mPresenter.getTaskProperties(activity);
final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity,
placeholderRule.getPlaceholderIntent());
@@ -2236,6 +2272,9 @@
if (SplitPresenter.shouldShowSplit(splitAttributes)) {
return false;
}
+ if (SplitPresenter.shouldShowPlaceholderWhenExpanded(splitAttributes)) {
+ return false;
+ }
mTransactionManager.getCurrentTransactionRecord()
.setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
@@ -2586,25 +2625,42 @@
/**
* Gets all overlay containers from all tasks in this process, or an empty list if there's
* no overlay container.
- * <p>
- * Note that we only support one overlay container for each task, but an app could have multiple
- * tasks.
*/
@VisibleForTesting
@GuardedBy("mLock")
@NonNull
- List<TaskFragmentContainer> getAllOverlayTaskFragmentContainers() {
+ List<TaskFragmentContainer> getAllNonFinishingOverlayContainers() {
final List<TaskFragmentContainer> overlayContainers = new ArrayList<>();
for (int i = 0; i < mTaskContainers.size(); i++) {
final TaskContainer taskContainer = mTaskContainers.valueAt(i);
- final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer();
- if (overlayContainer != null) {
- overlayContainers.add(overlayContainer);
- }
+ final List<TaskFragmentContainer> overlayContainersPerTask = taskContainer
+ .getTaskFragmentContainers()
+ .stream()
+ .filter(c -> c.isOverlay() && !c.isFinished())
+ .toList();
+ overlayContainers.addAll(overlayContainersPerTask);
}
return overlayContainers;
}
+ /**
+ * Creates an overlay container or updates a visible overlay container if its
+ * {@link TaskFragmentContainer#getTaskId()}, {@link TaskFragmentContainer#getOverlayTag()}
+ * and {@link TaskFragmentContainer#getAssociatedActivityToken()} matches.
+ * <p>
+ * This method will also dismiss any existing overlay container if:
+ * <ul>
+ * <li>it's visible but not meet the criteria to update overlay</li>
+ * <li>{@link TaskFragmentContainer#getOverlayTag()} matches but not meet the criteria to
+ * update overlay</li>
+ * </ul>
+ *
+ * @param wct the {@link WindowContainerTransaction}
+ * @param options the {@link ActivityOptions} to launch the overlay
+ * @param intent the intent of activity to launch
+ * @param launchActivity the activity to launch the overlay container
+ * @return the overlay container
+ */
@VisibleForTesting
// Suppress GuardedBy warning because lint ask to mark this method as
// @GuardedBy(container.mController.mLock), which is mLock itself
@@ -2615,8 +2671,10 @@
@NonNull WindowContainerTransaction wct, @NonNull Bundle options,
@NonNull Intent intent, @NonNull Activity launchActivity) {
final List<TaskFragmentContainer> overlayContainers =
- getAllOverlayTaskFragmentContainers();
+ getAllNonFinishingOverlayContainers();
final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG));
+ final boolean associateLaunchingActivity = options
+ .getBoolean(KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY, true);
// If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
// specified by Intent, expand the overlay container to fill the parent task instead.
@@ -2637,35 +2695,91 @@
final int taskId = getTaskId(launchActivity);
if (!overlayContainers.isEmpty()) {
for (final TaskFragmentContainer overlayContainer : overlayContainers) {
- if (!overlayTag.equals(overlayContainer.getOverlayTag())
- && taskId == overlayContainer.getTaskId()) {
- // If there's an overlay container with different tag shown in the same
- // task, dismiss the existing overlay container.
- mPresenter.cleanupContainer(wct, overlayContainer,
- false /* shouldFinishDependant */);
- }
- if (overlayTag.equals(overlayContainer.getOverlayTag())
- && taskId != overlayContainer.getTaskId()) {
+ final boolean isTopNonFinishingOverlay = overlayContainer.equals(
+ overlayContainer.getTaskContainer().getTopNonFinishingTaskFragmentContainer(
+ true /* includePin */, true /* includeOverlay */));
+ if (taskId != overlayContainer.getTaskId()) {
// If there's an overlay container with same tag in a different task,
// dismiss the overlay container since the tag must be unique per process.
+ if (overlayTag.equals(overlayContainer.getOverlayTag())) {
+ Log.w(TAG, "The overlay container with tag:"
+ + overlayContainer.getOverlayTag() + " is dismissed because"
+ + " there's an existing overlay container with the same tag but"
+ + " different task ID:" + overlayContainer.getTaskId() + ". "
+ + "The new associated activity is " + launchActivity);
+ mPresenter.cleanupContainer(wct, overlayContainer,
+ false /* shouldFinishDependant */);
+ }
+ continue;
+ }
+ if (!overlayTag.equals(overlayContainer.getOverlayTag())) {
+ // If there's an overlay container with different tag on top in the same
+ // task, dismiss the existing overlay container.
+ if (isTopNonFinishingOverlay) {
+ mPresenter.cleanupContainer(wct, overlayContainer,
+ false /* shouldFinishDependant */);
+ }
+ continue;
+ }
+ // The overlay container has the same tag and task ID with the new launching
+ // overlay container.
+ if (!isTopNonFinishingOverlay) {
+ // Dismiss the invisible overlay container regardless of activity
+ // association if it collides the tag of new launched overlay container .
+ Log.w(TAG, "The invisible overlay container with tag:"
+ + overlayContainer.getOverlayTag() + " is dismissed because"
+ + " there's a launching overlay container with the same tag."
+ + " The new associated activity is " + launchActivity);
mPresenter.cleanupContainer(wct, overlayContainer,
false /* shouldFinishDependant */);
+ continue;
}
- if (overlayTag.equals(overlayContainer.getOverlayTag())
- && taskId == overlayContainer.getTaskId()) {
- mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
- getMinDimensions(intent));
- // We can just return the updated overlay container and don't need to
- // check other condition since we only have one OverlayCreateParams, and
- // if the tag and task are matched, it's impossible to match another task
- // or tag since tags and tasks are all unique.
- return overlayContainer;
+ // Requesting an always-on-top overlay.
+ if (!associateLaunchingActivity) {
+ if (overlayContainer.isAssociatedWithActivity()) {
+ // Dismiss the overlay container since it has associated with an activity.
+ Log.w(TAG, "The overlay container with tag:"
+ + overlayContainer.getOverlayTag() + " is dismissed because"
+ + " there's an existing overlay container with the same tag but"
+ + " different associated launching activity. The overlay container"
+ + " doesn't associate with any activity.");
+ mPresenter.cleanupContainer(wct, overlayContainer,
+ false /* shouldFinishDependant */);
+ continue;
+ } else {
+ // The existing overlay container doesn't associate an activity as well.
+ // Just update the overlay and return.
+ // Note that going to this condition means the tag, task ID matches a
+ // visible always-on-top overlay, and won't dismiss any overlay any more.
+ mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
+ getMinDimensions(intent));
+ return overlayContainer;
+ }
}
+ if (launchActivity.getActivityToken()
+ != overlayContainer.getAssociatedActivityToken()) {
+ Log.w(TAG, "The overlay container with tag:"
+ + overlayContainer.getOverlayTag() + " is dismissed because"
+ + " there's an existing overlay container with the same tag but"
+ + " different associated launching activity. The new associated"
+ + " activity is " + launchActivity);
+ // The associated activity must be the same, or it will be dismissed.
+ mPresenter.cleanupContainer(wct, overlayContainer,
+ false /* shouldFinishDependant */);
+ continue;
+ }
+ // Reaching here means the launching activity launch an overlay container with the
+ // same task ID, tag, while there's a previously launching visible overlay
+ // container. We'll regard it as updating the existing overlay container.
+ mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
+ getMinDimensions(intent));
+ return overlayContainer;
+
}
}
// Launch the overlay container to the task with taskId.
return createEmptyContainer(wct, intent, taskId, attrs, launchActivity, overlayTag,
- options);
+ options, associateLaunchingActivity);
}
private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@@ -2754,6 +2868,23 @@
}
@Override
+ public void onActivityPostPaused(@NonNull Activity activity) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
+ synchronized (mLock) {
+ final TransactionRecord transactionRecord = mTransactionManager
+ .startNewTransaction();
+ transactionRecord.setOriginType(TRANSIT_CLOSE);
+ SplitController.this.onActivityPaused(
+ transactionRecord.getTransaction(), activity);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+ }
+ }
+
+ @Override
public void onActivityPostDestroyed(@NonNull Activity activity) {
if (activity.isChild()) {
// Skip Activity that is child of another Activity (ActivityGroup) because it's
@@ -2761,7 +2892,12 @@
return;
}
synchronized (mLock) {
- SplitController.this.onActivityDestroyed(activity);
+ final TransactionRecord transactionRecord = mTransactionManager
+ .startNewTransaction();
+ transactionRecord.setOriginType(TRANSIT_CLOSE);
+ SplitController.this.onActivityDestroyed(
+ transactionRecord.getTransaction(), activity);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
}
@@ -3111,7 +3247,12 @@
if (taskContainer != null) {
final DividerPresenter dividerPresenter =
mDividerPresenters.get(taskContainer.getTaskId());
- taskContainer.updateTopSplitContainerForDivider(dividerPresenter);
+ final List<TaskFragmentContainer> containersToFinish = new ArrayList<>();
+ taskContainer.updateTopSplitContainerForDivider(
+ dividerPresenter, containersToFinish);
+ for (final TaskFragmentContainer container : containersToFinish) {
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
+ }
updateContainersInTask(wct, taskContainer);
}
action.accept(wct);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 0d31266..6231ea0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -467,6 +467,11 @@
reorderTaskFragmentToFront(wct,
pinnedContainer.getSecondaryContainer().getTaskFragmentToken());
}
+ final TaskFragmentContainer alwaysOnTopOverlayContainer = container.getTaskContainer()
+ .getAlwaysOnTopOverlayContainer();
+ if (alwaysOnTopOverlayContainer != null) {
+ reorderTaskFragmentToFront(wct, alwaysOnTopOverlayContainer.getTaskFragmentToken());
+ }
}
@Override
@@ -565,7 +570,7 @@
@Override
void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder primary,
- @Nullable IBinder secondary) {
+ @Nullable IBinder secondary) {
final TaskFragmentContainer container = mController.getContainer(primary);
if (container == null) {
throw new IllegalStateException("setCompanionTaskFragment on TaskFragment that is"
@@ -590,7 +595,12 @@
final Rect relativeBounds = sanitizeBounds(attributes.getRelativeBounds(), minDimensions,
taskBounds);
final boolean isFillParent = relativeBounds.isEmpty();
- final boolean isIsolatedNavigated = !isFillParent && container.isOverlay();
+ // Note that we only set isolated navigation for overlay container without activity
+ // association. Activity will be launched to an expanded container on top of the overlay
+ // if the overlay is associated with an activity. Thus, an overlay with activity association
+ // will never be isolated navigated.
+ final boolean isIsolatedNavigated = container.isOverlay()
+ && !container.isAssociatedWithActivity() && !isFillParent;
final boolean dimOnTask = !isFillParent
&& attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK
&& Flags.fullscreenDimFlag();
@@ -716,6 +726,12 @@
return !(splitAttributes.getSplitType() instanceof ExpandContainersSplitType);
}
+ static boolean shouldShowPlaceholderWhenExpanded(@NonNull SplitAttributes splitAttributes) {
+ // The placeholder should be kept if the expand split type is a result of user dragging
+ // the divider.
+ return SplitAttributesHelper.isDraggableExpandType(splitAttributes);
+ }
+
@NonNull
SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties,
@NonNull SplitRule rule, @NonNull SplitAttributes defaultSplitAttributes,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index a215bdf..fdf0910 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -22,6 +22,9 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.inMultiWindowMode;
+import static androidx.window.extensions.embedding.DividerPresenter.RATIO_EXPANDED_PRIMARY;
+import static androidx.window.extensions.embedding.DividerPresenter.RATIO_EXPANDED_SECONDARY;
+
import android.app.Activity;
import android.app.ActivityClient;
import android.app.WindowConfiguration;
@@ -40,6 +43,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
import java.util.ArrayList;
import java.util.List;
@@ -64,9 +70,11 @@
@Nullable
private SplitPinContainer mSplitPinContainer;
- /** The overlay container in this Task. */
+ /**
+ * The {@link TaskFragmentContainer#isAlwaysOnTopOverlay()} in the Task.
+ */
@Nullable
- private TaskFragmentContainer mOverlayContainer;
+ private TaskFragmentContainer mAlwaysOnTopOverlayContainer;
@NonNull
private final Configuration mConfiguration;
@@ -89,6 +97,25 @@
final Set<IBinder> mFinishedContainer = new ArraySet<>();
/**
+ * The {@link RatioSplitType} that will be applied to newly added containers. This is to ensure
+ * the required UX that, after user dragging the divider, the split ratio is persistent after
+ * launching a new activity into a new TaskFragment in the same Task.
+ */
+ private RatioSplitType mOverrideSplitType;
+
+ /**
+ * If {@code true}, suppress the placeholder rules in the {@link TaskContainer}.
+ * <p>
+ * This is used in case the user drags the divider to fully expand the primary container and
+ * dismiss the secondary container while a {@link SplitPlaceholderRule} is used. Without this
+ * flag, after dismissing the secondary container, a placeholder will be launched again.
+ * <p>
+ * This flag is set true when the primary container is fully expanded and cleared when a new
+ * split is added to the {@link TaskContainer}.
+ */
+ private boolean mPlaceholderRuleSuppressed;
+
+ /**
* The {@link TaskContainer} constructor
*
* @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with
@@ -211,10 +238,19 @@
return mContainers.isEmpty() && mFinishedContainer.isEmpty();
}
- /** Called when the activity is destroyed. */
- void onActivityDestroyed(@NonNull IBinder activityToken) {
+ /** Called when the activity {@link Activity#isFinishing()} and paused. */
+ void onFinishingActivityPaused(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder activityToken) {
for (TaskFragmentContainer container : mContainers) {
- container.onActivityDestroyed(activityToken);
+ container.onFinishingActivityPaused(wct, activityToken);
+ }
+ }
+
+ /** Called when the activity is destroyed. */
+ void onActivityDestroyed(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder activityToken) {
+ for (TaskFragmentContainer container : mContainers) {
+ container.onActivityDestroyed(wct, activityToken);
}
}
@@ -282,10 +318,12 @@
return null;
}
- /** Returns the overlay container in the task, or {@code null} if it doesn't exist. */
+ /**
+ * Returns the always-on-top overlay container in the task, or {@code null} if it doesn't exist.
+ */
@Nullable
- TaskFragmentContainer getOverlayContainer() {
- return mOverlayContainer;
+ TaskFragmentContainer getAlwaysOnTopOverlayContainer() {
+ return mAlwaysOnTopOverlayContainer;
}
int indexOf(@NonNull TaskFragmentContainer child) {
@@ -313,6 +351,11 @@
}
void addSplitContainer(@NonNull SplitContainer splitContainer) {
+ // Reset the placeholder rule suppression when a new split container is added.
+ mPlaceholderRuleSuppressed = false;
+
+ applyOverrideSplitTypeIfNeeded(splitContainer);
+
if (splitContainer instanceof SplitPinContainer) {
mSplitPinContainer = (SplitPinContainer) splitContainer;
mSplitContainers.add(splitContainer);
@@ -327,6 +370,39 @@
}
}
+ boolean isPlaceholderRuleSuppressed() {
+ return mPlaceholderRuleSuppressed;
+ }
+
+ // If there is an override SplitType due to user dragging the divider, the split ratio should
+ // be applied to newly added SplitContainers.
+ private void applyOverrideSplitTypeIfNeeded(@NonNull SplitContainer splitContainer) {
+ if (mOverrideSplitType == null) {
+ return;
+ }
+ final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
+ final DividerAttributes dividerAttributes = splitAttributes.getDividerAttributes();
+ if (!(splitAttributes.getSplitType() instanceof RatioSplitType)) {
+ // Skip if the original split type is not a ratio type.
+ return;
+ }
+ if (dividerAttributes == null
+ || dividerAttributes.getDividerType() != DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
+ // Skip if the split does not have a draggable divider.
+ return;
+ }
+ updateDefaultSplitAttributes(splitContainer, mOverrideSplitType);
+ }
+
+ private static void updateDefaultSplitAttributes(
+ @NonNull SplitContainer splitContainer, @NonNull SplitType overrideSplitType) {
+ splitContainer.updateDefaultSplitAttributes(
+ new SplitAttributes.Builder(splitContainer.getDefaultSplitAttributes())
+ .setSplitType(overrideSplitType)
+ .build()
+ );
+ }
+
void removeSplitContainers(@NonNull List<SplitContainer> containers) {
mSplitContainers.removeAll(containers);
}
@@ -398,18 +474,47 @@
return mContainers;
}
- void updateTopSplitContainerForDivider(@NonNull DividerPresenter dividerPresenter) {
+ void updateTopSplitContainerForDivider(
+ @NonNull DividerPresenter dividerPresenter,
+ @NonNull List<TaskFragmentContainer> outContainersToFinish) {
final SplitContainer topSplitContainer = getTopNonFinishingSplitContainer();
if (topSplitContainer == null) {
return;
}
-
+ final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer();
final float newRatio = dividerPresenter.calculateNewSplitRatio(topSplitContainer);
- topSplitContainer.updateDefaultSplitAttributes(
- new SplitAttributes.Builder(topSplitContainer.getDefaultSplitAttributes())
- .setSplitType(new SplitAttributes.SplitType.RatioSplitType(newRatio))
- .build()
- );
+
+ // If the primary container is fully expanded, we should finish all the associated
+ // secondary containers.
+ if (newRatio == RATIO_EXPANDED_PRIMARY) {
+ for (final SplitContainer splitContainer : mSplitContainers) {
+ if (primaryContainer == splitContainer.getPrimaryContainer()) {
+ outContainersToFinish.add(splitContainer.getSecondaryContainer());
+ }
+ }
+
+ // Temporarily suppress the placeholder rule in the TaskContainer. This will be restored
+ // if a new split is added into the TaskContainer.
+ mPlaceholderRuleSuppressed = true;
+
+ mOverrideSplitType = null;
+ return;
+ }
+
+ final SplitType newSplitType;
+ if (newRatio == RATIO_EXPANDED_SECONDARY) {
+ newSplitType = new ExpandContainersSplitType();
+ // We do not want to apply ExpandContainersSplitType to new split containers.
+ mOverrideSplitType = null;
+ } else {
+ // We save the override RatioSplitType and apply to new split containers.
+ newSplitType = mOverrideSplitType = new RatioSplitType(newRatio);
+ }
+ for (final SplitContainer splitContainer : mSplitContainers) {
+ if (primaryContainer == splitContainer.getPrimaryContainer()) {
+ updateDefaultSplitAttributes(splitContainer, newSplitType);
+ }
+ }
}
@Nullable
@@ -430,7 +535,21 @@
updateSplitPinContainerIfNecessary();
// Update overlay container after split pin container since the overlay should be on top of
// pin container.
- updateOverlayContainerIfNecessary();
+ updateAlwaysOnTopOverlayIfNecessary();
+ }
+
+ private void updateAlwaysOnTopOverlayIfNecessary() {
+ final List<TaskFragmentContainer> alwaysOnTopOverlays = mContainers
+ .stream().filter(TaskFragmentContainer::isAlwaysOnTopOverlay).toList();
+ if (alwaysOnTopOverlays.size() > 1) {
+ throw new IllegalStateException("There must be at most one always-on-top overlay "
+ + "container per Task");
+ }
+ mAlwaysOnTopOverlayContainer = alwaysOnTopOverlays.isEmpty()
+ ? null : alwaysOnTopOverlays.getFirst();
+ if (mAlwaysOnTopOverlayContainer != null) {
+ moveContainerToLastIfNecessary(mAlwaysOnTopOverlayContainer);
+ }
}
private void updateSplitPinContainerIfNecessary() {
@@ -458,18 +577,6 @@
}
}
- private void updateOverlayContainerIfNecessary() {
- final List<TaskFragmentContainer> overlayContainers = mContainers.stream()
- .filter(TaskFragmentContainer::isOverlay).toList();
- if (overlayContainers.size() > 1) {
- throw new IllegalStateException("There must be at most one overlay container per Task");
- }
- mOverlayContainer = overlayContainers.isEmpty() ? null : overlayContainers.get(0);
- if (mOverlayContainer != null) {
- moveContainerToLastIfNecessary(mOverlayContainer);
- }
- }
-
/** Moves the {@code container} to the last to align taskFragments' z-order. */
private void moveContainerToLastIfNecessary(@NonNull TaskFragmentContainer container) {
final int index = mContainers.indexOf(container);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index e20a3e0..094ebcb 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -113,6 +113,18 @@
@NonNull
private final Bundle mLaunchOptions = new Bundle();
+ /**
+ * The associated {@link Activity#getActivityToken()} of the overlay container.
+ * Must be {@code null} for non-overlay container.
+ * <p>
+ * If an overlay container is associated with an activity, this overlay container will be
+ * dismissed when the associated activity is destroyed. If the overlay container is visible,
+ * activity will be launched on top of the overlay container and expanded to fill the parent
+ * container.
+ */
+ @Nullable
+ private final IBinder mAssociatedActivityToken;
+
/** Indicates whether the container was cleaned up after the last activity was removed. */
private boolean mIsFinished;
@@ -178,7 +190,7 @@
/**
* @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController,
- * TaskFragmentContainer, String, Bundle)
+ * TaskFragmentContainer, String, Bundle, Activity)
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent,
@@ -187,7 +199,7 @@
@Nullable TaskFragmentContainer pairedPrimaryContainer) {
this(pendingAppearedActivity, pendingAppearedIntent, taskContainer,
controller, pairedPrimaryContainer, null /* overlayTag */,
- null /* launchOptions */);
+ null /* launchOptions */, null /* associatedActivity */);
}
/**
@@ -197,12 +209,14 @@
* @param overlayTag Sets to indicate this taskFragment is an overlay container
* @param launchOptions The launch options to create this container. Must not be
* {@code null} for an overlay container
+ * @param associatedActivity the associated activity of the overlay container. Must be
+ * {@code null} for a non-overlay container.
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
@NonNull SplitController controller,
@Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
- @Nullable Bundle launchOptions) {
+ @Nullable Bundle launchOptions, @Nullable Activity associatedActivity) {
if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
|| (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
throw new IllegalArgumentException(
@@ -214,7 +228,13 @@
mOverlayTag = overlayTag;
if (overlayTag != null) {
Objects.requireNonNull(launchOptions);
+ } else if (associatedActivity != null) {
+ throw new IllegalArgumentException("Associated activity must be null for "
+ + "non-overlay activity.");
}
+ mAssociatedActivityToken = associatedActivity != null
+ ? associatedActivity.getActivityToken() : null;
+
if (launchOptions != null) {
mLaunchOptions.putAll(launchOptions);
}
@@ -420,14 +440,38 @@
}
}
+ /** Called when the activity {@link Activity#isFinishing()} and paused. */
+ void onFinishingActivityPaused(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder activityToken) {
+ finishSelfWithActivityIfNeeded(wct, activityToken);
+ }
+
/** Called when the activity is destroyed. */
- void onActivityDestroyed(@NonNull IBinder activityToken) {
+ void onActivityDestroyed(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder activityToken) {
removePendingAppearedActivity(activityToken);
if (mInfo != null) {
// Remove the activity now because there can be a delay before the server callback.
mInfo.getActivities().remove(activityToken);
}
mActivitiesToFinishOnExit.remove(activityToken);
+ finishSelfWithActivityIfNeeded(wct, activityToken);
+ }
+
+ @VisibleForTesting
+ void finishSelfWithActivityIfNeeded(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder activityToken) {
+ if (mIsFinished) {
+ return;
+ }
+ // Early return if this container is not an overlay with activity association.
+ if (!isOverlay() || !isAssociatedWithActivity()) {
+ return;
+ }
+ if (mAssociatedActivityToken == activityToken) {
+ // If the associated activity is destroyed, also finish this overlay container.
+ mController.mPresenter.cleanupContainer(wct, this, false /* shouldFinishDependent */);
+ }
}
@Nullable
@@ -961,6 +1005,32 @@
return mLaunchOptions;
}
+ /**
+ * Returns the associated Activity token of this overlay container. It must be {@code null}
+ * for non-overlay container.
+ * <p>
+ * If an overlay container is associated with an activity, this overlay container will be
+ * dismissed when the associated activity is destroyed. If the overlay container is visible,
+ * activity will be launched on top of the overlay container and expanded to fill the parent
+ * container.
+ */
+ @Nullable
+ IBinder getAssociatedActivityToken() {
+ return mAssociatedActivityToken;
+ }
+
+ boolean isAssociatedWithActivity() {
+ return mAssociatedActivityToken != null;
+ }
+
+ /**
+ * Returns {@code true} if the overlay container should be always on top, which should be
+ * a non-fill-parent overlay without activity association.
+ */
+ boolean isAlwaysOnTopOverlay() {
+ return isOverlay() && !isAssociatedWithActivity();
+ }
+
@Override
public String toString() {
return toString(true /* includeContainersToFinishOnExit */);
@@ -980,6 +1050,7 @@
+ " runningActivityCount=" + getRunningActivityCount()
+ " isFinished=" + mIsFinished
+ " overlayTag=" + mOverlayTag
+ + " associatedActivity" + mAssociatedActivityToken
+ " lastRequestedBounds=" + mLastRequestedBounds
+ " pendingAppearedActivities=" + mPendingAppearedActivities
+ (includeContainersToFinishOnExit ? " containersToFinishOnExit="
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
index 47d01da..de0171d 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
@@ -144,10 +144,12 @@
new Configuration(),
DEFAULT_DIVIDER_ATTRIBUTES,
mSurfaceControl,
- getInitialDividerPosition(mSplitContainer),
+ getInitialDividerPosition(
+ mSplitContainer, true /* isVerticalSplit */, false /* isReversedLayout */),
true /* isVerticalSplit */,
false /* isReversedLayout */,
- Display.DEFAULT_DISPLAY);
+ Display.DEFAULT_DISPLAY,
+ false /* isDraggableExpandType */);
mDividerPresenter = new DividerPresenter(
MOCK_TASK_ID, mDragEventCallback, mock(Executor.class));
@@ -348,7 +350,8 @@
dividerWidthPx,
dividerAttributes,
true /* isVerticalSplit */,
- false /* isReversedLayout */));
+ 0 /* minPosition */,
+ 900 /* maxPosition */));
// Top-to-bottom split
when(event.getRawY()).thenReturn(1000f); // Touch event is in display space
@@ -361,7 +364,8 @@
dividerWidthPx,
dividerAttributes,
false /* isVerticalSplit */,
- false /* isReversedLayout */));
+ 0 /* minPosition */,
+ 900 /* maxPosition */));
}
@Test
@@ -453,7 +457,6 @@
final Rect primaryBounds = new Rect(0, 0, 500, 2000);
final Rect secondaryBounds = new Rect(600, 0, 1100, 2000);
final int dividerWidthPx = 100;
- final int dividerPosition = 300;
final TaskFragmentContainer mockPrimaryContainer =
createMockTaskFragmentContainer(mPrimaryContainerToken, primaryBounds);
@@ -462,6 +465,8 @@
when(mSplitContainer.getPrimaryContainer()).thenReturn(mockPrimaryContainer);
when(mSplitContainer.getSecondaryContainer()).thenReturn(mockSecondaryContainer);
+ // Test the normal case
+ int dividerPosition = 300;
assertEquals(
0.3f, // Primary is 300px after dragging.
DividerPresenter.calculateNewSplitRatio(
@@ -470,7 +475,43 @@
taskBounds,
dividerWidthPx,
true /* isVerticalSplit */,
- false /* isReversedLayout */),
+ false /* isReversedLayout */,
+ 200 /* minPosition */,
+ 1000 /* maxPosition */,
+ false /* isDraggingToFullscreenAllowed */),
+ 0.0001 /* delta */);
+
+ // Test the case when dragging to fullscreen is allowed and divider is dragged to the edge
+ dividerPosition = 0;
+ assertEquals(
+ DividerPresenter.RATIO_EXPANDED_SECONDARY,
+ DividerPresenter.calculateNewSplitRatio(
+ mSplitContainer,
+ dividerPosition,
+ taskBounds,
+ dividerWidthPx,
+ true /* isVerticalSplit */,
+ false /* isReversedLayout */,
+ 200 /* minPosition */,
+ 1000 /* maxPosition */,
+ true /* isDraggingToFullscreenAllowed */),
+ 0.0001 /* delta */);
+
+ // Test the case when dragging to fullscreen is not allowed and divider is dragged to the
+ // edge.
+ dividerPosition = 0;
+ assertEquals(
+ 0.2f, // Adjusted to the minPosition 200
+ DividerPresenter.calculateNewSplitRatio(
+ mSplitContainer,
+ dividerPosition,
+ taskBounds,
+ dividerWidthPx,
+ true /* isVerticalSplit */,
+ false /* isReversedLayout */,
+ 200 /* minPosition */,
+ 1000 /* maxPosition */,
+ false /* isDraggingToFullscreenAllowed */),
0.0001 /* delta */);
}
@@ -482,7 +523,6 @@
final Rect primaryBounds = new Rect(0, 0, 2000, 1100);
final Rect secondaryBounds = new Rect(0, 0, 2000, 500);
final int dividerWidthPx = 100;
- final int dividerPosition = 300;
final TaskFragmentContainer mockPrimaryContainer =
createMockTaskFragmentContainer(mPrimaryContainerToken, primaryBounds);
@@ -491,6 +531,8 @@
when(mSplitContainer.getPrimaryContainer()).thenReturn(mockPrimaryContainer);
when(mSplitContainer.getSecondaryContainer()).thenReturn(mockSecondaryContainer);
+ // Test the normal case
+ int dividerPosition = 300;
assertEquals(
// After dragging, secondary is [0, 0, 2000, 300]. Primary is [0, 400, 2000, 1100].
0.7f,
@@ -500,7 +542,46 @@
taskBounds,
dividerWidthPx,
false /* isVerticalSplit */,
- true /* isReversedLayout */),
+ true /* isReversedLayout */,
+ 200 /* minPosition */,
+ 1000 /* maxPosition */,
+ true /* isDraggingToFullscreenAllowed */),
+ 0.0001 /* delta */);
+
+ // Test the case when dragging to fullscreen is not allowed and divider is dragged to the
+ // edge.
+ dividerPosition = 0;
+ assertEquals(
+ // The primary (bottom) container is expanded
+ DividerPresenter.RATIO_EXPANDED_PRIMARY,
+ DividerPresenter.calculateNewSplitRatio(
+ mSplitContainer,
+ dividerPosition,
+ taskBounds,
+ dividerWidthPx,
+ false /* isVerticalSplit */,
+ true /* isReversedLayout */,
+ 200 /* minPosition */,
+ 1000 /* maxPosition */,
+ true /* isDraggingToFullscreenAllowed */),
+ 0.0001 /* delta */);
+
+ // Test the case when dragging to fullscreen is not allowed and divider is dragged to the
+ // edge.
+ dividerPosition = 0;
+ assertEquals(
+ // Adjusted to minPosition 200, so the primary (bottom) container is 800.
+ 0.8f,
+ DividerPresenter.calculateNewSplitRatio(
+ mSplitContainer,
+ dividerPosition,
+ taskBounds,
+ dividerWidthPx,
+ false /* isVerticalSplit */,
+ true /* isReversedLayout */,
+ 200 /* minPosition */,
+ 1000 /* maxPosition */,
+ false /* isDraggingToFullscreenAllowed */),
0.0001 /* delta */);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 28fbadb..b1b1984 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -39,6 +39,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -177,37 +178,59 @@
}
@Test
- public void testGetOverlayContainers() {
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers()).isEmpty();
+ public void testGetAllNonFinishingOverlayContainers() {
+ assertThat(mSplitController.getAllNonFinishingOverlayContainers()).isEmpty();
final TaskFragmentContainer overlayContainer1 =
createTestOverlayContainer(TASK_ID, "test1");
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ assertThat(mSplitController.getAllNonFinishingOverlayContainers())
.containsExactly(overlayContainer1);
- assertThrows(
- "The exception must throw if there are two overlay containers in the same task.",
- IllegalStateException.class,
- () -> createTestOverlayContainer(TASK_ID, "test2"));
+ final TaskFragmentContainer overlayContainer2 =
+ createTestOverlayContainer(TASK_ID, "test2");
+
+ assertThat(mSplitController.getAllNonFinishingOverlayContainers())
+ .containsExactly(overlayContainer1, overlayContainer2);
final TaskFragmentContainer overlayContainer3 =
createTestOverlayContainer(TASK_ID + 1, "test3");
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
- .containsExactly(overlayContainer1, overlayContainer3);
+ assertThat(mSplitController.getAllNonFinishingOverlayContainers())
+ .containsExactly(overlayContainer1, overlayContainer2, overlayContainer3);
+
+ final TaskFragmentContainer finishingOverlayContainer =
+ createTestOverlayContainer(TASK_ID, "test4");
+ spyOn(finishingOverlayContainer);
+ doReturn(true).when(finishingOverlayContainer).isFinished();
+
+ assertThat(mSplitController.getAllNonFinishingOverlayContainers())
+ .containsExactly(overlayContainer1, overlayContainer2, overlayContainer3);
}
@Test
- public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_anotherTagInTask_dismissOverlay() {
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_anotherTagInTask() {
+ createExistingOverlayContainers(false /* visible */);
+ createMockTaskFragmentContainer(mActivity);
+
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test3");
+
+ assertWithMessage("overlayContainer1 is still there since it's not visible.")
+ .that(mSplitController.getAllNonFinishingOverlayContainers())
+ .containsExactly(mOverlayContainer1, mOverlayContainer2, overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlay_visibleOverlaySameTagInTask_dismissOverlay() {
createExistingOverlayContainers();
final TaskFragmentContainer overlayContainer =
createOrUpdateOverlayTaskFragmentIfNeeded("test3");
- assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
- + " is launched to the same task")
- .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ assertWithMessage("overlayContainer1 must be dismissed since it's visible"
+ + " in the same task.")
+ .that(mSplitController.getAllNonFinishingOverlayContainers())
.containsExactly(mOverlayContainer2, overlayContainer);
}
@@ -222,23 +245,24 @@
assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ " is launched with the same tag as an existing overlay container in a different "
+ "task")
- .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .that(mSplitController.getAllNonFinishingOverlayContainers())
.containsExactly(mOverlayContainer2, overlayContainer);
}
@Test
- public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAndTask_updateOverlay() {
+ public void testCreateOrUpdateOverlay_sameTagTaskAndActivity_updateOverlay() {
createExistingOverlayContainers();
final Rect bounds = new Rect(0, 0, 100, 100);
mSplitController.setActivityStackAttributesCalculator(params ->
new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- "test1");
+ mOverlayContainer1.getOverlayTag(),
+ mOverlayContainer1.getTopNonFinishingActivity());
assertWithMessage("overlayContainer1 must be updated since the new overlay container"
+ " is launched with the same tag and task")
- .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .that(mSplitController.getAllNonFinishingOverlayContainers())
.containsExactly(mOverlayContainer1, mOverlayContainer2);
assertThat(overlayContainer).isEqualTo(mOverlayContainer1);
@@ -247,6 +271,37 @@
}
@Test
+ public void testCreateOrUpdateOverlay_sameTagAndTaskButNotActivity_dismissOverlay() {
+ createExistingOverlayContainers();
+
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ mSplitController.setActivityStackAttributesCalculator(params ->
+ new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ mOverlayContainer1.getOverlayTag(), mActivity);
+
+ assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ + " is associated with different launching activity")
+ .that(mSplitController.getAllNonFinishingOverlayContainers())
+ .containsExactly(mOverlayContainer2, overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissOverlay() {
+ createExistingOverlayContainers(false /* visible */);
+ createMockTaskFragmentContainer(mActivity);
+
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test2");
+
+ // OverlayContainer2 is dismissed since new container is launched with the
+ // same tag in different task.
+ assertWithMessage("overlayContainer1 must be dismissed")
+ .that(mSplitController.getAllNonFinishingOverlayContainers())
+ .containsExactly(mOverlayContainer1, overlayContainer);
+ }
+
+ @Test
public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissMultipleOverlays() {
createExistingOverlayContainers();
@@ -257,15 +312,19 @@
// different tag. OverlayContainer2 is dismissed since new container is launched with the
// same tag in different task.
assertWithMessage("overlayContainer1 and overlayContainer2 must be dismissed")
- .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .that(mSplitController.getAllNonFinishingOverlayContainers())
.containsExactly(overlayContainer);
}
private void createExistingOverlayContainers() {
- mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1");
- mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2");
+ createExistingOverlayContainers(true /* visible */);
+ }
+
+ private void createExistingOverlayContainers(boolean visible) {
+ mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1", visible);
+ mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2", visible);
List<TaskFragmentContainer> overlayContainers = mSplitController
- .getAllOverlayTaskFragmentContainers();
+ .getAllNonFinishingOverlayContainers();
assertThat(overlayContainers).containsExactly(mOverlayContainer1, mOverlayContainer2);
}
@@ -294,9 +353,9 @@
new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
final TaskFragmentContainer overlayContainer =
createOrUpdateOverlayTaskFragmentIfNeeded("test");
- setupTaskFragmentInfo(overlayContainer, mActivity);
+ setupTaskFragmentInfo(overlayContainer, mActivity, true /* isVisible */);
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ assertThat(mSplitController.getAllNonFinishingOverlayContainers())
.containsExactly(overlayContainer);
assertThat(overlayContainer.getTaskId()).isEqualTo(TASK_ID);
assertThat(overlayContainer.areLastRequestedBoundsEqual(bounds)).isTrue();
@@ -304,14 +363,16 @@
}
@Test
- public void testGetTopNonFishingTaskFragmentContainerWithOverlay() {
- final TaskFragmentContainer overlayContainer =
- createTestOverlayContainer(TASK_ID, "test1");
-
- // Add a SplitPinContainer, the overlay should be on top
+ public void testGetTopNonFishingTaskFragmentContainerWithoutAssociatedOverlay() {
final Activity primaryActivity = createMockActivity();
final Activity secondaryActivity = createMockActivity();
-
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ mSplitController.setActivityStackAttributesCalculator(params ->
+ new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
+ final TaskFragmentContainer overlayContainer =
+ createTestOverlayContainer(TASK_ID, "test1", true /* isVisible */,
+ false /* shouldAssociateWithActivity */);
+ overlayContainer.setIsolatedNavigationEnabled(true);
final TaskFragmentContainer primaryContainer =
createMockTaskFragmentContainer(primaryActivity);
final TaskFragmentContainer secondaryContainer =
@@ -353,10 +414,10 @@
@Test
public void testGetTopNonFinishingActivityWithOverlay() {
- TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test1");
-
final Activity activity = createMockActivity();
final TaskFragmentContainer container = createMockTaskFragmentContainer(activity);
+ final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID,
+ "test1");
final TaskContainer task = container.getTaskContainer();
assertThat(task.getTopNonFinishingActivity(true /* includeOverlay */))
@@ -373,8 +434,9 @@
}
@Test
- public void testUpdateOverlayContainer_dismissOverlayIfNeeded() {
- TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test");
+ public void testUpdateOverlayContainer_dismissNonAssociatedOverlayIfNeeded() {
+ TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test",
+ true /* isVisible */, false /* associatedLaunchingActivity */);
mSplitController.updateOverlayContainer(mTransaction, overlayContainer);
@@ -443,11 +505,10 @@
@Test
public void testOnTaskFragmentParentInfoChanged_positionOnlyChange_earlyReturn() {
- final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test");
+ final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test",
+ true /* isVisible */, false /* associatedLaunchingActivity */);
final TaskContainer taskContainer = overlayContainer.getTaskContainer();
- assertThat(taskContainer.getOverlayContainer()).isEqualTo(overlayContainer);
-
spyOn(taskContainer);
final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties();
final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(
@@ -463,16 +524,15 @@
assertWithMessage("The overlay container must still be dismissed even if "
+ "#updateContainer is not called")
- .that(taskContainer.getOverlayContainer()).isNull();
+ .that(taskContainer.getTaskFragmentContainers()).isEmpty();
}
@Test
- public void testOnTaskFragmentParentInfoChanged_invisibleTask_callDismissOverlayContainer() {
- final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test");
+ public void testOnTaskFragmentParentInfoChanged_invisibleTask_callDismissNonAssocOverlay() {
+ final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test",
+ true /* isVisible */, false /* associatedLaunchingActivity */);
final TaskContainer taskContainer = overlayContainer.getTaskContainer();
- assertThat(taskContainer.getOverlayContainer()).isEqualTo(overlayContainer);
-
spyOn(taskContainer);
final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties();
final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(
@@ -487,7 +547,7 @@
assertWithMessage("The overlay container must still be dismissed even if "
+ "#updateContainer is not called")
- .that(taskContainer.getOverlayContainer()).isNull();
+ .that(taskContainer.getTaskFragmentContainers()).isEmpty();
}
@Test
@@ -510,7 +570,7 @@
}
@Test
- public void testApplyActivityStackAttributesForOverlayContainer() {
+ public void testApplyActivityStackAttributesForOverlayContainerAssociatedWithActivity() {
final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
final IBinder token = container.getTaskFragmentToken();
final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder()
@@ -527,7 +587,35 @@
WINDOWING_MODE_MULTI_WINDOW);
verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
TaskFragmentAnimationParams.DEFAULT);
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, true);
+ // Set isolated navigation to false if the overlay container is associated with
+ // the launching activity.
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true);
+ }
+
+ @Test
+ public void testApplyActivityStackAttributesForOverlayContainerWithoutAssociatedActivity() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG,
+ true, /* isVisible */ false /* associatedWithLaunchingActivity */);
+ final IBinder token = container.getTaskFragmentToken();
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder()
+ .setRelativeBounds(new Rect(0, 0, 200, 200))
+ .setWindowAttributes(new WindowAttributes(DIM_AREA_ON_TASK))
+ .build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container,
+ attributes, null /* minDimensions */);
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ attributes.getRelativeBounds());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction,
+ container, WINDOWING_MODE_MULTI_WINDOW);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ // Set isolated navigation to false if the overlay container is associated with
+ // the launching activity.
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction,
+ container, true);
verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true);
}
@@ -563,8 +651,7 @@
mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
new Size(relativeBounds.width() + 1, relativeBounds.height()));
- verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
- new Rect());
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container, new Rect());
verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
WINDOWING_MODE_UNDEFINED);
verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
@@ -573,16 +660,65 @@
verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
}
+ @Test
+ public void testFinishSelfWithActivityIfNeeded() {
+ TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+
+ container.finishSelfWithActivityIfNeeded(mTransaction, mActivity.getActivityToken());
+
+ verify(mSplitPresenter, never()).cleanupContainer(any(), any(), anyBoolean());
+
+ TaskFragmentContainer overlayWithoutAssociation = createTestOverlayContainer(TASK_ID,
+ "test", false /* associateLaunchingActivity */);
+
+ overlayWithoutAssociation.finishSelfWithActivityIfNeeded(mTransaction,
+ mActivity.getActivityToken());
+
+ verify(mSplitPresenter, never()).cleanupContainer(any(), any(), anyBoolean());
+ assertThat(mSplitController.getAllNonFinishingOverlayContainers())
+ .contains(overlayWithoutAssociation);
+
+ TaskFragmentContainer overlayWithAssociation =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test");
+ overlayWithAssociation.setInfo(mTransaction, createMockTaskFragmentInfo(
+ overlayWithAssociation, mActivity, true /* isVisible */));
+ assertThat(mSplitController.getAllNonFinishingOverlayContainers())
+ .contains(overlayWithAssociation);
+ clearInvocations(mSplitPresenter);
+
+ overlayWithAssociation.finishSelfWithActivityIfNeeded(mTransaction, new Binder());
+
+ verify(mSplitPresenter, never()).cleanupContainer(any(), any(), anyBoolean());
+
+ overlayWithAssociation.finishSelfWithActivityIfNeeded(mTransaction,
+ mActivity.getActivityToken());
+
+ verify(mSplitPresenter).cleanupContainer(mTransaction, overlayWithAssociation, false);
+
+ assertThat(mSplitController.getAllNonFinishingOverlayContainers())
+ .doesNotContain(overlayWithAssociation);
+ }
+
/**
- * A simplified version of {@link SplitController.ActivityStartMonitor
- * #createOrUpdateOverlayTaskFragmentIfNeeded}
+ * A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded}
*/
@Nullable
private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(@NonNull String tag) {
final Bundle launchOptions = new Bundle();
launchOptions.putString(KEY_OVERLAY_TAG, tag);
+ return createOrUpdateOverlayTaskFragmentIfNeeded(tag, mActivity);
+ }
+
+ /**
+ * A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded}
+ */
+ @Nullable
+ private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+ @NonNull String tag, @NonNull Activity activity) {
+ final Bundle launchOptions = new Bundle();
+ launchOptions.putString(KEY_OVERLAY_TAG, tag);
return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction,
- launchOptions, mIntent, mActivity);
+ launchOptions, mIntent, activity);
}
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
@@ -590,23 +726,41 @@
private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
final TaskFragmentContainer container = mSplitController.newContainer(activity,
activity.getTaskId());
- setupTaskFragmentInfo(container, activity);
+ setupTaskFragmentInfo(container, activity, false /* isVisible */);
return container;
}
@NonNull
private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
+ return createTestOverlayContainer(taskId, tag, false /* isVisible */,
+ true /* associateLaunchingActivity */);
+ }
+
+ @NonNull
+ private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag,
+ boolean isVisible) {
+ return createTestOverlayContainer(taskId, tag, isVisible,
+ true /* associateLaunchingActivity */);
+ }
+
+ // TODO(b/243518738): add more test coverage on overlay container without activity association
+ // once we have use cases.
+ @NonNull
+ private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag,
+ boolean isVisible, boolean associateLaunchingActivity) {
Activity activity = createMockActivity();
TaskFragmentContainer overlayContainer = mSplitController.newContainer(
null /* pendingAppearedActivity */, mIntent, activity, taskId,
- null /* pairedPrimaryContainer */, tag, Bundle.EMPTY);
- setupTaskFragmentInfo(overlayContainer, activity);
+ null /* pairedPrimaryContainer */, tag, Bundle.EMPTY,
+ associateLaunchingActivity);
+ setupTaskFragmentInfo(overlayContainer, activity, isVisible);
return overlayContainer;
}
private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
- @NonNull Activity activity) {
- final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
+ @NonNull Activity activity,
+ boolean isVisible) {
+ final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity, isVisible);
container.setInfo(mTransaction, info);
mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index c246a19..3441c2b 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -227,13 +227,13 @@
// When the activity is not finishing, do not clear the record.
doReturn(false).when(mActivity).isFinishing();
- mSplitController.onActivityDestroyed(mActivity);
+ mSplitController.onActivityDestroyed(mTransaction, mActivity);
assertTrue(tf.hasActivity(mActivity.getActivityToken()));
// Clear the record when the activity is finishing and destroyed.
doReturn(true).when(mActivity).isFinishing();
- mSplitController.onActivityDestroyed(mActivity);
+ mSplitController.onActivityDestroyed(mTransaction, mActivity);
assertFalse(tf.hasActivity(mActivity.getActivityToken()));
}
@@ -612,7 +612,7 @@
assertFalse(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
- anyString(), any());
+ anyString(), any(), anyBoolean());
}
@Test
@@ -775,7 +775,7 @@
assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
- anyString(), any());
+ anyString(), any(), anyBoolean());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -818,7 +818,7 @@
assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
- anyString(), any());
+ anyString(), any(), anyBoolean());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index cc00a49..0af4179 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -402,7 +402,7 @@
assertTrue(container.hasActivity(mActivity.getActivityToken()));
- taskContainer.onActivityDestroyed(mActivity.getActivityToken());
+ taskContainer.onActivityDestroyed(mTransaction, mActivity.getActivityToken());
// It should not contain the destroyed Activity.
assertFalse(container.hasActivity(mActivity.getActivityToken()));
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 36d3313..7a98683 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -23,4 +23,12 @@
<uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.WAKEUP_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
+
+ <application>
+ <activity
+ android:name=".desktopmode.DesktopWallpaperActivity"
+ android:excludeFromRecents="true"
+ android:launchMode="singleInstance"
+ android:theme="@style/DesktopWallpaperTheme" />
+ </application>
</manifest>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 4ee2c1a..c2c90c8 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -515,8 +515,20 @@
<!-- The size of the icon shown in the resize veil. -->
<dimen name="desktop_mode_resize_veil_icon_size">96dp</dimen>
+ <!-- The with of the border around the app task for edge resizing, when
+ enable_windowing_edge_drag_resize is enabled. -->
+ <dimen name="desktop_mode_edge_handle">12dp</dimen>
+
+ <!-- The original width of the border around the app task for edge resizing, when
+ enable_windowing_edge_drag_resize is disabled. -->
<dimen name="freeform_resize_handle">15dp</dimen>
+ <!-- The size of the corner region for drag resizing with touch, when a larger touch region is
+ appropriate. Applied when enable_windowing_edge_drag_resize is enabled. -->
+ <dimen name="desktop_mode_corner_resize_large">48dp</dimen>
+
+ <!-- The original size of the corner region for darg resizing, when
+ enable_windowing_edge_drag_resize is disabled. -->
<dimen name="freeform_resize_corner">44dp</dimen>
<!-- The width of the area at the sides of the screen where a freeform task will transition to
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 08c2a02..13c0e66 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -23,6 +23,14 @@
<item name="android:windowAnimationStyle">@style/Animation.ForcedResizable</item>
</style>
+ <!-- Theme used for the activity that shows below the desktop mode windows to show wallpaper -->
+ <style name="DesktopWallpaperTheme" parent="@android:style/Theme.Wallpaper.NoTitleBar">
+ <item name="android:statusBarColor">@android:color/transparent</item>
+ <item name="android:navigationBarColor">@android:color/transparent</item>
+ <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+ <item name="android:windowAnimationStyle">@null</item>
+ </style>
+
<style name="Animation.ForcedResizable" parent="@android:style/Animation">
<item name="android:activityOpenEnterAnimation">@anim/forced_resizable_enter</item>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index a32b435..4988a94 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -28,6 +28,7 @@
import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.Cuj.CujType;
import com.android.wm.shell.common.InteractionJankMonitorUtils;
@@ -108,7 +109,8 @@
}
}
- private boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
+ @VisibleForTesting
+ boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
return apps.length > 0 && mCujType != NO_CUJ;
}
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 b933e5d..1408ead 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
@@ -59,6 +59,7 @@
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver;
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
@@ -241,6 +242,7 @@
mainChoreographer,
taskOrganizer,
displayController,
+ rootTaskDisplayAreaOrganizer,
syncQueue,
transitions);
}
@@ -568,6 +570,18 @@
@WMSingleton
@Provides
+ static Optional<DesktopTasksTransitionObserver> provideDesktopTasksTransitionObserver(
+ Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+ Transitions transitions,
+ ShellInit shellInit
+ ) {
+ return desktopModeTaskRepository.flatMap(repository ->
+ Optional.of(new DesktopTasksTransitionObserver(repository, transitions, shellInit))
+ );
+ }
+
+ @WMSingleton
+ @Provides
static DesktopModeLoggerTransitionObserver provideDesktopModeLoggerTransitionObserver(
ShellInit shellInit,
Transitions transitions,
@@ -622,7 +636,8 @@
@Provides
static Object provideIndependentShellComponentsToCreate(
DragAndDropController dragAndDropController,
- DefaultMixedHandler defaultMixedHandler) {
+ DefaultMixedHandler defaultMixedHandler,
+ Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional) {
return new Object();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index e1e41ee..f1a475a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -36,7 +36,14 @@
true
}
}
-
+ "moveToNextDisplay" -> {
+ if (!runMoveToNextDisplay(args, pw)) {
+ pw.println("Task not found. Please enter a valid taskId.")
+ false
+ } else {
+ true
+ }
+ }
else -> {
pw.println("Invalid command: ${args[0]}")
false
@@ -61,8 +68,28 @@
return controller.moveToDesktop(taskId, WindowContainerTransaction())
}
+ private fun runMoveToNextDisplay(args: Array<String>, pw: PrintWriter): Boolean {
+ if (args.size < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments")
+ return false
+ }
+
+ val taskId = try {
+ args[1].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: task id should be an integer")
+ return false
+ }
+
+ controller.moveToNextDisplay(taskId)
+ return true
+ }
+
override fun printShellCommandHelp(pw: PrintWriter, prefix: String) {
pw.println("$prefix moveToDesktop <taskId> ")
pw.println("$prefix Move a task with given id to desktop mode.")
+ pw.println("$prefix moveToNextDisplay <taskId> ")
+ pw.println("$prefix Move a task with given id to next display.")
}
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 120d681..50cea01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -22,6 +22,7 @@
import android.util.ArraySet
import android.util.SparseArray
import android.view.Display.INVALID_DISPLAY
+import android.window.WindowContainerToken
import androidx.core.util.forEach
import androidx.core.util.keyIterator
import androidx.core.util.valueIterator
@@ -49,6 +50,8 @@
var stashed: Boolean = false
)
+ // Token of the current wallpaper activity, used to remove it when the last task is removed
+ var wallpaperActivityToken: WindowContainerToken? = null
// Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
private val freeformTasksInZOrder = mutableListOf<Int>()
private val activeTasksListeners = ArraySet<ActiveTasksListener>()
@@ -200,6 +203,15 @@
}
/**
+ * Check if a task with the given [taskId] is the only active task on its display
+ */
+ fun isOnlyActiveTask(taskId: Int): Boolean {
+ return displayData.valueIterator().asSequence().any { data ->
+ data.activeTasks.singleOrNull() == taskId
+ }
+ }
+
+ /**
* Get a set of the active tasks for given [displayId]
*/
fun getActiveTasks(displayId: Int): ArraySet<Int> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 58942ec..068661a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -40,6 +40,7 @@
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.RemoteTransition
import android.window.TransitionInfo
@@ -381,7 +382,6 @@
)
val wct = WindowContainerTransaction()
exitSplitIfApplicable(wct, taskInfo)
- moveHomeTaskToFront(wct)
bringDesktopAppsToFront(taskInfo.displayId, wct)
addMoveToDesktopChanges(wct, taskInfo)
wct.setBounds(taskInfo.token, freeformBounds)
@@ -401,6 +401,22 @@
shellTaskOrganizer.applyTransaction(wct)
}
+ /**
+ * Perform clean up of the desktop wallpaper activity if the closed window task is
+ * the last active task.
+ *
+ * @param wct transaction to modify if the last active task is closed
+ * @param taskId task id of the window that's being closed
+ */
+ fun onDesktopWindowClose(
+ wct: WindowContainerTransaction,
+ taskId: Int
+ ) {
+ if (desktopModeTaskRepository.isOnlyActiveTask(taskId)) {
+ removeWallpaperActivity(wct)
+ }
+ }
+
/** Move a task with given `taskId` to fullscreen */
fun moveToFullscreen(taskId: Int) {
shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task ->
@@ -588,7 +604,7 @@
if (taskBoundsBeforeMaximize != null) {
destinationBounds.set(taskBoundsBeforeMaximize)
} else {
- getDefaultDesktopTaskBounds(displayLayout, destinationBounds)
+ destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout))
}
} else {
// Save current bounds so that task can be restored back to original bounds if necessary
@@ -624,18 +640,14 @@
}
}
- private fun getDefaultDesktopTaskBounds(displayLayout: DisplayLayout, outBounds: Rect) {
+ private fun getDefaultDesktopTaskBounds(displayLayout: DisplayLayout): Rect {
// TODO(b/319819547): Account for app constraints so apps do not become letterboxed
- val screenBounds = Rect(0, 0, displayLayout.width(), displayLayout.height())
- // Update width and height with default desktop mode values
- val desiredWidth = screenBounds.width().times(DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
- val desiredHeight = screenBounds.height().times(DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
- outBounds.set(0, 0, desiredWidth, desiredHeight)
- // Center the task in screen bounds
- outBounds.offset(
- screenBounds.centerX() - outBounds.centerX(),
- screenBounds.centerY() - outBounds.centerY()
- )
+ val desiredWidth = (displayLayout.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
+ val desiredHeight = (displayLayout.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
+ val heightOffset = (displayLayout.height() - desiredHeight) / 2
+ val widthOffset = (displayLayout.width() - desiredWidth) / 2
+ return Rect(widthOffset, heightOffset,
+ desiredWidth + widthOffset, desiredHeight + heightOffset)
}
private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect {
@@ -680,9 +692,15 @@
KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: bringDesktopAppsToFront")
val activeTasks = desktopModeTaskRepository.getActiveTasks(displayId)
- // First move home to front and then other tasks on top of it
- moveHomeTaskToFront(wct)
+ if (Flags.enableDesktopWindowingWallpaperActivity()) {
+ // Add translucent wallpaper activity to show the wallpaper underneath
+ addWallpaperActivity(wct)
+ } else {
+ // Move home to front
+ moveHomeTaskToFront(wct)
+ }
+ // Then move other tasks on top of it
val allTasksInZOrder = desktopModeTaskRepository.getFreeformTasksInZOrder()
activeTasks
// Sort descending as the top task is at index 0. It should be ordered to top last
@@ -698,6 +716,26 @@
?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) }
}
+ private fun addWallpaperActivity(wct: WindowContainerTransaction) {
+ KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: addWallpaper")
+ val intent = Intent(context, DesktopWallpaperActivity::class.java)
+ val options = ActivityOptions.makeBasic().apply {
+ isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
+ pendingIntentBackgroundActivityStartMode =
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ }
+ val pendingIntent = PendingIntent.getActivity(context, /* requestCode = */ 0, intent,
+ PendingIntent.FLAG_IMMUTABLE)
+ wct.sendPendingIntent(pendingIntent, intent, options.toBundle())
+ }
+
+ private fun removeWallpaperActivity(wct: WindowContainerTransaction) {
+ desktopModeTaskRepository.wallpaperActivityToken?.let { token ->
+ KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: removeWallpaper")
+ wct.removeTask(token)
+ }
+ }
+
fun releaseVisualIndicator() {
val t = SurfaceControl.Transaction()
visualIndicator?.releaseVisualIndicator(t)
@@ -745,6 +783,9 @@
reason = "recents animation is running"
false
}
+ // Handle back navigation for the last window if wallpaper available
+ shouldRemoveWallpaper(request) ->
+ true
// Only handle open or to front transitions
request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> {
reason = "transition type not handled (${request.type})"
@@ -781,6 +822,7 @@
val result = triggerTask?.let { task ->
when {
+ request.type == TRANSIT_TO_BACK -> handleBackNavigation(task)
// If display has tasks stashed, handle as stashed launch
task.isStashed -> handleStashedTaskLaunch(task)
// Check if the task has a top transparent activity
@@ -828,6 +870,14 @@
return Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)
}
+ private fun shouldRemoveWallpaper(request: TransitionRequestInfo): Boolean {
+ return Flags.enableDesktopWindowingWallpaperActivity() &&
+ request.type == TRANSIT_TO_BACK &&
+ request.triggerTask?.let { task ->
+ desktopModeTaskRepository.isOnlyActiveTask(task.taskId)
+ } ?: false
+ }
+
private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
@@ -885,12 +935,26 @@
}
}
+ /** Handle back navigation by removing wallpaper activity if it's the last active task */
+ private fun handleBackNavigation(task: RunningTaskInfo): WindowContainerTransaction? {
+ if (desktopModeTaskRepository.isOnlyActiveTask(task.taskId) &&
+ desktopModeTaskRepository.wallpaperActivityToken != null) {
+ // Remove wallpaper activity when the last active task is removed
+ return WindowContainerTransaction().also { wct ->
+ removeWallpaperActivity(wct)
+ }
+ } else {
+ return null
+ }
+ }
+
private fun addMoveToDesktopChanges(
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo
) {
- val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode
- val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FREEFORM) {
+ val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
+ val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
+ val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) {
// Display windowing is freeform, set to undefined and inherit it
WINDOWING_MODE_UNDEFINED
} else {
@@ -907,8 +971,9 @@
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo
) {
- val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode
- val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
+ val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
+ val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FULLSCREEN) {
// Display windowing is fullscreen, set to undefined and inherit it
WINDOWING_MODE_UNDEFINED
} else {
@@ -1090,17 +1155,14 @@
* @param taskInfo the task being dragged.
* @param y height of drag, to be checked against status bar height.
*/
- fun onDragPositioningEndThroughStatusBar(
- inputCoordinates: PointF,
- taskInfo: RunningTaskInfo,
- freeformBounds: Rect
- ) {
+ fun onDragPositioningEndThroughStatusBar(inputCoordinates: PointF, taskInfo: RunningTaskInfo) {
val indicator = visualIndicator ?: return
val indicatorType = indicator
.updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
when (indicatorType) {
DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
- finalizeDragToDesktop(taskInfo, freeformBounds)
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+ finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
}
DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
new file mode 100644
index 0000000..20df264
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.os.IBinder
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.util.KtProtoLog
+
+/**
+ * A [Transitions.TransitionObserver] that observes shell transitions and updates
+ * the [DesktopModeTaskRepository] state TODO: b/332682201
+ * This observes transitions related to desktop mode
+ * and other transitions that originate both within and outside shell.
+ */
+class DesktopTasksTransitionObserver(
+ private val desktopModeTaskRepository: DesktopModeTaskRepository,
+ private val transitions: Transitions,
+ shellInit: ShellInit
+) : Transitions.TransitionObserver {
+
+ init {
+ if (Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.isEnabled()) {
+ shellInit.addInitCallback(::onInit, this)
+ }
+ }
+
+ fun onInit() {
+ KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTasksTransitionObserver: onInit")
+ transitions.registerObserver(this)
+ }
+
+ override fun onTransitionReady(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction
+ ) {
+ // TODO: b/332682201 Update repository state
+ updateWallpaperToken(info)
+ }
+
+ override fun onTransitionStarting(transition: IBinder) {
+ // TODO: b/332682201 Update repository state
+ }
+
+ override fun onTransitionMerged(merged: IBinder, playing: IBinder) {
+ // TODO: b/332682201 Update repository state
+ }
+
+ override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
+ // TODO: b/332682201 Update repository state
+ }
+
+ private fun updateWallpaperToken(info: TransitionInfo) {
+ if (!enableDesktopWindowingWallpaperActivity()) {
+ return
+ }
+ info.changes.forEach { change ->
+ change.taskInfo?.let { taskInfo ->
+ if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) {
+ when (change.mode) {
+ WindowManager.TRANSIT_OPEN ->
+ desktopModeTaskRepository.wallpaperActivityToken = taskInfo.token
+ WindowManager.TRANSIT_CLOSE ->
+ desktopModeTaskRepository.wallpaperActivityToken = null
+ else -> {}
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
new file mode 100644
index 0000000..c4a4474
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.Activity
+import android.app.ActivityManager
+import android.content.ComponentName
+import android.os.Bundle
+import android.view.WindowManager
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.util.KtProtoLog
+
+/**
+ * A transparent activity used in the desktop mode to show the wallpaper under the freeform windows.
+ * This activity will be running in `FULLSCREEN` windowing mode, which ensures it hides Launcher.
+ * When entering desktop, we would ensure that it's added behind desktop apps and removed when
+ * leaving the desktop mode.
+ *
+ * Note! This activity should NOT interact directly with any other code in the Shell without calling
+ * onto the shell main thread. Activities are always started on the main thread.
+ */
+class DesktopWallpaperActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopWallpaperActivity: onCreate")
+ super.onCreate(savedInstanceState)
+ window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
+ }
+
+ companion object {
+ private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
+ private val wallpaperActivityComponent =
+ ComponentName(SYSTEM_UI_PACKAGE_NAME, DesktopWallpaperActivity::class.java.name)
+
+ @JvmStatic
+ fun isWallpaperTask(taskInfo: ActivityManager.RunningTaskInfo) =
+ taskInfo.baseIntent.component?.let(::isWallpaperComponent) ?: false
+
+ @JvmStatic
+ fun isWallpaperComponent(component: ComponentName) =
+ component == wallpaperActivityComponent
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index c16eac8..57cf992 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -96,6 +96,7 @@
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
@@ -522,9 +523,27 @@
mTaskOrganizer.reparentChildSurfaceToTask(taskId, overlay, t);
t.setLayer(overlay, Integer.MAX_VALUE);
t.apply();
+ // This serves as a last resort in case the Shell Transition is not handled properly.
+ // We want to make sure the overlay passed from Launcher gets removed eventually.
+ mayRemoveContentOverlay(overlay);
}
}
+ private void mayRemoveContentOverlay(SurfaceControl overlay) {
+ final WeakReference<SurfaceControl> overlayRef = new WeakReference<>(overlay);
+ final long timeoutDuration = (mEnterAnimationDuration
+ + CONTENT_OVERLAY_FADE_OUT_DELAY_MS
+ + EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS) * 2L;
+ mMainExecutor.executeDelayed(() -> {
+ final SurfaceControl overlayLeash = overlayRef.get();
+ if (overlayLeash != null && overlayLeash.isValid() && overlayLeash == mPipOverlay) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "Cleanup the overlay(%s) as a last resort.", overlayLeash);
+ removeContentOverlay(overlayLeash, null /* callback */);
+ }
+ }, timeoutDuration);
+ }
+
/**
* Callback when launcher aborts swipe-pip-to-home operation.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index a454d48..e829d4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -17,8 +17,10 @@
package com.android.wm.shell.pip2.phone;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
@@ -182,6 +184,10 @@
mResizeTransition = null;
return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback);
}
+
+ if (isRemovePipTransition(info)) {
+ return removePipImmediately(info, startTransaction, finishTransaction, finishCallback);
+ }
return false;
}
@@ -291,6 +297,10 @@
startOverlayFadeoutAnimation();
}
+ //
+ // Subroutines setting up and starting transitions' animations.
+ //
+
private void startOverlayFadeoutAnimation() {
ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
@@ -326,6 +336,7 @@
mPipScheduler.setPipTaskToken(mPipTaskToken);
startTransaction.apply();
+ // TODO: b/275910498 Use a new implementation of the PiP animator here.
finishCallback.onTransitionFinished(null);
return true;
}
@@ -353,11 +364,26 @@
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
startTransaction.apply();
+ // TODO: b/275910498 Use a new implementation of the PiP animator here.
finishCallback.onTransitionFinished(null);
onExitPip();
return true;
}
+ private boolean removePipImmediately(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ onExitPip();
+ return true;
+ }
+
+ //
+ // Utility methods for checking PiP-related transition info and requests.
+ //
+
@Nullable
private TransitionInfo.Change getPipChange(TransitionInfo info) {
for (TransitionInfo.Change change : info.getChanges()) {
@@ -415,6 +441,25 @@
&& info.getChanges().size() == 1;
}
+ private boolean isRemovePipTransition(@NonNull TransitionInfo info) {
+ if (mPipTaskToken == null) {
+ // PiP removal makes sense if enter-PiP has cached a valid pinned task token.
+ return false;
+ }
+ TransitionInfo.Change pipChange = info.getChange(mPipTaskToken);
+ if (pipChange == null) {
+ // Search for the PiP change by token since the windowing mode might be FULLSCREEN now.
+ return false;
+ }
+
+ boolean isPipMovedToBack = info.getType() == TRANSIT_TO_BACK
+ && pipChange.getMode() == TRANSIT_TO_BACK;
+ boolean isPipClosed = info.getType() == TRANSIT_CLOSE
+ && pipChange.getMode() == TRANSIT_CLOSE;
+ // PiP is being removed if the pinned task is either moved to back or closed.
+ return isPipMovedToBack || isPipClosed;
+ }
+
/**
* TODO: b/275910498 Use a new implementation of the PiP animator here.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
index e8f58fe..62d195e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
@@ -37,4 +37,9 @@
* Called when a running task vanishes.
*/
void onRunningTaskVanished(in RunningTaskInfo taskInfo);
+
+ /**
+ * Called when a running task changes.
+ */
+ void onRunningTaskChanged(in RunningTaskInfo taskInfo);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index eebd133..77b8663 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -16,6 +16,9 @@
package com.android.wm.shell.recents;
+import android.annotation.Nullable;
+import android.graphics.Color;
+
import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
@@ -40,4 +43,12 @@
*/
default void addAnimationStateListener(Executor listenerExecutor, Consumer<Boolean> listener) {
}
+
+ /**
+ * Sets a background color on the transition root layered behind the outgoing task. {@code null}
+ * may be used to clear any previously set colors to avoid showing a background at all. The
+ * color is always shown at full opacity.
+ */
+ default void setTransitionBackgroundColor(@Nullable Color color) {
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index f9fcfac..e7d9812 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -19,6 +19,7 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.pm.PackageManager.FEATURE_PC;
+import static com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
@@ -26,10 +27,10 @@
import android.app.ActivityTaskManager;
import android.app.IApplicationThread;
import android.app.PendingIntent;
-import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Color;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Slog;
@@ -86,7 +87,7 @@
private final ActivityTaskManager mActivityTaskManager;
private RecentsTransitionHandler mTransitionHandler = null;
private IRecentTasksListener mListener;
- private final boolean mIsDesktopMode;
+ private final boolean mPcFeatureEnabled;
// Mapping of split task ids, mappings are symmetrical (ie. if t1 is the taskid of a task in a
// pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1)
@@ -133,7 +134,7 @@
mShellController = shellController;
mShellCommandHandler = shellCommandHandler;
mActivityTaskManager = activityTaskManager;
- mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
+ mPcFeatureEnabled = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
mTaskStackListener = taskStackListener;
mDesktopModeTaskRepository = desktopModeTaskRepository;
mMainExecutor = mainExecutor;
@@ -252,8 +253,10 @@
notifyRunningTaskVanished(taskInfo);
}
- public void onTaskWindowingModeChanged(TaskInfo taskInfo) {
+ /** Notify listeners that the windowing mode of the given Task was updated. */
+ public void onTaskWindowingModeChanged(ActivityManager.RunningTaskInfo taskInfo) {
notifyRecentTasksChanged();
+ notifyRunningTaskChanged(taskInfo);
}
@Override
@@ -278,7 +281,9 @@
* Notify the running task listener that a task appeared on desktop environment.
*/
private void notifyRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
- if (mListener == null || !mIsDesktopMode || taskInfo.realActivity == null) {
+ if (mListener == null
+ || !shouldEnableRunningTasksForDesktopMode()
+ || taskInfo.realActivity == null) {
return;
}
try {
@@ -292,7 +297,9 @@
* Notify the running task listener that a task was removed on desktop environment.
*/
private void notifyRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- if (mListener == null || !mIsDesktopMode || taskInfo.realActivity == null) {
+ if (mListener == null
+ || !shouldEnableRunningTasksForDesktopMode()
+ || taskInfo.realActivity == null) {
return;
}
try {
@@ -302,6 +309,27 @@
}
}
+ /**
+ * Notify the running task listener that a task was changed on desktop environment.
+ */
+ private void notifyRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mListener == null
+ || !shouldEnableRunningTasksForDesktopMode()
+ || taskInfo.realActivity == null) {
+ return;
+ }
+ try {
+ mListener.onRunningTaskChanged(taskInfo);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed call onRunningTaskChanged", e);
+ }
+ }
+
+ private boolean shouldEnableRunningTasksForDesktopMode() {
+ return mPcFeatureEnabled
+ || (DesktopModeStatus.isEnabled() && enableDesktopWindowingTaskbarRunningApps());
+ }
+
@VisibleForTesting
void registerRecentTasksListener(IRecentTasksListener listener) {
mListener = listener;
@@ -449,6 +477,16 @@
});
});
}
+
+ @Override
+ public void setTransitionBackgroundColor(@Nullable Color color) {
+ mMainExecutor.execute(() -> {
+ if (mTransitionHandler == null) {
+ return;
+ }
+ mTransitionHandler.setTransitionBackgroundColor(color);
+ });
+ }
}
@@ -476,6 +514,11 @@
public void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
mListener.call(l -> l.onRunningTaskVanished(taskInfo));
}
+
+ @Override
+ public void onRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ mListener.call(l -> l.onRunningTaskChanged(taskInfo));
+ }
};
public IRecentTasksImpl(RecentTasksController controller) {
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 24cf370..c625b69 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
@@ -36,6 +36,7 @@
import android.app.IApplicationThread;
import android.app.PendingIntent;
import android.content.Intent;
+import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -56,6 +57,8 @@
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.common.ProtoLog;
@@ -92,6 +95,7 @@
private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>();
private final HomeTransitionObserver mHomeTransitionObserver;
+ private @Nullable Color mBackgroundColor;
public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
@Nullable RecentTasksController recentTasksController,
@@ -123,6 +127,15 @@
mStateListeners.add(listener);
}
+ /**
+ * Sets a background color on the transition root layered behind the outgoing task. {@code null}
+ * may be used to clear any previously set colors to avoid showing a background at all. The
+ * color is always shown at full opacity.
+ */
+ public void setTransitionBackgroundColor(@Nullable Color color) {
+ mBackgroundColor = color;
+ }
+
@VisibleForTesting
public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
IApplicationThread appThread, IRecentsAnimationRunner listener) {
@@ -469,6 +482,16 @@
final int belowLayers = info.getChanges().size();
final int middleLayers = info.getChanges().size() * 2;
final int aboveLayers = info.getChanges().size() * 3;
+
+ // Add a background color to each transition root in this transition.
+ if (mBackgroundColor != null) {
+ info.getChanges().stream()
+ .mapToInt((change) -> TransitionUtil.rootIndexFor(change, info))
+ .distinct()
+ .mapToObj((rootIndex) -> info.getRoot(rootIndex).getLeash())
+ .forEach((root) -> createBackgroundSurface(t, root, middleLayers));
+ }
+
for (int i = 0; i < info.getChanges().size(); ++i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
@@ -1107,6 +1130,29 @@
return true;
}
+ private void createBackgroundSurface(SurfaceControl.Transaction transaction,
+ SurfaceControl parent, int layer) {
+ if (mBackgroundColor == null) {
+ return;
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " adding background color to layer=%d", layer);
+ final SurfaceControl background = new SurfaceControl.Builder()
+ .setName("recents_background")
+ .setColorLayer()
+ .setOpaque(true)
+ .setParent(parent)
+ .build();
+ transaction.setColor(background, colorToFloatArray(mBackgroundColor));
+ transaction.setLayer(background, layer);
+ transaction.setAlpha(background, 1F);
+ transaction.show(background);
+ }
+
+ private static float[] colorToFloatArray(@NonNull Color color) {
+ return new float[]{color.red(), color.green(), color.blue()};
+ }
+
private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct,
SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) {
if (!sendUserLeaveHint && task.isLeaf()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index c9185ae..b1a1e59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -20,6 +20,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP;
import static com.android.wm.shell.transition.Transitions.TransitionObserver;
import android.annotation.NonNull;
@@ -60,7 +61,8 @@
@NonNull SurfaceControl.Transaction finishTransaction) {
for (TransitionInfo.Change change : info.getChanges()) {
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo == null
+ if (info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
+ || taskInfo == null
|| taskInfo.displayId != DEFAULT_DISPLAY
|| taskInfo.taskId == -1
|| !taskInfo.isRunning) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index c59a1b4..87dc391 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -19,23 +19,30 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.PackageManager.FEATURE_PC;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
import static android.view.WindowManager.TRANSIT_CHANGE;
import android.app.ActivityManager.RunningTaskInfo;
+import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Rect;
import android.os.Handler;
+import android.provider.Settings;
import android.util.SparseArray;
import android.view.Choreographer;
+import android.view.Display;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
+import android.window.DisplayAreaInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.wm.shell.R;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -53,6 +60,7 @@
private final Handler mMainHandler;
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final SyncTransactionQueue mSyncQueue;
private final Transitions mTransitions;
private TaskOperations mTaskOperations;
@@ -65,6 +73,7 @@
Choreographer mainChoreographer,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
SyncTransactionQueue syncQueue,
Transitions transitions) {
mContext = context;
@@ -72,6 +81,7 @@
mMainChoreographer = mainChoreographer;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mSyncQueue = syncQueue;
mTransitions = transitions;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -158,10 +168,33 @@
}
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
- return taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- || (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
- && taskInfo.configuration.windowConfiguration.getDisplayWindowingMode()
- == WINDOWING_MODE_FREEFORM);
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ return true;
+ }
+ if (taskInfo.getActivityType() != ACTIVITY_TYPE_STANDARD) {
+ return false;
+ }
+ final DisplayAreaInfo rootDisplayAreaInfo =
+ mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId);
+ if (rootDisplayAreaInfo != null) {
+ return rootDisplayAreaInfo.configuration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FREEFORM;
+ }
+
+ // It is possible that the rootDisplayAreaInfo is null when a task appears soon enough after
+ // a new display shows up, because TDA may appear after task appears in WM shell. Instead of
+ // fixing the synchronization issues, let's use other signals to "guess" the answer. It is
+ // OK in this context because no other captions other than the legacy developer option
+ // freeform and Kingyo/CF PC may use this class. WM shell should have full control over the
+ // condition where captions should show up in all new cases such as desktop mode, for which
+ // we should use different window decor view models. Ultimately Kingyo/CF PC may need to
+ // spin up their own window decor view model when they start to care about multiple
+ // displays.
+ if (isPc()) {
+ return true;
+ }
+ return taskInfo.displayId != Display.DEFAULT_DISPLAY
+ && forcesDesktopModeOnExternalDisplays();
}
private void createWindowDecoration(
@@ -231,7 +264,10 @@
mTaskOperations.minimizeTask(mTaskToken);
} else if (id == R.id.maximize_window) {
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- mTaskOperations.maximizeTask(taskInfo);
+ final DisplayAreaInfo rootDisplayAreaInfo =
+ mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId);
+ mTaskOperations.maximizeTask(taskInfo,
+ rootDisplayAreaInfo.configuration.windowConfiguration.getWindowingMode());
}
}
@@ -305,4 +341,17 @@
return true;
}
}
+
+ /**
+ * Returns if this device is a PC.
+ */
+ private boolean isPc() {
+ return mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
+ }
+
+ private boolean forcesDesktopModeOnExternalDisplays() {
+ final ContentResolver resolver = mContext.getContentResolver();
+ return Settings.Global.getInt(resolver,
+ DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;
+ }
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index beead6a..43fd32b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -16,17 +16,23 @@
package com.android.wm.shell.windowdecor;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
+
import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
+import android.util.Size;
import android.view.Choreographer;
import android.view.SurfaceControl;
import android.view.View;
@@ -222,7 +228,6 @@
mHandler,
mChoreographer,
mDisplay.getDisplayId(),
- 0 /* taskCornerRadius */,
mDecorationContainerSurface,
mDragPositioningCallback,
mSurfaceControlBuilderSupplier,
@@ -234,12 +239,10 @@
.getScaledTouchSlop();
mDragDetector.setTouchSlop(touchSlop);
- final int resize_handle = mResult.mRootView.getResources()
- .getDimensionPixelSize(R.dimen.freeform_resize_handle);
- final int resize_corner = mResult.mRootView.getResources()
- .getDimensionPixelSize(R.dimen.freeform_resize_corner);
- mDragResizeListener.setGeometry(
- mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
+ final Resources res = mResult.mRootView.getResources();
+ mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */,
+ new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res),
+ getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index a0f9c6b..777ab9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -84,6 +84,7 @@
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
+import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
@@ -407,7 +408,9 @@
mSplitScreenController.moveTaskToFullscreen(getOtherSplitTask(mTaskId).taskId,
SplitScreenController.EXIT_REASON_DESKTOP_MODE);
} else {
- mTaskOperations.closeTask(mTaskToken);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mDesktopTasksController.onDesktopWindowClose(wct, mTaskId);
+ mTaskOperations.closeTask(mTaskToken, wct);
}
} else if (id == R.id.back_button) {
mTaskOperations.injectBackKey();
@@ -858,10 +861,7 @@
// as it likely will change.
relevantDecor.updateHoverAndPressStatus(ev);
mDesktopTasksController.onDragPositioningEndThroughStatusBar(
- new PointF(ev.getRawX(), ev.getRawY()),
- relevantDecor.mTaskInfo,
- calculateFreeformBounds(ev.getDisplayId(),
- DesktopTasksController.DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
+ new PointF(ev.getRawX(), ev.getRawY()), relevantDecor.mTaskInfo);
mMoveToDesktopAnimator = null;
return;
} else {
@@ -913,23 +913,6 @@
}
}
- /**
- * Gets bounds of a scaled window centered relative to the screen bounds
- * @param scale the amount to scale to relative to the Screen Bounds
- */
- private Rect calculateFreeformBounds(int displayId, float scale) {
- // TODO(b/319819547): Account for app constraints so apps do not become letterboxed
- final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
- final int screenWidth = displayLayout.width();
- final int screenHeight = displayLayout.height();
-
- final float adjustmentPercentage = (1f - scale) / 2;
- return new Rect((int) (screenWidth * adjustmentPercentage),
- (int) (screenHeight * adjustmentPercentage),
- (int) (screenWidth * (adjustmentPercentage + scale)),
- (int) (screenHeight * (adjustmentPercentage + scale)));
- }
-
@Nullable
private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
@@ -1025,6 +1008,7 @@
return false;
}
return DesktopModeStatus.isEnabled()
+ && !DesktopWallpaperActivity.isWallpaperTask(taskInfo)
&& taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
&& taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
&& !taskInfo.configuration.windowConfiguration.isAlwaysOnTop()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 963b130..da1699cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -24,6 +24,9 @@
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -42,6 +45,7 @@
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.Log;
+import android.util.Size;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
@@ -276,7 +280,6 @@
mHandler,
mChoreographer,
mDisplay.getDisplayId(),
- mRelayoutParams.mCornerRadius,
mDecorationContainerSurface,
mDragPositioningCallback,
mSurfaceControlBuilderSupplier,
@@ -288,15 +291,13 @@
.getScaledTouchSlop();
mDragDetector.setTouchSlop(touchSlop);
- final int resize_handle = mResult.mRootView.getResources()
- .getDimensionPixelSize(R.dimen.freeform_resize_handle);
- final int resize_corner = mResult.mRootView.getResources()
- .getDimensionPixelSize(R.dimen.freeform_resize_corner);
-
// If either task geometry or position have changed, update this task's
// exclusion region listener
+ final Resources res = mResult.mRootView.getResources();
if (mDragResizeListener.setGeometry(
- mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop)
+ new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius,
+ new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res),
+ getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop)
|| !mTaskInfo.positionInParent.equals(mPositionInParent)) {
updateExclusionRegion();
}
@@ -427,7 +428,7 @@
return mHandleMenu != null;
}
- boolean shouldResizeListenerHandleEvent(MotionEvent e, Point offset) {
+ boolean shouldResizeListenerHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
return mDragResizeListener != null && mDragResizeListener.shouldHandleEvent(e, offset);
}
@@ -466,8 +467,8 @@
* until a resize event calls showResizeVeil below.
*/
void createResizeVeil() {
- mResizeVeil = new ResizeVeil(mContext, mAppIconDrawable, mTaskInfo, mTaskSurface,
- mSurfaceControlBuilderSupplier, mDisplay, mSurfaceControlTransactionSupplier);
+ mResizeVeil = new ResizeVeil(mContext, mDisplayController, mAppIconDrawable, mTaskInfo,
+ mTaskSurface, mSurfaceControlTransactionSupplier);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
index 8ce2d6d..421ffd9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
@@ -23,6 +23,9 @@
* Callback called when receiving drag-resize or drag-move related input events.
*/
public interface DragPositioningCallback {
+ /**
+ * Indicates the direction of resizing. May be combined together to indicate a diagonal drag.
+ */
@IntDef(flag = true, value = {
CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM
})
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 97eb4a4..9624d46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -30,6 +30,7 @@
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
+import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
@@ -39,6 +40,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.Size;
import android.view.Choreographer;
import android.view.IWindowSession;
import android.view.InputChannel;
@@ -55,6 +57,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
+import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -66,40 +69,20 @@
class DragResizeInputListener implements AutoCloseable {
private static final String TAG = "DragResizeInputListener";
private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();
- private final Context mContext;
- private final Handler mHandler;
- private final Choreographer mChoreographer;
- private final InputManager mInputManager;
private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
private final int mDisplayId;
private final IBinder mClientToken;
- private final InputTransferToken mInputTransferToken;
private final SurfaceControl mDecorationSurface;
private final InputChannel mInputChannel;
private final TaskResizeInputEventReceiver mInputEventReceiver;
- private final DragPositioningCallback mCallback;
private final SurfaceControl mInputSinkSurface;
private final IBinder mSinkClientToken;
private final InputChannel mSinkInputChannel;
private final DisplayController mDisplayController;
-
- private int mTaskWidth;
- private int mTaskHeight;
- private int mResizeHandleThickness;
- private int mCornerSize;
- private int mTaskCornerRadius;
-
- private Rect mLeftTopCornerBounds;
- private Rect mRightTopCornerBounds;
- private Rect mLeftBottomCornerBounds;
- private Rect mRightBottomCornerBounds;
-
- private int mDragPointerId = -1;
- private DragDetector mDragDetector;
private final Region mTouchRegion = new Region();
DragResizeInputListener(
@@ -107,23 +90,17 @@
Handler handler,
Choreographer choreographer,
int displayId,
- int taskCornerRadius,
SurfaceControl decorationSurface,
DragPositioningCallback callback,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
DisplayController displayController) {
- mInputManager = context.getSystemService(InputManager.class);
- mContext = context;
- mHandler = handler;
- mChoreographer = choreographer;
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mDisplayId = displayId;
- mTaskCornerRadius = taskCornerRadius;
mDecorationSurface = decorationSurface;
mDisplayController = displayController;
mClientToken = new Binder();
- mInputTransferToken = new InputTransferToken();
+ final InputTransferToken inputTransferToken = new InputTransferToken();
mInputChannel = new InputChannel();
try {
mWindowSession.grantInputChannel(
@@ -136,18 +113,19 @@
INPUT_FEATURE_SPY,
TYPE_APPLICATION,
null /* windowToken */,
- mInputTransferToken,
+ inputTransferToken,
TAG + " of " + decorationSurface.toString(),
mInputChannel);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
- mInputEventReceiver = new TaskResizeInputEventReceiver(
- mInputChannel, mHandler, mChoreographer);
- mCallback = callback;
- mDragDetector = new DragDetector(mInputEventReceiver);
- mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
+ mInputEventReceiver = new TaskResizeInputEventReceiver(context, mInputChannel, callback,
+ handler, choreographer, () -> {
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId);
+ return new Size(layout.width(), layout.height());
+ }, this::updateSinkInputChannel);
+ mInputEventReceiver.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
mInputSinkSurface = surfaceControlBuilderSupplier.get()
.setName("TaskInputSink of " + decorationSurface)
@@ -171,7 +149,7 @@
INPUT_FEATURE_NO_INPUT_CHANNEL,
TYPE_INPUT_CONSUMER,
null /* windowToken */,
- mInputTransferToken,
+ inputTransferToken,
"TaskInputSink of " + decorationSurface,
mSinkInputChannel);
} catch (RemoteException e) {
@@ -182,86 +160,26 @@
/**
* Updates the geometry (the touch region) of this drag resize handler.
*
- * @param taskWidth The width of the task.
- * @param taskHeight The height of the task.
- * @param resizeHandleThickness The thickness of the resize handle in pixels.
- * @param cornerSize The size of the resize handle centered in each corner.
- * @param touchSlop The distance in pixels user has to drag with touch for it to register as
- * a resize action.
+ * @param incomingGeometry The geometry update to apply for this task's drag resize regions.
+ * @param touchSlop The distance in pixels user has to drag with touch for it to register
+ * as a resize action.
* @return whether the geometry has changed or not
*/
- boolean setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize,
- int touchSlop) {
- if (mTaskWidth == taskWidth && mTaskHeight == taskHeight
- && mResizeHandleThickness == resizeHandleThickness
- && mCornerSize == cornerSize) {
+ boolean setGeometry(@NonNull DragResizeWindowGeometry incomingGeometry, int touchSlop) {
+ DragResizeWindowGeometry geometry = mInputEventReceiver.getGeometry();
+ if (incomingGeometry.equals(geometry)) {
+ // Geometry hasn't changed size so skip all updates.
return false;
+ } else {
+ geometry = incomingGeometry;
}
-
- mTaskWidth = taskWidth;
- mTaskHeight = taskHeight;
- mResizeHandleThickness = resizeHandleThickness;
- mCornerSize = cornerSize;
- mDragDetector.setTouchSlop(touchSlop);
+ mInputEventReceiver.setTouchSlop(touchSlop);
mTouchRegion.setEmpty();
- final Rect topInputBounds = new Rect(
- -mResizeHandleThickness,
- -mResizeHandleThickness,
- mTaskWidth + mResizeHandleThickness,
- 0);
- mTouchRegion.union(topInputBounds);
-
- final Rect leftInputBounds = new Rect(
- -mResizeHandleThickness,
- 0,
- 0,
- mTaskHeight);
- mTouchRegion.union(leftInputBounds);
-
- final Rect rightInputBounds = new Rect(
- mTaskWidth,
- 0,
- mTaskWidth + mResizeHandleThickness,
- mTaskHeight);
- mTouchRegion.union(rightInputBounds);
-
- final Rect bottomInputBounds = new Rect(
- -mResizeHandleThickness,
- mTaskHeight,
- mTaskWidth + mResizeHandleThickness,
- mTaskHeight + mResizeHandleThickness);
- mTouchRegion.union(bottomInputBounds);
-
- // Set up touch areas in each corner.
- int cornerRadius = mCornerSize / 2;
- mLeftTopCornerBounds = new Rect(
- -cornerRadius,
- -cornerRadius,
- cornerRadius,
- cornerRadius);
- mTouchRegion.union(mLeftTopCornerBounds);
-
- mRightTopCornerBounds = new Rect(
- mTaskWidth - cornerRadius,
- -cornerRadius,
- mTaskWidth + cornerRadius,
- cornerRadius);
- mTouchRegion.union(mRightTopCornerBounds);
-
- mLeftBottomCornerBounds = new Rect(
- -cornerRadius,
- mTaskHeight - cornerRadius,
- cornerRadius,
- mTaskHeight + cornerRadius);
- mTouchRegion.union(mLeftBottomCornerBounds);
-
- mRightBottomCornerBounds = new Rect(
- mTaskWidth - cornerRadius,
- mTaskHeight - cornerRadius,
- mTaskWidth + cornerRadius,
- mTaskHeight + cornerRadius);
- mTouchRegion.union(mRightBottomCornerBounds);
+ // Apply the geometry to the touch region.
+ geometry.union(mTouchRegion);
+ mInputEventReceiver.setGeometry(geometry);
+ mInputEventReceiver.setTouchRegion(mTouchRegion);
try {
mWindowSession.updateInputChannel(
@@ -276,8 +194,9 @@
e.rethrowFromSystemServer();
}
+ final Size taskSize = geometry.getTaskSize();
mSurfaceControlTransactionSupplier.get()
- .setWindowCrop(mInputSinkSurface, mTaskWidth, mTaskHeight)
+ .setWindowCrop(mInputSinkSurface, taskSize.getWidth(), taskSize.getHeight())
.apply();
// The touch region of the TaskInputSink should be the touch region of this
// DragResizeInputHandler minus the task bounds. Pilfering events isn't enough to prevent
@@ -290,21 +209,16 @@
// issue. However, were there touchscreen-only a region out of the task bounds, mouse
// gestures will become no-op in that region, even though the mouse gestures may appear to
// be performed on the input window behind the resize handle.
- mTouchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE);
+ mTouchRegion.op(0, 0, taskSize.getWidth(), taskSize.getHeight(), Region.Op.DIFFERENCE);
updateSinkInputChannel(mTouchRegion);
return true;
}
/**
- * Generate a Region that encapsulates all 4 corner handles
+ * Generate a Region that encapsulates all 4 corner handles and window edges.
*/
- Region getCornersRegion() {
- Region region = new Region();
- region.union(mLeftTopCornerBounds);
- region.union(mLeftBottomCornerBounds);
- region.union(mRightTopCornerBounds);
- region.union(mRightBottomCornerBounds);
- return region;
+ @NonNull Region getCornersRegion() {
+ return mInputEventReceiver.getCornersRegion();
}
private void updateSinkInputChannel(Region region) {
@@ -322,7 +236,7 @@
}
}
- boolean shouldHandleEvent(MotionEvent e, Point offset) {
+ boolean shouldHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
return mInputEventReceiver.shouldHandleEvent(e, offset);
}
@@ -351,19 +265,37 @@
.apply();
}
- private class TaskResizeInputEventReceiver extends InputEventReceiver
- implements DragDetector.MotionEventHandler {
- private final Choreographer mChoreographer;
- private final Runnable mConsumeBatchEventRunnable;
+ private static class TaskResizeInputEventReceiver extends InputEventReceiver implements
+ DragDetector.MotionEventHandler {
+ @NonNull private final Context mContext;
+ private final InputManager mInputManager;
+ @NonNull private final InputChannel mInputChannel;
+ @NonNull private final DragPositioningCallback mCallback;
+ @NonNull private final Choreographer mChoreographer;
+ @NonNull private final Runnable mConsumeBatchEventRunnable;
+ @NonNull private final DragDetector mDragDetector;
+ @NonNull private final Supplier<Size> mDisplayLayoutSizeSupplier;
+ @NonNull private final Consumer<Region> mTouchRegionConsumer;
+ private final Rect mTmpRect = new Rect();
private boolean mConsumeBatchEventScheduled;
+ private DragResizeWindowGeometry mDragResizeWindowGeometry;
+ private Region mTouchRegion;
private boolean mShouldHandleEvents;
private int mLastCursorType = PointerIcon.TYPE_DEFAULT;
private Rect mDragStartTaskBounds;
- private final Rect mTmpRect = new Rect();
+ private int mDragPointerId = -1;
- private TaskResizeInputEventReceiver(
- InputChannel inputChannel, Handler handler, Choreographer choreographer) {
+ private TaskResizeInputEventReceiver(@NonNull Context context,
+ @NonNull InputChannel inputChannel,
+ @NonNull DragPositioningCallback callback, @NonNull Handler handler,
+ @NonNull Choreographer choreographer,
+ @NonNull Supplier<Size> displayLayoutSizeSupplier,
+ @NonNull Consumer<Region> touchRegionConsumer) {
super(inputChannel, handler.getLooper());
+ mContext = context;
+ mInputManager = context.getSystemService(InputManager.class);
+ mInputChannel = inputChannel;
+ mCallback = callback;
mChoreographer = choreographer;
mConsumeBatchEventRunnable = () -> {
@@ -376,6 +308,48 @@
scheduleConsumeBatchEvent();
}
};
+
+ mDragDetector = new DragDetector(this);
+ mDisplayLayoutSizeSupplier = displayLayoutSizeSupplier;
+ mTouchRegionConsumer = touchRegionConsumer;
+ }
+
+ /**
+ * Returns the geometry of the areas to drag resize.
+ */
+ DragResizeWindowGeometry getGeometry() {
+ return mDragResizeWindowGeometry;
+ }
+
+ /**
+ * Updates the geometry of the areas to drag resize.
+ */
+ void setGeometry(@NonNull DragResizeWindowGeometry dragResizeWindowGeometry) {
+ mDragResizeWindowGeometry = dragResizeWindowGeometry;
+ }
+
+ /**
+ * Sets how much slop to allow for touches.
+ */
+ void setTouchSlop(int touchSlop) {
+ mDragDetector.setTouchSlop(touchSlop);
+ }
+
+ /**
+ * Updates the region accepting input for drag resizing the task.
+ */
+ void setTouchRegion(@NonNull Region touchRegion) {
+ mTouchRegion = touchRegion;
+ }
+
+ /**
+ * Returns the union of all regions that can be touched for drag resizing; the corners and
+ * window edges.
+ */
+ @NonNull Region getCornersRegion() {
+ Region region = new Region();
+ mDragResizeWindowGeometry.union(region);
+ return region;
}
@Override
@@ -416,14 +390,15 @@
boolean isTouch = isTouchEvent(e);
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
- mShouldHandleEvents = shouldHandleEvent(e, isTouch, new Point() /* offset */);
+ mShouldHandleEvents = mDragResizeWindowGeometry.shouldHandleEvent(e, isTouch,
+ new Point() /* offset */);
if (mShouldHandleEvents) {
mDragPointerId = e.getPointerId(0);
float x = e.getX(0);
float y = e.getY(0);
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
- int ctrlType = calculateCtrlType(isTouch, x, y);
+ int ctrlType = mDragResizeWindowGeometry.calculateCtrlType(isTouch, x, y);
mDragStartTaskBounds = mCallback.onDragPositioningStart(ctrlType,
rawX, rawY);
// Increase the input sink region to cover the whole screen; this is to
@@ -455,7 +430,7 @@
// If taskBounds has changed, setGeometry will be called and update the
// sink region. Otherwise, we should revert it here.
if (taskBounds.equals(mDragStartTaskBounds)) {
- updateSinkInputChannel(mTouchRegion);
+ mTouchRegionConsumer.accept(mTouchRegion);
}
}
mShouldHandleEvents = false;
@@ -480,125 +455,20 @@
private void updateInputSinkRegionForDrag(Rect taskBounds) {
mTmpRect.set(taskBounds);
- final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId);
- final Region dragTouchRegion = new Region(-taskBounds.left,
- -taskBounds.top,
- -taskBounds.left + layout.width(),
- -taskBounds.top + layout.height());
+ final Size displayLayoutSize = mDisplayLayoutSizeSupplier.get();
+ final Region dragTouchRegion = new Region(-taskBounds.left, -taskBounds.top,
+ -taskBounds.left + displayLayoutSize.getWidth(),
+ -taskBounds.top + displayLayoutSize.getHeight());
// Remove the localized task bounds from the touch region.
mTmpRect.offsetTo(0, 0);
dragTouchRegion.op(mTmpRect, Region.Op.DIFFERENCE);
- updateSinkInputChannel(dragTouchRegion);
- }
-
- private boolean isInCornerBounds(float xf, float yf) {
- return calculateCornersCtrlType(xf, yf) != 0;
- }
-
- private boolean isInResizeHandleBounds(float x, float y) {
- return calculateResizeHandlesCtrlType(x, y) != 0;
- }
-
- @DragPositioningCallback.CtrlType
- private int calculateCtrlType(boolean isTouch, float x, float y) {
- if (isTouch) {
- return calculateCornersCtrlType(x, y);
- }
- return calculateResizeHandlesCtrlType(x, y);
- }
-
- @DragPositioningCallback.CtrlType
- private int calculateResizeHandlesCtrlType(float x, float y) {
- int ctrlType = 0;
- // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
- // sides will use the bounds specified in setGeometry and not go into task bounds.
- if (x < mTaskCornerRadius) {
- ctrlType |= CTRL_TYPE_LEFT;
- }
- if (x > mTaskWidth - mTaskCornerRadius) {
- ctrlType |= CTRL_TYPE_RIGHT;
- }
- if (y < mTaskCornerRadius) {
- ctrlType |= CTRL_TYPE_TOP;
- }
- if (y > mTaskHeight - mTaskCornerRadius) {
- ctrlType |= CTRL_TYPE_BOTTOM;
- }
- // Check distances from the center if it's in one of four corners.
- if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0
- && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) {
- return checkDistanceFromCenter(ctrlType, x, y);
- }
- // Otherwise, we should make sure we don't resize tasks inside task bounds.
- return (x < 0 || y < 0 || x >= mTaskWidth || y >= mTaskHeight) ? ctrlType : 0;
- }
-
- // If corner input is not within appropriate distance of corner radius, do not use it.
- // If input is not on a corner or is within valid distance, return ctrlType.
- @DragPositioningCallback.CtrlType
- private int checkDistanceFromCenter(@DragPositioningCallback.CtrlType int ctrlType,
- float x, float y) {
- int centerX;
- int centerY;
-
- // Determine center of rounded corner circle; this is simply the corner if radius is 0.
- switch (ctrlType) {
- case CTRL_TYPE_LEFT | CTRL_TYPE_TOP: {
- centerX = mTaskCornerRadius;
- centerY = mTaskCornerRadius;
- break;
- }
- case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM: {
- centerX = mTaskCornerRadius;
- centerY = mTaskHeight - mTaskCornerRadius;
- break;
- }
- case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP: {
- centerX = mTaskWidth - mTaskCornerRadius;
- centerY = mTaskCornerRadius;
- break;
- }
- case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM: {
- centerX = mTaskWidth - mTaskCornerRadius;
- centerY = mTaskHeight - mTaskCornerRadius;
- break;
- }
- default: {
- throw new IllegalArgumentException("ctrlType should be complex, but it's 0x"
- + Integer.toHexString(ctrlType));
- }
- }
- double distanceFromCenter = Math.hypot(x - centerX, y - centerY);
-
- if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness
- && distanceFromCenter >= mTaskCornerRadius) {
- return ctrlType;
- }
- return 0;
- }
-
- @DragPositioningCallback.CtrlType
- private int calculateCornersCtrlType(float x, float y) {
- int xi = (int) x;
- int yi = (int) y;
- if (mLeftTopCornerBounds.contains(xi, yi)) {
- return CTRL_TYPE_LEFT | CTRL_TYPE_TOP;
- }
- if (mLeftBottomCornerBounds.contains(xi, yi)) {
- return CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM;
- }
- if (mRightTopCornerBounds.contains(xi, yi)) {
- return CTRL_TYPE_RIGHT | CTRL_TYPE_TOP;
- }
- if (mRightBottomCornerBounds.contains(xi, yi)) {
- return CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM;
- }
- return 0;
+ mTouchRegionConsumer.accept(dragTouchRegion);
}
private void updateCursorType(int displayId, int deviceId, int pointerId, float x,
float y) {
- @DragPositioningCallback.CtrlType int ctrlType = calculateResizeHandlesCtrlType(x, y);
+ @DragPositioningCallback.CtrlType int ctrlType =
+ mDragResizeWindowGeometry.calculateCtrlType(/* isTouch= */ false, x, y);
int cursorType = PointerIcon.TYPE_DEFAULT;
switch (ctrlType) {
@@ -640,19 +510,7 @@
}
private boolean shouldHandleEvent(MotionEvent e, Point offset) {
- return shouldHandleEvent(e, isTouchEvent(e), offset);
- }
-
- private boolean shouldHandleEvent(MotionEvent e, boolean isTouch, Point offset) {
- boolean result;
- final float x = e.getX(0) + offset.x;
- final float y = e.getY(0) + offset.y;
- if (isTouch) {
- result = isInCornerBounds(x, y);
- } else {
- result = isInResizeHandleBounds(x, y);
- }
- return result;
+ return mDragResizeWindowGeometry.shouldHandleEvent(e, offset);
}
private boolean isTouchEvent(MotionEvent e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
new file mode 100644
index 0000000..eafb569
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+
+import static com.android.window.flags.Flags.enableWindowingEdgeDragResize;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED;
+
+import android.annotation.NonNull;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.Size;
+import android.view.MotionEvent;
+
+import com.android.wm.shell.R;
+
+import java.util.Objects;
+
+/**
+ * Geometry for a drag resize region for a particular window.
+ */
+final class DragResizeWindowGeometry {
+ private final int mTaskCornerRadius;
+ private final Size mTaskSize;
+ // The size of the handle applied to the edges of the window, for the user to drag resize.
+ private final int mResizeHandleThickness;
+ // The task corners to permit drag resizing with a course input, such as touch.
+
+ private final @NonNull TaskCorners mLargeTaskCorners;
+ // The task corners to permit drag resizing with a fine input, such as stylus or cursor.
+ private final @NonNull TaskCorners mFineTaskCorners;
+ // The bounds for each edge drag region, which can resize the task in one direction.
+ private final @NonNull Rect mTopEdgeBounds;
+ private final @NonNull Rect mLeftEdgeBounds;
+ private final @NonNull Rect mRightEdgeBounds;
+ private final @NonNull Rect mBottomEdgeBounds;
+
+ DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize,
+ int resizeHandleThickness, int fineCornerSize, int largeCornerSize) {
+ mTaskCornerRadius = taskCornerRadius;
+ mTaskSize = taskSize;
+ mResizeHandleThickness = resizeHandleThickness;
+
+ mLargeTaskCorners = new TaskCorners(mTaskSize, largeCornerSize);
+ mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize);
+
+ // Save touch areas for each edge.
+ mTopEdgeBounds = new Rect(
+ -mResizeHandleThickness,
+ -mResizeHandleThickness,
+ mTaskSize.getWidth() + mResizeHandleThickness,
+ 0);
+ mLeftEdgeBounds = new Rect(
+ -mResizeHandleThickness,
+ 0,
+ 0,
+ mTaskSize.getHeight());
+ mRightEdgeBounds = new Rect(
+ mTaskSize.getWidth(),
+ 0,
+ mTaskSize.getWidth() + mResizeHandleThickness,
+ mTaskSize.getHeight());
+ mBottomEdgeBounds = new Rect(
+ -mResizeHandleThickness,
+ mTaskSize.getHeight(),
+ mTaskSize.getWidth() + mResizeHandleThickness,
+ mTaskSize.getHeight() + mResizeHandleThickness);
+ }
+
+ /**
+ * Returns the resource value to use for the resize handle on the edge of the window.
+ */
+ static int getResizeEdgeHandleSize(@NonNull Resources res) {
+ return enableWindowingEdgeDragResize()
+ ? res.getDimensionPixelSize(R.dimen.desktop_mode_edge_handle)
+ : res.getDimensionPixelSize(R.dimen.freeform_resize_handle);
+ }
+
+ /**
+ * Returns the resource value to use for course input, such as touch, that benefits from a large
+ * square on each of the window's corners.
+ */
+ static int getLargeResizeCornerSize(@NonNull Resources res) {
+ return res.getDimensionPixelSize(R.dimen.desktop_mode_corner_resize_large);
+ }
+
+ /**
+ * Returns the resource value to use for fine input, such as stylus, that can use a smaller
+ * square on each of the window's corners.
+ */
+ static int getFineResizeCornerSize(@NonNull Resources res) {
+ return res.getDimensionPixelSize(R.dimen.freeform_resize_corner);
+ }
+
+ /**
+ * Returns the size of the task this geometry is calculated for.
+ */
+ @NonNull Size getTaskSize() {
+ // Safe to return directly since size is immutable.
+ return mTaskSize;
+ }
+
+ /**
+ * Returns the union of all regions that can be touched for drag resizing; the corners window
+ * and window edges.
+ */
+ void union(@NonNull Region region) {
+ // Apply the edge resize regions.
+ region.union(mTopEdgeBounds);
+ region.union(mLeftEdgeBounds);
+ region.union(mRightEdgeBounds);
+ region.union(mBottomEdgeBounds);
+
+ if (enableWindowingEdgeDragResize()) {
+ // Apply the corners as well for the larger corners, to ensure we capture all possible
+ // touches.
+ mLargeTaskCorners.union(region);
+ } else {
+ // Only apply fine corners for the legacy approach.
+ mFineTaskCorners.union(region);
+ }
+ }
+
+ /**
+ * Returns if this MotionEvent should be handled, based on its source and position.
+ */
+ boolean shouldHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
+ return shouldHandleEvent(e, isTouchEvent(e), offset);
+ }
+
+ /**
+ * Returns if this MotionEvent should be handled, based on its source and position.
+ */
+ boolean shouldHandleEvent(@NonNull MotionEvent e, boolean isTouch, @NonNull Point offset) {
+ final float x = e.getX(0) + offset.x;
+ final float y = e.getY(0) + offset.y;
+
+ if (enableWindowingEdgeDragResize()) {
+ // First check if touch falls within a corner.
+ // Large corner bounds are used for course input like touch, otherwise fine bounds.
+ boolean result = isTouch
+ ? isInCornerBounds(mLargeTaskCorners, x, y)
+ : isInCornerBounds(mFineTaskCorners, x, y);
+ // Check if touch falls within the edge resize handle, since edge resizing can apply
+ // for any input source.
+ if (!result) {
+ result = isInEdgeResizeBounds(x, y);
+ }
+ return result;
+ } else {
+ // Legacy uses only fine corners for touch, and edges only for non-touch input.
+ return isTouch
+ ? isInCornerBounds(mFineTaskCorners, x, y)
+ : isInEdgeResizeBounds(x, y);
+ }
+ }
+
+ private boolean isTouchEvent(@NonNull MotionEvent e) {
+ return (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
+ }
+
+ private boolean isInCornerBounds(TaskCorners corners, float xf, float yf) {
+ return corners.calculateCornersCtrlType(xf, yf) != 0;
+ }
+
+ private boolean isInEdgeResizeBounds(float x, float y) {
+ return calculateEdgeResizeCtrlType(x, y) != 0;
+ }
+
+ /**
+ * Returns the control type for the drag-resize, based on the touch regions and this
+ * MotionEvent's coordinates.
+ */
+ @DragPositioningCallback.CtrlType
+ int calculateCtrlType(boolean isTouch, float x, float y) {
+ if (enableWindowingEdgeDragResize()) {
+ // First check if touch falls within a corner.
+ // Large corner bounds are used for course input like touch, otherwise fine bounds.
+ int ctrlType = isTouch
+ ? mLargeTaskCorners.calculateCornersCtrlType(x, y)
+ : mFineTaskCorners.calculateCornersCtrlType(x, y);
+ // Check if touch falls within the edge resize handle, since edge resizing can apply
+ // for any input source.
+ if (ctrlType == CTRL_TYPE_UNDEFINED) {
+ ctrlType = calculateEdgeResizeCtrlType(x, y);
+ }
+ return ctrlType;
+ } else {
+ // Legacy uses only fine corners for touch, and edges only for non-touch input.
+ return isTouch
+ ? mFineTaskCorners.calculateCornersCtrlType(x, y)
+ : calculateEdgeResizeCtrlType(x, y);
+ }
+ }
+
+ @DragPositioningCallback.CtrlType
+ private int calculateEdgeResizeCtrlType(float x, float y) {
+ int ctrlType = CTRL_TYPE_UNDEFINED;
+ // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
+ // sides will use the bounds specified in setGeometry and not go into task bounds.
+ if (x < mTaskCornerRadius) {
+ ctrlType |= CTRL_TYPE_LEFT;
+ }
+ if (x > mTaskSize.getWidth() - mTaskCornerRadius) {
+ ctrlType |= CTRL_TYPE_RIGHT;
+ }
+ if (y < mTaskCornerRadius) {
+ ctrlType |= CTRL_TYPE_TOP;
+ }
+ if (y > mTaskSize.getHeight() - mTaskCornerRadius) {
+ ctrlType |= CTRL_TYPE_BOTTOM;
+ }
+ // If the touch is within one of the four corners, check if it is within the bounds of the
+ // // handle.
+ if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0
+ && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) {
+ return checkDistanceFromCenter(ctrlType, x, y);
+ }
+ // Otherwise, we should make sure we don't resize tasks inside task bounds.
+ return (x < 0 || y < 0 || x >= mTaskSize.getWidth() || y >= mTaskSize.getHeight())
+ ? ctrlType : CTRL_TYPE_UNDEFINED;
+ }
+
+ /**
+ * Return {@code ctrlType} if the corner input is outside the (potentially rounded) corner of
+ * the task, and within the thickness of the resize handle. Otherwise, return 0.
+ */
+ @DragPositioningCallback.CtrlType
+ private int checkDistanceFromCenter(@DragPositioningCallback.CtrlType int ctrlType, float x,
+ float y) {
+ final Point cornerRadiusCenter = calculateCenterForCornerRadius(ctrlType);
+ double distanceFromCenter = Math.hypot(x - cornerRadiusCenter.x, y - cornerRadiusCenter.y);
+
+ if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness
+ && distanceFromCenter >= mTaskCornerRadius) {
+ return ctrlType;
+ }
+ return CTRL_TYPE_UNDEFINED;
+ }
+
+ /**
+ * Returns center of rounded corner circle; this is simply the corner if radius is 0.
+ */
+ private Point calculateCenterForCornerRadius(@DragPositioningCallback.CtrlType int ctrlType) {
+ int centerX;
+ int centerY;
+
+ switch (ctrlType) {
+ case CTRL_TYPE_LEFT | CTRL_TYPE_TOP: {
+ centerX = mTaskCornerRadius;
+ centerY = mTaskCornerRadius;
+ break;
+ }
+ case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM: {
+ centerX = mTaskCornerRadius;
+ centerY = mTaskSize.getHeight() - mTaskCornerRadius;
+ break;
+ }
+ case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP: {
+ centerX = mTaskSize.getWidth() - mTaskCornerRadius;
+ centerY = mTaskCornerRadius;
+ break;
+ }
+ case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM: {
+ centerX = mTaskSize.getWidth() - mTaskCornerRadius;
+ centerY = mTaskSize.getHeight() - mTaskCornerRadius;
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException(
+ "ctrlType should be complex, but it's 0x" + Integer.toHexString(ctrlType));
+ }
+ }
+ return new Point(centerX, centerY);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) return false;
+ if (this == obj) return true;
+ if (!(obj instanceof DragResizeWindowGeometry other)) return false;
+
+ return this.mTaskCornerRadius == other.mTaskCornerRadius
+ && this.mTaskSize.equals(other.mTaskSize)
+ && this.mResizeHandleThickness == other.mResizeHandleThickness
+ && this.mFineTaskCorners.equals(other.mFineTaskCorners)
+ && this.mLargeTaskCorners.equals(other.mLargeTaskCorners)
+ && this.mTopEdgeBounds.equals(other.mTopEdgeBounds)
+ && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds)
+ && this.mRightEdgeBounds.equals(other.mRightEdgeBounds)
+ && this.mBottomEdgeBounds.equals(other.mBottomEdgeBounds);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mTaskCornerRadius,
+ mTaskSize,
+ mResizeHandleThickness,
+ mFineTaskCorners,
+ mLargeTaskCorners,
+ mTopEdgeBounds,
+ mLeftEdgeBounds,
+ mRightEdgeBounds,
+ mBottomEdgeBounds);
+ }
+
+ /**
+ * Representation of the drag resize regions at the corner of the window.
+ */
+ private static class TaskCorners {
+ // The size of the square applied to the corners of the window, for the user to drag
+ // resize.
+ private final int mCornerSize;
+ // The square for each corner.
+ private final @NonNull Rect mLeftTopCornerBounds;
+ private final @NonNull Rect mRightTopCornerBounds;
+ private final @NonNull Rect mLeftBottomCornerBounds;
+ private final @NonNull Rect mRightBottomCornerBounds;
+
+ TaskCorners(@NonNull Size taskSize, int cornerSize) {
+ mCornerSize = cornerSize;
+ final int cornerRadius = cornerSize / 2;
+ mLeftTopCornerBounds = new Rect(
+ -cornerRadius,
+ -cornerRadius,
+ cornerRadius,
+ cornerRadius);
+
+ mRightTopCornerBounds = new Rect(
+ taskSize.getWidth() - cornerRadius,
+ -cornerRadius,
+ taskSize.getWidth() + cornerRadius,
+ cornerRadius);
+
+ mLeftBottomCornerBounds = new Rect(
+ -cornerRadius,
+ taskSize.getHeight() - cornerRadius,
+ cornerRadius,
+ taskSize.getHeight() + cornerRadius);
+
+ mRightBottomCornerBounds = new Rect(
+ taskSize.getWidth() - cornerRadius,
+ taskSize.getHeight() - cornerRadius,
+ taskSize.getWidth() + cornerRadius,
+ taskSize.getHeight() + cornerRadius);
+ }
+
+ /**
+ * Updates the region to include all four corners.
+ */
+ void union(Region region) {
+ region.union(mLeftTopCornerBounds);
+ region.union(mRightTopCornerBounds);
+ region.union(mLeftBottomCornerBounds);
+ region.union(mRightBottomCornerBounds);
+ }
+
+ /**
+ * Returns the control type based on the position of the {@code MotionEvent}'s coordinates.
+ */
+ @DragPositioningCallback.CtrlType
+ int calculateCornersCtrlType(float x, float y) {
+ int xi = (int) x;
+ int yi = (int) y;
+ if (mLeftTopCornerBounds.contains(xi, yi)) {
+ return CTRL_TYPE_LEFT | CTRL_TYPE_TOP;
+ }
+ if (mLeftBottomCornerBounds.contains(xi, yi)) {
+ return CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM;
+ }
+ if (mRightTopCornerBounds.contains(xi, yi)) {
+ return CTRL_TYPE_RIGHT | CTRL_TYPE_TOP;
+ }
+ if (mRightBottomCornerBounds.contains(xi, yi)) {
+ return CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM;
+ }
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "TaskCorners of size " + mCornerSize + " for the"
+ + " top left " + mLeftTopCornerBounds
+ + " top right " + mRightTopCornerBounds
+ + " bottom left " + mLeftBottomCornerBounds
+ + " bottom right " + mRightBottomCornerBounds;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) return false;
+ if (this == obj) return true;
+ if (!(obj instanceof TaskCorners other)) return false;
+
+ return this.mCornerSize == other.mCornerSize
+ && this.mLeftTopCornerBounds.equals(other.mLeftTopCornerBounds)
+ && this.mRightTopCornerBounds.equals(other.mRightTopCornerBounds)
+ && this.mLeftBottomCornerBounds.equals(other.mLeftBottomCornerBounds)
+ && this.mRightBottomCornerBounds.equals(other.mRightBottomCornerBounds);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mCornerSize,
+ mLeftTopCornerBounds,
+ mRightTopCornerBounds,
+ mLeftBottomCornerBounds,
+ mRightBottomCornerBounds);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index d072f8c..2c4092a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -20,6 +20,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.ColorRes;
+import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.res.Configuration;
@@ -40,7 +41,7 @@
import android.window.TaskConstants;
import com.android.wm.shell.R;
-import com.android.wm.shell.common.SurfaceUtils;
+import com.android.wm.shell.common.DisplayController;
import java.util.function.Supplier;
@@ -48,6 +49,7 @@
* Creates and updates a veil that covers task contents on resize.
*/
public class ResizeVeil {
+ private static final String TAG = "ResizeVeil";
private static final int RESIZE_ALPHA_DURATION = 100;
private static final int VEIL_CONTAINER_LAYER = TaskConstants.TASK_CHILD_LAYER_RESIZE_VEIL;
@@ -57,8 +59,10 @@
private static final int VEIL_ICON_LAYER = 1;
private final Context mContext;
- private final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
+ private final DisplayController mDisplayController;
private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
+ private final SurfaceControlBuilderFactory mSurfaceControlBuilderFactory;
+ private final WindowDecoration.SurfaceControlViewHostFactory mSurfaceControlViewHostFactory;
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final Drawable mAppIcon;
private ImageView mIconView;
@@ -74,41 +78,82 @@
private final RunningTaskInfo mTaskInfo;
private SurfaceControlViewHost mViewHost;
- private final Display mDisplay;
+ private Display mDisplay;
private ValueAnimator mVeilAnimator;
- public ResizeVeil(Context context, Drawable appIcon, RunningTaskInfo taskInfo,
+ private boolean mIsShowing = false;
+
+ private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
+ new DisplayController.OnDisplaysChangedListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ if (mTaskInfo.displayId != displayId) {
+ return;
+ }
+ mDisplayController.removeDisplayWindowListener(this);
+ setupResizeVeil();
+ }
+ };
+
+ public ResizeVeil(Context context,
+ @NonNull DisplayController displayController,
+ Drawable appIcon, RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
- Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Display display,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) {
+ this(context,
+ displayController,
+ appIcon,
+ taskInfo,
+ taskSurface,
+ surfaceControlTransactionSupplier,
+ new SurfaceControlBuilderFactory() {},
+ new WindowDecoration.SurfaceControlViewHostFactory() {});
+ }
+
+ public ResizeVeil(Context context,
+ @NonNull DisplayController displayController,
+ Drawable appIcon, RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
+ SurfaceControlBuilderFactory surfaceControlBuilderFactory,
+ WindowDecoration.SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
mContext = context;
+ mDisplayController = displayController;
mAppIcon = appIcon;
- mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mTaskInfo = taskInfo;
mParentSurface = taskSurface;
- mDisplay = display;
+ mSurfaceControlBuilderFactory = surfaceControlBuilderFactory;
+ mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
setupResizeVeil();
}
-
/**
* Create the veil in its default invisible state.
*/
private void setupResizeVeil() {
- mVeilSurface = mSurfaceControlBuilderSupplier.get()
+ if (!obtainDisplayOrRegisterListener()) {
+ // Display may not be available yet, skip this until then.
+ return;
+ }
+ mVeilSurface = mSurfaceControlBuilderFactory
+ .create("Resize veil of Task=" + mTaskInfo.taskId)
.setContainerLayer()
- .setName("Resize veil of Task=" + mTaskInfo.taskId)
.setHidden(true)
.setParent(mParentSurface)
.setCallsite("ResizeVeil#setupResizeVeil")
.build();
- mBackgroundSurface = SurfaceUtils.makeColorLayer(mVeilSurface,
- "Resize veil background of Task=" + mTaskInfo.taskId, mSurfaceSession);
- mIconSurface = mSurfaceControlBuilderSupplier.get()
- .setName("Resize veil icon of Task= " + mTaskInfo.taskId)
- .setContainerLayer()
- .setParent(mVeilSurface)
+ mBackgroundSurface = mSurfaceControlBuilderFactory
+ .create("Resize veil background of Task=" + mTaskInfo.taskId, mSurfaceSession)
+ .setColorLayer()
.setHidden(true)
+ .setParent(mVeilSurface)
+ .setCallsite("ResizeVeil#setupResizeVeil")
+ .build();
+ mIconSurface = mSurfaceControlBuilderFactory
+ .create("Resize veil icon of Task=" + mTaskInfo.taskId)
+ .setContainerLayer()
+ .setHidden(true)
+ .setParent(mVeilSurface)
.setCallsite("ResizeVeil#setupResizeVeil")
.build();
@@ -131,10 +176,20 @@
final WindowlessWindowManager wwm = new WindowlessWindowManager(mTaskInfo.configuration,
mIconSurface, null /* hostInputToken */);
- mViewHost = new SurfaceControlViewHost(mContext, mDisplay, wwm, "ResizeVeil");
+
+ mViewHost = mSurfaceControlViewHostFactory.create(mContext, mDisplay, wwm, "ResizeVeil");
mViewHost.setView(root, lp);
}
+ private boolean obtainDisplayOrRegisterListener() {
+ mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
+ if (mDisplay == null) {
+ mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener);
+ return false;
+ }
+ return true;
+ }
+
/**
* Shows the veil surface/view.
*
@@ -146,6 +201,12 @@
*/
public void showVeil(SurfaceControl.Transaction t, SurfaceControl parentSurface,
Rect taskBounds, boolean fadeIn) {
+ if (!isReady() || isVisible()) {
+ t.apply();
+ return;
+ }
+ mIsShowing = true;
+
// Parent surface can change, ensure it is up to date.
if (!parentSurface.equals(mParentSurface)) {
t.reparent(mVeilSurface, parentSurface);
@@ -226,6 +287,9 @@
* Animate veil's alpha to 1, fading it in.
*/
public void showVeil(SurfaceControl parentSurface, Rect taskBounds) {
+ if (!isReady() || isVisible()) {
+ return;
+ }
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
showVeil(t, parentSurface, taskBounds, true /* fadeIn */);
}
@@ -247,6 +311,9 @@
* @param newBounds bounds to update veil to.
*/
public void updateResizeVeil(Rect newBounds) {
+ if (!isVisible()) {
+ return;
+ }
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
updateResizeVeil(t, newBounds);
}
@@ -260,6 +327,10 @@
* @param newBounds bounds to update veil to.
*/
public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) {
+ if (!isVisible()) {
+ t.apply();
+ return;
+ }
if (mVeilAnimator != null && mVeilAnimator.isStarted()) {
mVeilAnimator.removeAllUpdateListeners();
mVeilAnimator.end();
@@ -272,6 +343,9 @@
* Animate veil's alpha to 0, fading it out.
*/
public void hideVeil() {
+ if (!isVisible()) {
+ return;
+ }
cancelAnimation();
mVeilAnimator = new ValueAnimator();
mVeilAnimator.setFloatValues(1, 0);
@@ -292,6 +366,7 @@
}
});
mVeilAnimator.start();
+ mIsShowing = false;
}
@ColorRes
@@ -318,10 +393,26 @@
}
/**
+ * Whether the resize veil is currently visible.
+ *
+ * Note: when animating a {@link ResizeVeil#hideVeil()}, the veil is considered visible as soon
+ * as the animation starts.
+ */
+ private boolean isVisible() {
+ return mIsShowing;
+ }
+
+ /** Whether the resize veil is ready to be shown. */
+ private boolean isReady() {
+ return mViewHost != null;
+ }
+
+ /**
* Dispose of veil when it is no longer needed, likely on close of its container decor.
*/
void dispose() {
cancelAnimation();
+ mIsShowing = false;
mVeilAnimator = null;
if (mViewHost != null) {
@@ -342,5 +433,16 @@
mVeilSurface = null;
}
t.apply();
+ mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
+ }
+
+ interface SurfaceControlBuilderFactory {
+ default SurfaceControl.Builder create(@NonNull String name) {
+ return new SurfaceControl.Builder().setName(name);
+ }
+ default SurfaceControl.Builder create(@NonNull String name,
+ @NonNull SurfaceSession surfaceSession) {
+ return new SurfaceControl.Builder(surfaceSession).setName(name);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
index d0fcd86..53d4e27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
@@ -72,7 +72,10 @@
}
void closeTask(WindowContainerToken taskToken) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
+ closeTask(taskToken, new WindowContainerTransaction());
+ }
+
+ void closeTask(WindowContainerToken taskToken, WindowContainerTransaction wct) {
wct.removeTask(taskToken);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mTransitionStarter.startRemoveTransition(wct);
@@ -91,14 +94,12 @@
}
}
- void maximizeTask(RunningTaskInfo taskInfo) {
+ void maximizeTask(RunningTaskInfo taskInfo, int containerWindowingMode) {
WindowContainerTransaction wct = new WindowContainerTransaction();
int targetWindowingMode = taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_FREEFORM;
- int displayWindowingMode =
- taskInfo.configuration.windowConfiguration.getDisplayWindowingMode();
wct.setWindowingMode(taskInfo.token,
- targetWindowingMode == displayWindowingMode
+ targetWindowingMode == containerWindowingMode
? WINDOWING_MODE_UNDEFINED : targetWindowingMode);
if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) {
wct.setBounds(taskInfo.token, null);
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 51b0a24..36da1ac 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
@@ -668,6 +668,10 @@
default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
return new SurfaceControlViewHost(c, d, wmm, "WindowDecoration");
}
+ default SurfaceControlViewHost create(Context c, Display d,
+ WindowlessWindowManager wmm, String callsite) {
+ return new SurfaceControlViewHost(c, d, wmm, callsite);
+ }
}
/**
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index d64bfed..b85d793 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -33,7 +33,7 @@
/**
* Test entering pip from an app via auto-enter property when navigating to home.
*
- * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:AutoEnterPipOnGoToHomeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
index 371fee2..d059211 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
@@ -30,7 +30,7 @@
/**
* Test auto entering pip using a source rect hint.
*
- * To run this test: `atest AutoEnterPipWithSourceRectHintTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:AutoEnterPipWithSourceRectHintTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index 1c0820a..a5e0550 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -31,7 +31,7 @@
/**
* Test closing a pip window by swiping it to the bottom-center of the screen
*
- * To run this test: `atest WMShellFlickerTests:ExitPipWithSwipeDownTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:ClosePipBySwipingDownTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index 860307f..d177624 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -30,7 +30,7 @@
/**
* Test closing a pip window via the dismiss button
*
- * To run this test: `atest WMShellFlickerTests:ExitPipWithDismissButtonTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:ClosePipWithDismissButtonTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index c554161..a86803d 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -31,7 +31,7 @@
/**
* Test entering pip from an app via [onUserLeaveHint] and by navigating to home.
*
- * To run this test: `atest WMShellFlickerTests:EnterPipOnUserLeaveHintTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:EnterPipOnUserLeaveHintTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index 270ebf5..a0a61fe2 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -46,7 +46,7 @@
/**
* Test entering pip while changing orientation (from app in landscape to pip window in portrait)
*
- * To run this test: `atest WMShellFlickerTests:EnterPipToOtherOrientationTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:EnterPipToOtherOrientation`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index f97d8d1..d92f55a 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -28,7 +28,7 @@
/**
* Test entering pip from an app by interacting with the app UI
*
- * To run this test: `atest WMShellFlickerTests:EnterPipTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:EnterPipViaAppUiButtonTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 47bf418..8c0817d6 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -28,7 +28,7 @@
/**
* Test expanding a pip window back to full screen via the expand button
*
- * To run this test: `atest WMShellFlickerTests:ExitPipViaExpandButtonClickTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:ExitPipToAppViaExpandButtonTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index a356e68..90a9623 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -28,7 +28,7 @@
/**
* Test expanding a pip window back to full screen via an intent
*
- * To run this test: `atest WMShellFlickerTests:ExitPipViaIntentTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:ExitPipToAppViaIntentTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index eeff167..9306c77 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -33,7 +33,7 @@
/**
* Test expanding a pip window by double-clicking it
*
- * To run this test: `atest WMShellFlickerTests:ExpandPipOnDoubleClickTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:ExpandPipOnDoubleClickTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
index 12e395d..cb8ee27 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
@@ -39,7 +39,7 @@
/**
* Test entering pip from an app via auto-enter property when navigating to home from split screen.
*
- * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:FromSplitScreenAutoEnterPipOnGoToHomeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
index f81e849..f2f10ae 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
@@ -40,7 +40,7 @@
/**
* Test entering pip from an app via auto-enter property when navigating to home from split screen.
*
- * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:FromSplitScreenEnterPipOnUserLeaveHintTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 9b74622..265eb44 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -32,7 +32,7 @@
/**
* Test Pip movement with Launcher shelf height change (increase).
*
- * To run this test: `atest WMShellFlickerTests:MovePipUpShelfHeightChangeTest`
+ * To run this test: `atest WMShellFlickerTestsPip3:MovePipDownOnShelfHeightChange`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index ad3c69e..04fedf4 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -34,7 +34,10 @@
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-/** Test Pip launch. To run this test: `atest WMShellFlickerTests:PipKeyboardTest` */
+/**
+ * Test Pip launch. To run this test:
+ * `atest WMShellFlickerTestsPip3:MovePipOnImeVisibilityChangeTest`
+ */
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index 490ebd1..8d6be64 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -32,7 +32,7 @@
/**
* Test Pip movement with Launcher shelf height change (decrease).
*
- * To run this test: `atest WMShellFlickerTests:MovePipDownShelfHeightChangeTest`
+ * To run this test: `atest WMShellFlickerTestsPip3:MovePipUpOnShelfHeightChangeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index 9a6dacb..ed2a0a7 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -41,8 +41,8 @@
import org.junit.runners.Parameterized
/**
- * Test exiting Pip with orientation changes. To run this test: `atest
- * WMShellFlickerTests:SetRequestedOrientationWhilePinnedTest`
+ * Test exiting Pip with orientation changes. To run this test:
+ * `atest WMShellFlickerTestsPip1:SetRequestedOrientationWhilePinned`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index d2f803e..9109eaf 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -35,7 +35,7 @@
/**
* Test Pip Stack in bounds after rotations.
*
- * To run this test: `atest WMShellFlickerTests:PipRotationTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:ShowPipAndRotateDisplay`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 32c0703..13f95cc 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -39,7 +39,7 @@
static_libs: [
"WindowManager-Shell",
"junit",
- "flag-junit-base",
+ "flag-junit",
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
@@ -55,6 +55,9 @@
"platform-test-annotations",
"servicestests-utils",
"com_android_wm_shell_flags_lib",
+ "guava-android-testlib",
+ "com.android.window.flags.window-aconfig-java",
+ "platform-test-annotations",
],
libs: [
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
index 3672ae3..24f4d92 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
@@ -23,8 +23,10 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
+import android.content.Intent;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
@@ -38,6 +40,7 @@
private WindowContainerToken mToken = createMockWCToken();
private int mParentTaskId = INVALID_TASK_ID;
+ private Intent mBaseIntent = new Intent();
private @WindowConfiguration.ActivityType int mActivityType = ACTIVITY_TYPE_STANDARD;
private @WindowConfiguration.WindowingMode int mWindowingMode = WINDOWING_MODE_UNDEFINED;
private int mDisplayId = Display.DEFAULT_DISPLAY;
@@ -68,6 +71,15 @@
return this;
}
+ /**
+ * Set {@link ActivityManager.RunningTaskInfo#baseIntent} for the task info, by default
+ * an empty intent is assigned
+ */
+ public TestRunningTaskInfoBuilder setBaseIntent(@NonNull Intent intent) {
+ mBaseIntent = intent;
+ return this;
+ }
+
public TestRunningTaskInfoBuilder setActivityType(
@WindowConfiguration.ActivityType int activityType) {
mActivityType = activityType;
@@ -109,6 +121,7 @@
public ActivityManager.RunningTaskInfo build() {
final ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
info.taskId = sNextTaskId++;
+ info.baseIntent = mBaseIntent;
info.parentTaskId = mParentTaskId;
info.displayId = mDisplayId;
info.configuration.windowConfiguration.setBounds(mBounds);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 4061763..65169e3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -596,6 +596,7 @@
// Set up the monitoring objects.
doNothing().when(runner).onAnimationStart(anyInt(), any(), any(), any(), any());
+ doReturn(false).when(animationRunner).shouldMonitorCUJ(any());
doReturn(runner).when(animationRunner).getRunner();
doReturn(callback).when(animationRunner).getCallback();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 0c45d52..b2b54ac 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -119,6 +119,57 @@
}
@Test
+ fun isOnlyActiveTask_noActiveTasks() {
+ // Not an active task
+ assertThat(repo.isOnlyActiveTask(1)).isFalse()
+ }
+
+ @Test
+ fun isOnlyActiveTask_singleActiveTask() {
+ repo.addActiveTask(DEFAULT_DISPLAY, 1)
+ // The only active task
+ assertThat(repo.isActiveTask(1)).isTrue()
+ assertThat(repo.isOnlyActiveTask(1)).isTrue()
+ // Not an active task
+ assertThat(repo.isActiveTask(99)).isFalse()
+ assertThat(repo.isOnlyActiveTask(99)).isFalse()
+ }
+
+ @Test
+ fun isOnlyActiveTask_multipleActiveTasks() {
+ repo.addActiveTask(DEFAULT_DISPLAY, 1)
+ repo.addActiveTask(DEFAULT_DISPLAY, 2)
+ // Not the only task
+ assertThat(repo.isActiveTask(1)).isTrue()
+ assertThat(repo.isOnlyActiveTask(1)).isFalse()
+ // Not the only task
+ assertThat(repo.isActiveTask(2)).isTrue()
+ assertThat(repo.isOnlyActiveTask(2)).isFalse()
+ // Not an active task
+ assertThat(repo.isActiveTask(99)).isFalse()
+ assertThat(repo.isOnlyActiveTask(99)).isFalse()
+ }
+
+ @Test
+ fun isOnlyActiveTask_multipleDisplays() {
+ repo.addActiveTask(DEFAULT_DISPLAY, 1)
+ repo.addActiveTask(DEFAULT_DISPLAY, 2)
+ repo.addActiveTask(SECOND_DISPLAY, 3)
+ // Not the only task on DEFAULT_DISPLAY
+ assertThat(repo.isActiveTask(1)).isTrue()
+ assertThat(repo.isOnlyActiveTask(1)).isFalse()
+ // Not the only task on DEFAULT_DISPLAY
+ assertThat(repo.isActiveTask(2)).isTrue()
+ assertThat(repo.isOnlyActiveTask(2)).isFalse()
+ // The only active task on SECOND_DISPLAY
+ assertThat(repo.isActiveTask(3)).isTrue()
+ assertThat(repo.isOnlyActiveTask(3)).isTrue()
+ // Not an active task
+ assertThat(repo.isActiveTask(99)).isFalse()
+ assertThat(repo.isOnlyActiveTask(99)).isFalse()
+ }
+
+ @Test
fun addListener_notifiesVisibleFreeformTask() {
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
val listener = TestVisibilityListener()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 93a967e..64f6041 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -23,10 +23,13 @@
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.content.Intent
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
import android.os.Binder
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
@@ -34,11 +37,15 @@
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.DisplayAreaInfo
import android.window.RemoteTransition
import android.window.TransitionRequestInfo
+import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT
+import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
@@ -161,6 +168,10 @@
(i.arguments.first() as Rect).set(STABLE_BOUNDS)
}
+ val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
+
controller = createController()
controller.setSplitScreenController(splitScreenController)
@@ -219,7 +230,8 @@
}
@Test
- fun showDesktopApps_allAppsInvisible_bringsToFront() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperDisabled() {
val homeTask = setUpHomeTask()
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -238,7 +250,27 @@
}
@Test
- fun showDesktopApps_appsAlreadyVisible_bringsToFront() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperEnabled() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: wallpaper intent, task1, task2
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperDisabled() {
val homeTask = setUpHomeTask()
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -257,7 +289,27 @@
}
@Test
- fun showDesktopApps_someAppsInvisible_reordersAll() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskVisible(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: wallpaper intent, task1, task2
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperDisabled() {
val homeTask = setUpHomeTask()
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -276,7 +328,27 @@
}
@Test
- fun showDesktopApps_noActiveTasks_reorderHomeToTop() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperEnabled() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: wallpaper intent, task1, task2
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_noActiveTasks_reorderHomeToTop_desktopWallpaperDisabled() {
val homeTask = setUpHomeTask()
controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
@@ -288,7 +360,18 @@
}
@Test
- fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_noActiveTasks_addDesktopWallpaper_desktopWallpaperEnabled() {
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
setUpHomeTask(SECOND_DISPLAY)
@@ -307,6 +390,25 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
+ val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
+ setUpHomeTask(SECOND_DISPLAY)
+ val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
+ markTaskHidden(taskDefaultDisplay)
+ markTaskHidden(taskSecondDisplay)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(2)
+ // Expect order to be from bottom: wallpaper intent, task
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 1, taskDefaultDisplay)
+ }
+
+ @Test
fun getVisibleTaskCount_noTasks_returnsZero() {
assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
}
@@ -336,9 +438,10 @@
}
@Test
- fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() {
+ fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
val task = setUpFullscreenTask()
- task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
controller.moveToDesktop(task)
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
@@ -346,9 +449,10 @@
}
@Test
- fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() {
+ fun moveToDesktop_tdaFreeform_windowingModeSetToUndefined() {
val task = setUpFullscreenTask()
- task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
controller.moveToDesktop(task)
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
@@ -413,7 +517,8 @@
}
@Test
- fun moveToDesktop_otherFreeformTasksBroughtToFront() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperDisabled() {
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
val fullscreenTask = setUpFullscreenTask()
@@ -431,6 +536,26 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() {
+ val freeformTask = setUpFreeformTask()
+ val fullscreenTask = setUpFullscreenTask()
+ markTaskHidden(freeformTask)
+
+ controller.moveToDesktop(fullscreenTask)
+
+ with(getLatestMoveToDesktopWct()) {
+ // Operations should include wallpaper intent, freeform task, fullscreen task
+ assertThat(hierarchyOps).hasSize(3)
+ assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ assertReorderAt(index = 1, freeformTask)
+ assertReorderAt(index = 2, fullscreenTask)
+ assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+ }
+
+ @Test
fun moveToDesktop_onlyFreeformTasksFromCurrentDisplayBroughtToFront() {
setUpHomeTask(displayId = DEFAULT_DISPLAY)
val freeformTaskDefault = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -481,9 +606,10 @@
}
@Test
- fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() {
+ fun moveToFullscreen_tdaFullscreen_windowingModeSetToUndefined() {
val task = setUpFreeformTask()
- task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
controller.moveToFullscreen(task.taskId)
val wct = getLatestExitDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
@@ -491,9 +617,10 @@
}
@Test
- fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() {
+ fun moveToFullscreen_tdaFreeform_windowingModeSetToFullscreen() {
val task = setUpFreeformTask()
- task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
controller.moveToFullscreen(task.taskId)
val wct = getLatestExitDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
@@ -595,6 +722,48 @@
}
@Test
+ fun onDesktopWindowClose_noActiveTasks() {
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, 1 /* taskId */)
+ // Doesn't modify transaction
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
+ fun onDesktopWindowClose_singleActiveTask_noWallpaperActivityToken() {
+ val task = setUpFreeformTask()
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, task.taskId)
+ // Doesn't modify transaction
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
+ fun onDesktopWindowClose_singleActiveTask_hasWallpaperActivityToken() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, task.taskId)
+ // Adds remove wallpaper operation
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun onDesktopWindowClose_multipleActiveTasks() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, task1.taskId)
+ // Doesn't modify transaction
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
@@ -638,6 +807,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
@@ -684,7 +854,7 @@
createTransition(freeformTask2, type = TRANSIT_TO_FRONT)
)
assertThat(result?.changes?.get(freeformTask2.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
}
@Test
@@ -694,7 +864,7 @@
val task = createFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task))
assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
}
@Test
@@ -706,10 +876,11 @@
val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
assertThat(result?.changes?.get(taskDefaultDisplay.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
@@ -792,7 +963,56 @@
val result = controller.handleRequest(Binder(), createTransition(task))
assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_singleActiveTask_noToken() {
+ val task = setUpFreeformTask()
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+ // Doesn't handle request
+ assertThat(result).isNull()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_singleActiveTask_hasToken_desktopWallpaperDisabled() {
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+
+ val task = setUpFreeformTask()
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+ // Doesn't handle request
+ assertThat(result).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_singleActiveTask_hasToken_desktopWallpaperEnabled() {
+ val wallpaperToken = MockToken().token()
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+
+ val task = setUpFreeformTask()
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+ assertThat(result).isNotNull()
+ // Creates remove wallpaper transaction
+ result!!.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_multipleActiveTasks() {
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+ // Doesn't handle request
+ assertThat(result).isNull()
}
@Test
@@ -895,7 +1115,7 @@
val wct = getLatestExitDesktopWct()
assertThat(wct.changes[task2.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
}
@Test
@@ -998,6 +1218,9 @@
assertThat(desktopModeTaskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull()
}
+ private val desktopWallpaperIntent: Intent
+ get() = Intent(context, DesktopWallpaperActivity::class.java)
+
private fun setUpFreeformTask(
displayId: Int = DEFAULT_DISPLAY,
bounds: Rect? = null
@@ -1123,10 +1346,14 @@
}
}
-private fun WindowContainerTransaction.assertReorderAt(index: Int, task: RunningTaskInfo) {
+private fun WindowContainerTransaction.assertIndexInBounds(index: Int) {
assertWithMessage("WCT does not have a hierarchy operation at index $index")
.that(hierarchyOps.size)
.isGreaterThan(index)
+}
+
+private fun WindowContainerTransaction.assertReorderAt(index: Int, task: RunningTaskInfo) {
+ assertIndexInBounds(index)
val op = hierarchyOps[index]
assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER)
assertThat(op.container).isEqualTo(task.token.asBinder())
@@ -1137,3 +1364,17 @@
assertReorderAt(i, tasks[i])
}
}
+
+private fun WindowContainerTransaction.assertRemoveAt(index: Int, token: WindowContainerToken) {
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ assertThat(op.container).isEqualTo(token.asBinder())
+}
+
+private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: Intent) {
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_PENDING_INTENT)
+ assertThat(op.pendingIntent?.intent?.component).isEqualTo(intent.component)
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index d38e97f..40b59c1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -35,6 +35,7 @@
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -45,16 +46,21 @@
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.Bundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
@@ -70,6 +76,7 @@
import com.android.wm.shell.util.SplitBounds;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -99,6 +106,11 @@
private ActivityTaskManager mActivityTaskManager;
@Mock
private DisplayInsetsController mDisplayInsetsController;
+ @Mock
+ private IRecentTasksListener mRecentTasksListener;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private ShellTaskOrganizer mShellTaskOrganizer;
private RecentTasksController mRecentTasksController;
@@ -426,6 +438,85 @@
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS})
+ public void onTaskAdded_desktopModeRunningAppsEnabled_triggersOnRunningTaskAppeared()
+ throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+ mRecentTasksControllerReal.onTaskAdded(taskInfo);
+
+ verify(mRecentTasksListener).onRunningTaskAppeared(taskInfo);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS)
+ public void onTaskAdded_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskAppeared()
+ throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+ mRecentTasksControllerReal.onTaskAdded(taskInfo);
+
+ verify(mRecentTasksListener, never()).onRunningTaskAppeared(any());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS})
+ public void taskWindowingModeChanged_desktopRunningAppsEnabled_triggersOnRunningTaskChanged()
+ throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+ mRecentTasksControllerReal.onTaskWindowingModeChanged(taskInfo);
+
+ verify(mRecentTasksListener).onRunningTaskChanged(taskInfo);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS)
+ public void
+ taskWindowingModeChanged_desktopRunningAppsDisabled_doesNotTriggerOnRunningTaskChanged()
+ throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+ mRecentTasksControllerReal.onTaskWindowingModeChanged(taskInfo);
+
+ verify(mRecentTasksListener, never()).onRunningTaskChanged(any());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS})
+ public void onTaskRemoved_desktopModeRunningAppsEnabled_triggersOnRunningTaskVanished()
+ throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+ mRecentTasksControllerReal.onTaskRemoved(taskInfo);
+
+ verify(mRecentTasksListener).onRunningTaskVanished(taskInfo);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS)
+ public void onTaskRemoved_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskVanished()
+ throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+ mRecentTasksControllerReal.onTaskRemoved(taskInfo);
+
+ verify(mRecentTasksListener, never()).onRunningTaskVanished(any());
+ }
+
+ @Test
public void getNullSplitBoundsNonSplitTask() {
SplitBounds sb = mRecentTasksController.getSplitBoundsForTaskId(3);
assertNull("splitBounds should be null for non-split task", sb);
@@ -471,6 +562,7 @@
private ActivityManager.RunningTaskInfo makeRunningTaskInfo(int taskId) {
ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
info.taskId = taskId;
+ info.realActivity = new ComponentName("testPackage", "testClass");
return info;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index e7d37ad..0db10ef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -24,6 +24,7 @@
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
@@ -167,6 +168,25 @@
}
@Test
+ public void testStartDragToDesktopDoesNotTriggerCallback() throws RemoteException {
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+ when(info.getType()).thenReturn(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP);
+
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN, true);
+
+ mHomeTransitionObserver.onTransitionReady(mock(IBinder.class),
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ verify(mListener, times(0)).onHomeVisibilityChanged(anyBoolean());
+ }
+
+ @Test
public void testHomeActivityWithBackGestureNotifiesHomeIsVisible() throws RemoteException {
TransitionInfo info = mock(TransitionInfo.class);
TransitionInfo.Change change = mock(TransitionInfo.Change.class);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
new file mode 100644
index 0000000..82e5a1c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.graphics.Region;
+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.AndroidTestingRunner;
+import android.util.Size;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.window.flags.Flags;
+
+import com.google.common.testing.EqualsTester;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link DragResizeWindowGeometry}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DragResizeWindowGeometryTests
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DragResizeWindowGeometryTests {
+ private static final Size TASK_SIZE = new Size(500, 1000);
+ private static final int TASK_CORNER_RADIUS = 10;
+ private static final int EDGE_RESIZE_THICKNESS = 15;
+ private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
+ private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
+ private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
+ TASK_CORNER_RADIUS, TASK_SIZE, EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE,
+ LARGE_CORNER_SIZE);
+ // Points in the edge resize handle. Note that coordinates start from the top left.
+ private static final Point TOP_EDGE_POINT = new Point(TASK_SIZE.getWidth() / 2,
+ -EDGE_RESIZE_THICKNESS / 2);
+ private static final Point LEFT_EDGE_POINT = new Point(-EDGE_RESIZE_THICKNESS / 2,
+ TASK_SIZE.getHeight() / 2);
+ private static final Point RIGHT_EDGE_POINT = new Point(
+ TASK_SIZE.getWidth() + EDGE_RESIZE_THICKNESS / 2, TASK_SIZE.getHeight() / 2);
+ private static final Point BOTTOM_EDGE_POINT = new Point(TASK_SIZE.getWidth() / 2,
+ TASK_SIZE.getHeight() + EDGE_RESIZE_THICKNESS / 2);
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ /**
+ * Check that both groups of objects satisfy equals/hashcode within each group, and that each
+ * group is distinct from the next.
+ */
+ @Test
+ public void testEqualsAndHash() {
+ new EqualsTester()
+ .addEqualityGroup(
+ GEOMETRY,
+ new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+ EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
+ .addEqualityGroup(
+ new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+ EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE),
+ new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+ EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
+ .addEqualityGroup(
+ new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+ EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+ LARGE_CORNER_SIZE + 5),
+ new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+ EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+ LARGE_CORNER_SIZE + 5))
+ .testEquals();
+ }
+
+ @Test
+ public void testGetTaskSize() {
+ assertThat(GEOMETRY.getTaskSize()).isEqualTo(TASK_SIZE);
+ }
+
+ @Test
+ public void testRegionUnionContainsEdges() {
+ Region region = new Region();
+ GEOMETRY.union(region);
+ assertThat(region.isComplex()).isTrue();
+ // Region excludes task area. Note that coordinates start from top left.
+ assertThat(region.contains(TASK_SIZE.getWidth() / 2, TASK_SIZE.getHeight() / 2)).isFalse();
+ // Region includes edges outside the task window.
+ verifyVerticalEdge(region, LEFT_EDGE_POINT);
+ verifyHorizontalEdge(region, TOP_EDGE_POINT);
+ verifyVerticalEdge(region, RIGHT_EDGE_POINT);
+ verifyHorizontalEdge(region, BOTTOM_EDGE_POINT);
+ }
+
+ private static void verifyHorizontalEdge(@NonNull Region region, @NonNull Point point) {
+ assertThat(region.contains(point.x, point.y)).isTrue();
+ // Horizontally along the edge is still contained.
+ assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue();
+ assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue();
+ // Vertically along the edge is not contained.
+ assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse();
+ assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse();
+ }
+
+ private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) {
+ assertThat(region.contains(point.x, point.y)).isTrue();
+ // Horizontally along the edge is not contained.
+ assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isFalse();
+ assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isFalse();
+ // Vertically along the edge is contained.
+ assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isTrue();
+ assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isTrue();
+ }
+
+ /**
+ * Validate that with the flag enabled, the corner resize regions are the largest size, to
+ * capture all eligible input regardless of source (touch or cursor).
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() {
+ Region region = new Region();
+ GEOMETRY.union(region);
+ final int cornerRadius = LARGE_CORNER_SIZE / 2;
+
+ new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
+ }
+
+ /**
+ * Validate that with the flag disabled, the corner resize regions are the original smaller
+ * size.
+ */
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() {
+ Region region = new Region();
+ GEOMETRY.union(region);
+ final int cornerRadius = FINE_CORNER_SIZE / 2;
+
+ new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ public void testCalculateControlType_edgeDragResizeEnabled_edges() {
+ // The input source (touch or cursor) shouldn't impact the edge resize size.
+ validateCtrlTypeForEdges(/* isTouch= */ false);
+ validateCtrlTypeForEdges(/* isTouch= */ true);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ public void testCalculateControlType_edgeDragResizeDisabled_edges() {
+ // Edge resizing is not supported when the flag is disabled.
+ validateCtrlTypeForEdges(/* isTouch= */ false);
+ validateCtrlTypeForEdges(/* isTouch= */ false);
+ }
+
+ private void validateCtrlTypeForEdges(boolean isTouch) {
+ assertThat(GEOMETRY.calculateCtrlType(isTouch, LEFT_EDGE_POINT.x,
+ LEFT_EDGE_POINT.y)).isEqualTo(CTRL_TYPE_LEFT);
+ assertThat(GEOMETRY.calculateCtrlType(isTouch, TOP_EDGE_POINT.x,
+ TOP_EDGE_POINT.y)).isEqualTo(CTRL_TYPE_TOP);
+ assertThat(GEOMETRY.calculateCtrlType(isTouch, RIGHT_EDGE_POINT.x,
+ RIGHT_EDGE_POINT.y)).isEqualTo(CTRL_TYPE_RIGHT);
+ assertThat(GEOMETRY.calculateCtrlType(isTouch, BOTTOM_EDGE_POINT.x,
+ BOTTOM_EDGE_POINT.y)).isEqualTo(CTRL_TYPE_BOTTOM);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ public void testCalculateControlType_edgeDragResizeEnabled_corners() {
+ final TestPoints fineTestPoints = new TestPoints(TASK_SIZE, FINE_CORNER_SIZE / 2);
+ final TestPoints largeCornerTestPoints = new TestPoints(TASK_SIZE, LARGE_CORNER_SIZE / 2);
+
+ // When the flag is enabled, points within fine corners should pass regardless of touch or
+ // not. Points outside fine corners should not pass when using a course input (non-touch).
+ fineTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ true, true);
+ fineTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ true, true);
+ fineTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ false, true);
+ fineTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ false, false);
+
+ // When the flag is enabled, points near the large corners should only pass when the point
+ // is within the corner for large touch inputs.
+ largeCornerTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ true, true);
+ largeCornerTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ true,
+ false);
+ largeCornerTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ false, false);
+ largeCornerTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ false,
+ false);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ public void testCalculateControlType_edgeDragResizeDisabled_corners() {
+ final TestPoints fineTestPoints = new TestPoints(TASK_SIZE, FINE_CORNER_SIZE / 2);
+ final TestPoints largeCornerTestPoints = new TestPoints(TASK_SIZE, LARGE_CORNER_SIZE / 2);
+
+ // When the flag is disabled, points within fine corners should pass only when touch.
+ fineTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ true, true);
+ fineTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ true, false);
+ fineTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ false, false);
+ fineTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ false, false);
+
+ // When the flag is disabled, points near the large corners should never pass.
+ largeCornerTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ true, false);
+ largeCornerTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ true,
+ false);
+ largeCornerTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ false, false);
+ largeCornerTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ false,
+ false);
+ }
+
+ /**
+ * Class for creating points for testing the drag resize corners.
+ *
+ * <p>Creates points that are both just within the bounds of each corner, and just outside.
+ */
+ private static final class TestPoints {
+ private final Point mTopLeftPoint;
+ private final Point mTopLeftPointOutside;
+ private final Point mTopRightPoint;
+ private final Point mTopRightPointOutside;
+ private final Point mBottomLeftPoint;
+ private final Point mBottomLeftPointOutside;
+ private final Point mBottomRightPoint;
+ private final Point mBottomRightPointOutside;
+
+ TestPoints(@NonNull Size taskSize, int cornerRadius) {
+ // Point just inside corner square is included.
+ mTopLeftPoint = new Point(-cornerRadius + 1, -cornerRadius + 1);
+ // Point just outside corner square is excluded.
+ mTopLeftPointOutside = new Point(mTopLeftPoint.x - 5, mTopLeftPoint.y - 5);
+
+ mTopRightPoint = new Point(taskSize.getWidth() + cornerRadius - 1, -cornerRadius + 1);
+ mTopRightPointOutside = new Point(mTopRightPoint.x + 5, mTopRightPoint.y - 5);
+
+ mBottomLeftPoint = new Point(-cornerRadius + 1,
+ taskSize.getHeight() + cornerRadius - 1);
+ mBottomLeftPointOutside = new Point(mBottomLeftPoint.x - 5, mBottomLeftPoint.y + 5);
+
+ mBottomRightPoint = new Point(taskSize.getWidth() + cornerRadius - 1,
+ taskSize.getHeight() + cornerRadius - 1);
+ mBottomRightPointOutside = new Point(mBottomRightPoint.x + 5, mBottomRightPoint.y + 5);
+ }
+
+ /**
+ * Validates that all test points are either within or without the given region.
+ */
+ public void validateRegion(@NonNull Region region) {
+ // Point just inside corner square is included.
+ assertThat(region.contains(mTopLeftPoint.x, mTopLeftPoint.y)).isTrue();
+ // Point just outside corner square is excluded.
+ assertThat(region.contains(mTopLeftPointOutside.x, mTopLeftPointOutside.y)).isFalse();
+
+ assertThat(region.contains(mTopRightPoint.x, mTopRightPoint.y)).isTrue();
+ assertThat(
+ region.contains(mTopRightPointOutside.x, mTopRightPointOutside.y)).isFalse();
+
+ assertThat(region.contains(mBottomLeftPoint.x, mBottomLeftPoint.y)).isTrue();
+ assertThat(region.contains(mBottomLeftPointOutside.x,
+ mBottomLeftPointOutside.y)).isFalse();
+
+ assertThat(region.contains(mBottomRightPoint.x, mBottomRightPoint.y)).isTrue();
+ assertThat(region.contains(mBottomRightPointOutside.x,
+ mBottomRightPointOutside.y)).isFalse();
+ }
+
+ /**
+ * Validates that all test points within this drag corner size give the correct
+ * {@code @DragPositioningCallback.CtrlType}.
+ */
+ public void validateCtrlTypeForInnerPoints(@NonNull DragResizeWindowGeometry geometry,
+ boolean isTouch, boolean expectedWithinGeometry) {
+ assertThat(geometry.calculateCtrlType(isTouch, mTopLeftPoint.x,
+ mTopLeftPoint.y)).isEqualTo(
+ expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
+ assertThat(geometry.calculateCtrlType(isTouch, mTopRightPoint.x,
+ mTopRightPoint.y)).isEqualTo(
+ expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
+ assertThat(geometry.calculateCtrlType(isTouch, mBottomLeftPoint.x,
+ mBottomLeftPoint.y)).isEqualTo(
+ expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM
+ : CTRL_TYPE_UNDEFINED);
+ assertThat(geometry.calculateCtrlType(isTouch, mBottomRightPoint.x,
+ mBottomRightPoint.y)).isEqualTo(
+ expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM
+ : CTRL_TYPE_UNDEFINED);
+ }
+
+ /**
+ * Validates that all test points outside this drag corner size give the correct
+ * {@code @DragPositioningCallback.CtrlType}.
+ */
+ public void validateCtrlTypeForOutsidePoints(@NonNull DragResizeWindowGeometry geometry,
+ boolean isTouch, boolean expectedWithinGeometry) {
+ assertThat(geometry.calculateCtrlType(isTouch, mTopLeftPointOutside.x,
+ mTopLeftPointOutside.y)).isEqualTo(
+ expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
+ assertThat(geometry.calculateCtrlType(isTouch, mTopRightPointOutside.x,
+ mTopRightPointOutside.y)).isEqualTo(
+ expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
+ assertThat(geometry.calculateCtrlType(isTouch, mBottomLeftPointOutside.x,
+ mBottomLeftPointOutside.y)).isEqualTo(
+ expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM
+ : CTRL_TYPE_UNDEFINED);
+ assertThat(geometry.calculateCtrlType(isTouch, mBottomRightPointOutside.x,
+ mBottomRightPointOutside.y)).isEqualTo(
+ expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM
+ : CTRL_TYPE_UNDEFINED);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
new file mode 100644
index 0000000..847c2dd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Display
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.WindowlessWindowManager
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
+import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostFactory
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Spy
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
+
+
+/**
+ * Tests for [ResizeVeil].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ResizeVeilTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
[email protected]
+class ResizeVeilTest : ShellTestCase() {
+
+ @Mock
+ private lateinit var mockDisplayController: DisplayController
+ @Mock
+ private lateinit var mockAppIcon: Drawable
+ @Mock
+ private lateinit var mockDisplay: Display
+ @Mock
+ private lateinit var mockSurfaceControlViewHost: SurfaceControlViewHost
+ @Mock
+ private lateinit var mockSurfaceControlBuilderFactory: ResizeVeil.SurfaceControlBuilderFactory
+ @Mock
+ private lateinit var mockSurfaceControlViewHostFactory: SurfaceControlViewHostFactory
+ @Spy
+ private val spyResizeVeilSurfaceBuilder = SurfaceControl.Builder()
+ @Mock
+ private lateinit var mockResizeVeilSurface: SurfaceControl
+ @Spy
+ private val spyBackgroundSurfaceBuilder = SurfaceControl.Builder()
+ @Mock
+ private lateinit var mockBackgroundSurface: SurfaceControl
+ @Spy
+ private val spyIconSurfaceBuilder = SurfaceControl.Builder()
+ @Mock
+ private lateinit var mockIconSurface: SurfaceControl
+ @Mock
+ private lateinit var mockTransaction: SurfaceControl.Transaction
+
+ private val taskInfo = TestRunningTaskInfoBuilder().build()
+
+ @Before
+ fun setUp() {
+ whenever(mockSurfaceControlViewHostFactory.create(any(), any(), any(), any()))
+ .thenReturn(mockSurfaceControlViewHost)
+ whenever(mockSurfaceControlBuilderFactory
+ .create("Resize veil of Task=" + taskInfo.taskId))
+ .thenReturn(spyResizeVeilSurfaceBuilder)
+ doReturn(mockResizeVeilSurface).whenever(spyResizeVeilSurfaceBuilder).build()
+ whenever(mockSurfaceControlBuilderFactory
+ .create(eq("Resize veil background of Task=" + taskInfo.taskId), any()))
+ .thenReturn(spyBackgroundSurfaceBuilder)
+ doReturn(mockBackgroundSurface).whenever(spyBackgroundSurfaceBuilder).build()
+ whenever(mockSurfaceControlBuilderFactory
+ .create("Resize veil icon of Task=" + taskInfo.taskId))
+ .thenReturn(spyIconSurfaceBuilder)
+ doReturn(mockIconSurface).whenever(spyIconSurfaceBuilder).build()
+ }
+
+ @Test
+ fun init_displayAvailable_viewHostCreated() {
+ createResizeVeil(withDisplayAvailable = true)
+
+ verify(mockSurfaceControlViewHostFactory)
+ .create(any(), eq(mockDisplay), any(), eq("ResizeVeil"))
+ }
+
+ @Test
+ fun init_displayUnavailable_viewHostNotCreatedUntilDisplayAppears() {
+ createResizeVeil(withDisplayAvailable = false)
+
+ verify(mockSurfaceControlViewHostFactory, never())
+ .create(any(), eq(mockDisplay), any<WindowlessWindowManager>(), eq("ResizeVeil"))
+ val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java)
+ verify(mockDisplayController).addDisplayWindowListener(captor.capture())
+
+ whenever(mockDisplayController.getDisplay(taskInfo.displayId)).thenReturn(mockDisplay)
+ captor.value.onDisplayAdded(taskInfo.displayId)
+
+ verify(mockSurfaceControlViewHostFactory)
+ .create(any(), eq(mockDisplay), any(), eq("ResizeVeil"))
+ verify(mockDisplayController).removeDisplayWindowListener(any())
+ }
+
+ @Test
+ fun dispose_removesDisplayWindowListener() {
+ createResizeVeil().dispose()
+
+ verify(mockDisplayController).removeDisplayWindowListener(any())
+ }
+
+ @Test
+ fun showVeil() {
+ val veil = createResizeVeil()
+ val tx = mock<SurfaceControl.Transaction>()
+
+ veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+
+ verify(tx).show(mockResizeVeilSurface)
+ verify(tx).show(mockBackgroundSurface)
+ verify(tx).show(mockIconSurface)
+ verify(tx).apply()
+ }
+
+ @Test
+ fun showVeil_displayUnavailable_doesNotShow() {
+ val veil = createResizeVeil(withDisplayAvailable = false)
+ val tx = mock<SurfaceControl.Transaction>()
+
+ veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+
+ verify(tx, never()).show(mockResizeVeilSurface)
+ verify(tx, never()).show(mockBackgroundSurface)
+ verify(tx, never()).show(mockIconSurface)
+ verify(tx).apply()
+ }
+
+ @Test
+ fun showVeil_alreadyVisible_doesNotShowAgain() {
+ val veil = createResizeVeil()
+ val tx = mock<SurfaceControl.Transaction>()
+
+ veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+ veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+
+ verify(tx, times(1)).show(mockResizeVeilSurface)
+ verify(tx, times(1)).show(mockBackgroundSurface)
+ verify(tx, times(1)).show(mockIconSurface)
+ verify(tx, times(2)).apply()
+ }
+
+ @Test
+ fun showVeil_reparentsVeilToNewParent() {
+ val veil = createResizeVeil(parent = mock())
+ val tx = mock<SurfaceControl.Transaction>()
+
+ val newParent = mock<SurfaceControl>()
+ veil.showVeil(tx, newParent, Rect(0, 0, 100, 100), false /* fadeIn */)
+
+ verify(tx).reparent(mockResizeVeilSurface, newParent)
+ }
+
+ @Test
+ fun hideVeil_alreadyHidden_doesNothing() {
+ val veil = createResizeVeil()
+
+ veil.hideVeil()
+
+ verifyZeroInteractions(mockTransaction)
+ }
+
+ private fun createResizeVeil(
+ withDisplayAvailable: Boolean = true,
+ parent: SurfaceControl = mock()
+ ): ResizeVeil {
+ whenever(mockDisplayController.getDisplay(taskInfo.displayId))
+ .thenReturn(if (withDisplayAvailable) mockDisplay else null)
+ return ResizeVeil(
+ context,
+ mockDisplayController,
+ mockAppIcon,
+ taskInfo,
+ parent,
+ { mockTransaction },
+ mockSurfaceControlBuilderFactory,
+ mockSurfaceControlViewHostFactory
+ )
+ }
+}
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index 34a6bc2..839c7b6 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -119,30 +119,41 @@
* appear to be bogus.
*/
bool ZipFileRO::getEntryInfo(ZipEntryRO entry, uint16_t* pMethod,
+ uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset,
+ uint32_t* pModWhen, uint32_t* pCrc32) const
+{
+ return getEntryInfo(entry, pMethod, pUncompLen, pCompLen, pOffset, pModWhen,
+ pCrc32, nullptr);
+}
+
+bool ZipFileRO::getEntryInfo(ZipEntryRO entry, uint16_t* pMethod,
uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset,
- uint32_t* pModWhen, uint32_t* pCrc32) const
+ uint32_t* pModWhen, uint32_t* pCrc32, uint16_t* pExtraFieldSize) const
{
const _ZipEntryRO* zipEntry = reinterpret_cast<_ZipEntryRO*>(entry);
const ZipEntry& ze = zipEntry->entry;
- if (pMethod != NULL) {
+ if (pMethod != nullptr) {
*pMethod = ze.method;
}
- if (pUncompLen != NULL) {
+ if (pUncompLen != nullptr) {
*pUncompLen = ze.uncompressed_length;
}
- if (pCompLen != NULL) {
+ if (pCompLen != nullptr) {
*pCompLen = ze.compressed_length;
}
- if (pOffset != NULL) {
+ if (pOffset != nullptr) {
*pOffset = ze.offset;
}
- if (pModWhen != NULL) {
+ if (pModWhen != nullptr) {
*pModWhen = ze.mod_time;
}
- if (pCrc32 != NULL) {
+ if (pCrc32 != nullptr) {
*pCrc32 = ze.crc32;
}
+ if (pExtraFieldSize != nullptr) {
+ *pExtraFieldSize = ze.extra_field_size;
+ }
return true;
}
diff --git a/libs/androidfw/include/androidfw/ZipFileRO.h b/libs/androidfw/include/androidfw/ZipFileRO.h
index 031d2e8..f7c5007 100644
--- a/libs/androidfw/include/androidfw/ZipFileRO.h
+++ b/libs/androidfw/include/androidfw/ZipFileRO.h
@@ -151,6 +151,10 @@
uint32_t* pCompLen, off64_t* pOffset, uint32_t* pModWhen,
uint32_t* pCrc32) const;
+ bool getEntryInfo(ZipEntryRO entry, uint16_t* pMethod,
+ uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset,
+ uint32_t* pModWhen, uint32_t* pCrc32, uint16_t* pExtraFieldSize) const;
+
/*
* Create a new FileMap object that maps a subset of the archive. For
* an uncompressed entry this effectively provides a pointer to the
diff --git a/libs/hostgraphics/gui/Surface.h b/libs/hostgraphics/gui/Surface.h
index 2573931..36d8fba 100644
--- a/libs/hostgraphics/gui/Surface.h
+++ b/libs/hostgraphics/gui/Surface.h
@@ -52,6 +52,8 @@
virtual void destroy() {}
+ int getBuffersDataSpace() { return 0; }
+
protected:
virtual ~Surface() {}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 7439fbc..753a699 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_android_core_graphics_stack",
default_applicable_licenses: ["frameworks_base_libs_hwui_license"],
}
@@ -93,6 +94,7 @@
host: {
include_dirs: [
"external/vulkan-headers/include",
+ "frameworks/av/media/ndk/include",
],
cflags: [
"-Wno-unused-variable",
@@ -142,7 +144,6 @@
"libsync",
"libui",
"aconfig_text_flags_c_lib",
- "server_configurable_flags",
],
static_libs: [
"libEGL_blobCache",
@@ -267,6 +268,7 @@
cppflags: ["-Wno-conversion-null"],
srcs: [
+ "apex/android_canvas.cpp",
"apex/android_matrix.cpp",
"apex/android_paint.cpp",
"apex/android_region.cpp",
@@ -279,7 +281,6 @@
android: {
srcs: [ // sources that depend on android only libraries
"apex/android_bitmap.cpp",
- "apex/android_canvas.cpp",
"apex/jni_runtime.cpp",
],
},
@@ -338,6 +339,8 @@
"jni/android_graphics_ColorSpace.cpp",
"jni/android_graphics_drawable_AnimatedVectorDrawable.cpp",
"jni/android_graphics_drawable_VectorDrawable.cpp",
+ "jni/android_graphics_HardwareRenderer.cpp",
+ "jni/android_graphics_HardwareBufferRenderer.cpp",
"jni/android_graphics_HardwareRendererObserver.cpp",
"jni/android_graphics_Matrix.cpp",
"jni/android_graphics_Picture.cpp",
@@ -422,8 +425,6 @@
android: {
srcs: [ // sources that depend on android only libraries
"jni/android_graphics_TextureLayer.cpp",
- "jni/android_graphics_HardwareRenderer.cpp",
- "jni/android_graphics_HardwareBufferRenderer.cpp",
"jni/GIFMovie.cpp",
"jni/GraphicsStatsService.cpp",
"jni/Movie.cpp",
@@ -448,6 +449,12 @@
"libstatssocket_lazy",
],
},
+ linux: {
+ srcs: ["platform/linux/utils/SharedLib.cpp"],
+ },
+ darwin: {
+ srcs: ["platform/darwin/utils/SharedLib.cpp"],
+ },
host: {
cflags: [
"-Wno-unused-const-variable",
@@ -543,6 +550,7 @@
"renderthread/CanvasContext.cpp",
"renderthread/DrawFrameTask.cpp",
"renderthread/Frame.cpp",
+ "renderthread/RenderEffectCapabilityQuery.cpp",
"renderthread/RenderProxy.cpp",
"renderthread/RenderTask.cpp",
"renderthread/TimeLord.cpp",
@@ -576,6 +584,7 @@
"HWUIProperties.sysprop",
"Interpolator.cpp",
"JankTracker.cpp",
+ "Layer.cpp",
"LayerUpdateQueue.cpp",
"LightingInfo.cpp",
"Matrix.cpp",
@@ -624,7 +633,6 @@
"renderthread/CacheManager.cpp",
"renderthread/EglManager.cpp",
"renderthread/ReliableSurface.cpp",
- "renderthread/RenderEffectCapabilityQuery.cpp",
"renderthread/VulkanManager.cpp",
"renderthread/VulkanSurface.cpp",
"renderthread/RenderThread.cpp",
@@ -635,7 +643,6 @@
"AutoBackendTextureRelease.cpp",
"DeferredLayerUpdater.cpp",
"HardwareBitmapUploader.cpp",
- "Layer.cpp",
"ProfileDataContainer.cpp",
"Readback.cpp",
"WebViewFunctorManager.cpp",
diff --git a/libs/hwui/jni/HardwareBufferHelpers.cpp b/libs/hwui/jni/HardwareBufferHelpers.cpp
index 7e3f771..d3b48d3 100644
--- a/libs/hwui/jni/HardwareBufferHelpers.cpp
+++ b/libs/hwui/jni/HardwareBufferHelpers.cpp
@@ -16,7 +16,9 @@
#include "HardwareBufferHelpers.h"
+#ifdef __ANDROID__
#include <dlfcn.h>
+#endif
#include <log/log.h>
#ifdef __ANDROID__
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index d9e2c8c..df9f830 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -25,13 +25,16 @@
#include <SkColorSpace.h>
#include <SkData.h>
#include <SkImage.h>
+#ifdef __ANDROID__
#include <SkImageAndroid.h>
+#else
+#include <SkImagePriv.h>
+#endif
#include <SkPicture.h>
#include <SkPixmap.h>
#include <SkSerialProcs.h>
#include <SkStream.h>
#include <SkTypeface.h>
-#include <dlfcn.h>
#include <gui/TraceUtils.h>
#include <include/encode/SkPngEncoder.h>
#include <inttypes.h>
@@ -39,8 +42,10 @@
#include <media/NdkImage.h>
#include <media/NdkImageReader.h>
#include <nativehelper/JNIPlatformHelp.h>
+#ifdef __ANDROID__
#include <pipeline/skia/ShaderCache.h>
#include <private/EGL/cache.h>
+#endif
#include <renderthread/CanvasContext.h>
#include <renderthread/RenderProxy.h>
#include <renderthread/RenderTask.h>
@@ -59,6 +64,7 @@
#include "JvmErrorReporter.h"
#include "android_graphics_HardwareRendererObserver.h"
#include "utils/ForceDark.h"
+#include "utils/SharedLib.h"
namespace android {
@@ -498,7 +504,11 @@
return sk_ref_sp(img);
}
bm.setImmutable();
+#ifdef __ANDROID__
return SkImages::PinnableRasterFromBitmap(bm);
+#else
+ return SkMakeImageFromRasterBitmap(bm, kNever_SkCopyPixelsMode);
+#endif
}
return sk_ref_sp(img);
}
@@ -713,6 +723,7 @@
static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode(JNIEnv* env,
jobject clazz, jlong renderNodePtr, jint jwidth, jint jheight) {
+#ifdef __ANDROID__
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
if (jwidth <= 0 || jheight <= 0) {
ALOGW("Invalid width %d or height %d", jwidth, jheight);
@@ -796,6 +807,9 @@
sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs);
return bitmap::createBitmap(env, bitmap.release(),
android::bitmap::kBitmapCreateFlag_Premultiplied);
+#else
+ return nullptr;
+#endif
}
static void android_view_ThreadedRenderer_disableVsync(JNIEnv*, jclass) {
@@ -909,6 +923,7 @@
static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz,
jstring diskCachePath, jstring skiaDiskCachePath) {
+#ifdef __ANDROID__
const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL);
android::egl_set_cache_filename(cacheArray);
env->ReleaseStringUTFChars(diskCachePath, cacheArray);
@@ -916,6 +931,7 @@
const char* skiaCacheArray = env->GetStringUTFChars(skiaDiskCachePath, NULL);
uirenderer::skiapipeline::ShaderCache::get().setFilename(skiaCacheArray);
env->ReleaseStringUTFChars(skiaDiskCachePath, skiaCacheArray);
+#endif
}
static jboolean android_view_ThreadedRenderer_isWebViewOverlaysEnabled(JNIEnv* env, jobject clazz) {
@@ -1092,8 +1108,12 @@
gCopyRequest.getDestinationBitmap =
GetMethodIDOrDie(env, copyRequest, "getDestinationBitmap", "(II)J");
- void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
- fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface");
+#ifdef __ANDROID__
+ void* handle_ = SharedLib::openSharedLib("libandroid");
+#else
+ void* handle_ = SharedLib::openSharedLib("libandroid_runtime");
+#endif
+ fromSurface = (ANW_fromSurface)SharedLib::getSymbol(handle_, "ANativeWindow_fromSurface");
LOG_ALWAYS_FATAL_IF(fromSurface == nullptr,
"Failed to find required symbol ANativeWindow_fromSurface!");
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 34932b1..dc669a5 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -24,7 +24,6 @@
#include <SkImageAndroid.h>
#include <SkImageInfo.h>
#include <SkMatrix.h>
-#include <SkMultiPictureDocument.h>
#include <SkOverdrawCanvas.h>
#include <SkOverdrawColorFilter.h>
#include <SkPicture.h>
@@ -38,6 +37,7 @@
#include <android-base/properties.h>
#include <gui/TraceUtils.h>
#include <include/android/SkSurfaceAndroid.h>
+#include <include/docs/SkMultiPictureDocument.h>
#include <include/encode/SkPngEncoder.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <unistd.h>
@@ -185,7 +185,7 @@
// we need to keep it until after mMultiPic.close()
// procs is passed as a pointer, but just as a method of having an optional default.
// procs doesn't need to outlive this Make call.
- mMultiPic = SkMakeMultiPictureDocument(mOpenMultiPicStream.get(), &procs,
+ mMultiPic = SkMultiPictureDocument::Make(mOpenMultiPicStream.get(), &procs,
[sharingCtx = mSerialContext.get()](const SkPicture* pic) {
SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx);
});
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index cf14b1f..823b209 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -18,7 +18,6 @@
#include <SkColorSpace.h>
#include <SkDocument.h>
-#include <SkMultiPictureDocument.h>
#include <SkSurface.h>
#include "Lighting.h"
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
index b62711f..21fe6ff 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
@@ -16,10 +16,10 @@
#include "VkFunctorDrawable.h"
-#include <GrBackendDrawableInfo.h>
#include <SkAndroidFrameworkUtils.h>
#include <SkImage.h>
#include <SkM44.h>
+#include <include/gpu/ganesh/vk/GrBackendDrawableInfo.h>
#include <gui/TraceUtils.h>
#include <private/hwui/DrawVkInfo.h>
#include <utils/Color.h>
diff --git a/libs/hwui/platform/darwin/utils/SharedLib.cpp b/libs/hwui/platform/darwin/utils/SharedLib.cpp
new file mode 100644
index 0000000..6e9f0b4
--- /dev/null
+++ b/libs/hwui/platform/darwin/utils/SharedLib.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils/SharedLib.h"
+
+#include <dlfcn.h>
+
+namespace android {
+namespace uirenderer {
+
+void* SharedLib::openSharedLib(std::string filename) {
+ return dlopen((filename + ".dylib").c_str(), RTLD_NOW | RTLD_NODELETE);
+}
+
+void* SharedLib::getSymbol(void* library, const char* symbol) {
+ return dlsym(library, symbol);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/platform/linux/utils/SharedLib.cpp b/libs/hwui/platform/linux/utils/SharedLib.cpp
new file mode 100644
index 0000000..a9acf37
--- /dev/null
+++ b/libs/hwui/platform/linux/utils/SharedLib.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils/SharedLib.h"
+
+#include <dlfcn.h>
+
+namespace android {
+namespace uirenderer {
+
+void* SharedLib::openSharedLib(std::string filename) {
+ return dlopen((filename + ".so").c_str(), RTLD_NOW | RTLD_NODELETE);
+}
+
+void* SharedLib::getSymbol(void* library, const char* symbol) {
+ return dlsym(library, symbol);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/utils/SharedLib.h b/libs/hwui/utils/SharedLib.h
new file mode 100644
index 0000000..f4dcf0f
--- /dev/null
+++ b/libs/hwui/utils/SharedLib.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SHAREDLIB_H
+#define SHAREDLIB_H
+
+#include <string>
+
+namespace android {
+namespace uirenderer {
+
+class SharedLib {
+public:
+ static void* openSharedLib(std::string filename);
+ static void* getSymbol(void* library, const char* symbol);
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif // SHAREDLIB_H
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 19e59a7..d6d4989 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -79,6 +79,13 @@
}
flag {
+ name: "subscriptions_listener_thread"
+ namespace: "location"
+ description: "Flag for running onSubscriptionsChangeListener on FgThread"
+ bug: "332451908"
+}
+
+flag {
name: "gnss_configuration_from_resource"
namespace: "location"
description: "Flag for GNSS configuration from resource"
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index ce7474c..3ba0d59 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5232,6 +5232,13 @@
setParameters(keys, values);
}
+ private void logAndRun(String message, Runnable r) {
+ final String TAG = "MediaCodec";
+ android.util.Log.d(TAG, "enter: " + message);
+ r.run();
+ android.util.Log.d(TAG, "exit : " + message);
+ }
+
/**
* Sets an asynchronous callback for actionable MediaCodec events.
*
@@ -5261,14 +5268,40 @@
// even if we were to extend this to be callable dynamically, it must
// be called when codec is flushed, so no messages are pending.
if (newHandler != mCallbackHandler) {
- mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
- mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ if (android.media.codec.Flags.setCallbackStall()) {
+ logAndRun(
+ "[new handler] removeMessages(SET_CALLBACK)",
+ () -> {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ });
+ logAndRun(
+ "[new handler] removeMessages(CALLBACK)",
+ () -> {
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ });
+ } else {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ }
mCallbackHandler = newHandler;
}
}
} else if (mCallbackHandler != null) {
- mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
- mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ if (android.media.codec.Flags.setCallbackStall()) {
+ logAndRun(
+ "[null handler] removeMessages(SET_CALLBACK)",
+ () -> {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ });
+ logAndRun(
+ "[null handler] removeMessages(CALLBACK)",
+ () -> {
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ });
+ } else {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ }
}
if (mCallbackHandler != null) {
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 5331046..6593533 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -205,6 +205,10 @@
* media container format specified by mimeType at the requested
* security level.
*
+ * Calling this method while the application is running on the physical Android device or a
+ * {@link android.companion.virtual.VirtualDevice} may lead to different results, based on
+ * the different DRM capabilities of the devices.
+ *
* @param uuid The UUID of the crypto scheme.
* @param mimeType The MIME type of the media container, e.g. "video/mp4"
* or "video/webm"
@@ -1400,6 +1404,10 @@
* Open a new session with the MediaDrm object. A session ID is returned.
* By default, sessions are opened at the native security level of the device.
*
+ * If the application is currently running on a {@link android.companion.virtual.VirtualDevice}
+ * the security level will be adjusted accordingly to the maximum supported level for the
+ * display.
+ *
* @throws NotProvisionedException if provisioning is needed
* @throws ResourceBusyException if required resources are in use
*/
@@ -1422,6 +1430,10 @@
* can be queried using {@link #getSecurityLevel}. A session
* ID is returned.
*
+ * If the application is currently running on a {@link android.companion.virtual.VirtualDevice}
+ * the security level will be adjusted accordingly to the maximum supported level for the
+ * display.
+ *
* @param level the new security level
* @throws NotProvisionedException if provisioning is needed
* @throws ResourceBusyException if required resources are in use
@@ -2180,6 +2192,11 @@
* Returns a value that may be passed as a parameter to {@link #openSession(int)}
* requesting that the session be opened at the maximum security level of
* the device.
+ *
+ * This security level is only valid for the application running on the physical Android
+ * device (e.g. {@link android.content.Context#DEVICE_ID_DEFAULT}). While running on a
+ * {@link android.companion.virtual.VirtualDevice} the maximum supported security level
+ * might be different.
*/
public static final int getMaxSecurityLevel() {
return SECURITY_LEVEL_MAX;
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 5aa006b..c664d3d 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -2713,7 +2713,7 @@
List<RoutingSessionInfo> sessionInfos = getRoutingSessions();
RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
- transfer(targetSession, route, Process.myUserHandle(), mContext.getPackageName());
+ transfer(targetSession, route, mClientUser, mContext.getPackageName());
}
@Override
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 45b4370..988b5cf 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -332,7 +332,8 @@
<pre class=prettyprint>
<service android:name="<strong>MySynthDeviceService</strong>"
- android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+ android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
+ android:exported="true">
<intent-filter>
<action android:name="android.media.midi.MidiDeviceService" />
</intent-filter>
@@ -474,7 +475,8 @@
<pre class=prettyprint>
<service android:name="<strong>MidiEchoDeviceService</strong>"
- android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+ android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
+ android:exported="true">
<intent-filter>
<action android:name="android.media.midi.MidiUmpDeviceService" />
</intent-filter>
@@ -509,6 +511,11 @@
import android.media.midi.MidiReceiver;
import android.media.midi.MidiUmpDeviceService;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
public class MidiEchoDeviceService extends MidiUmpDeviceService {
private static final String TAG = "MidiEchoDeviceService";
// Other apps will write to this port.
@@ -528,8 +535,8 @@
@Override
// Declare the receivers associated with your input ports.
- public List<MidiReceiver> onGetInputPortReceivers() {
- return new ArrayList<MidiReceiver>(Collections.singletonList(mInputReceiver));
+ public List<MidiReceiver> onGetInputPortReceivers() {
+ return new ArrayList<MidiReceiver>(Collections.singletonList(mInputReceiver));
}
/**
diff --git a/media/java/android/media/session/ParcelableListBinder.java b/media/java/android/media/session/ParcelableListBinder.java
index bbf1e08..d788284 100644
--- a/media/java/android/media/session/ParcelableListBinder.java
+++ b/media/java/android/media/session/ParcelableListBinder.java
@@ -45,6 +45,7 @@
private static final int END_OF_PARCEL = 0;
private static final int ITEM_CONTINUED = 1;
+ private final Class<T> mListElementsClass;
private final Consumer<List<T>> mConsumer;
private final Object mLock = new Object();
@@ -61,9 +62,11 @@
/**
* Creates an instance.
*
+ * @param listElementsClass the class of the list elements.
* @param consumer a consumer that consumes the list received
*/
- public ParcelableListBinder(@NonNull Consumer<List<T>> consumer) {
+ public ParcelableListBinder(Class<T> listElementsClass, @NonNull Consumer<List<T>> consumer) {
+ mListElementsClass = listElementsClass;
mConsumer = consumer;
}
@@ -83,7 +86,13 @@
mCount = data.readInt();
}
while (i < mCount && data.readInt() != END_OF_PARCEL) {
- mList.add(data.readParcelable(null));
+ Object object = data.readParcelable(null);
+ if (mListElementsClass.isAssignableFrom(object.getClass())) {
+ // Checking list items are of compaitible types to validate against malicious
+ // apps calling it directly via reflection with non compilable items.
+ // See b/317048338 for more details
+ mList.add((T) object);
+ }
i++;
}
if (i >= mCount) {
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 47637b8..4bacbf9 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -15,10 +15,7 @@
*/
package android.media.session;
-import static com.android.media.flags.Flags.FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE;
-
import android.annotation.DrawableRes;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.Nullable;
@@ -298,8 +295,9 @@
* foreground.
*
* @see Builder#setState
+ * @hide
*/
- @FlaggedApi(FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE)
+ // TODO: b/335561702 Unhide this symbol for the next API bump.
public static final int STATE_PLAYBACK_SUPPRESSED = 12;
/**
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 94fce79..8609c4d 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -82,6 +82,7 @@
"libhidlbase",
"libsonivox",
"server_configurable_flags",
+ "android.companion.virtual.virtualdevice_aidl-cpp",
"[email protected]",
"[email protected]",
"[email protected]",
@@ -100,6 +101,7 @@
static_libs: [
"libgrallocusage",
"libmedia_midiiowrapper",
+ "android.companion.virtualdevice.flags-aconfig-cc",
"android.media.playback.flags-aconfig-cc",
],
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 82561f9..4f9917b 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -2886,6 +2886,10 @@
jint offset,
jint size,
sp<hardware::HidlMemory> *memory) {
+ if ((offset + size) > context->capacity()) {
+ ALOGW("extractMemoryFromContext: offset + size provided exceed capacity");
+ return;
+ }
*memory = context->toHidlMemory();
if (*memory == nullptr) {
if (!context->mBlock) {
@@ -2893,23 +2897,26 @@
return;
}
ALOGD("extractMemoryFromContext: realloc & copying from C2Block to IMemory (cap=%zu)",
- context->capacity());
+ context->capacity());
if (!obtain(context, context->capacity(),
context->mCodecNames, true /* secure */)) {
ALOGW("extractMemoryFromContext: failed to obtain secure block");
return;
}
- C2WriteView view = context->mBlock->map().get();
- if (view.error() != C2_OK) {
- ALOGW("extractMemoryFromContext: failed to map C2Block (%d)", view.error());
- return;
- }
- uint8_t *memoryPtr = static_cast<uint8_t *>(context->mMemory->unsecurePointer());
- memcpy(memoryPtr + offset, view.base() + offset, size);
- context->mBlock.reset();
- context->mReadWriteMapping.reset();
*memory = context->toHidlMemory();
}
+ if (context->mBlock == nullptr || context->mReadWriteMapping == nullptr) {
+ ALOGW("extractMemoryFromContext: Cannot extract memory as C2Block is not created/mapped");
+ return;
+ }
+ if (context->mReadWriteMapping->error() != C2_OK) {
+ ALOGW("extractMemoryFromContext: failed to map C2Block (%d)",
+ context->mReadWriteMapping->error());
+ return;
+ }
+ // We are proceeding to extract memory from C2Block
+ uint8_t *memoryPtr = static_cast<uint8_t *>(context->mMemory->unsecurePointer());
+ memcpy(memoryPtr + offset, context->mReadWriteMapping->base() + offset, size);
}
static void extractBufferFromContext(
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 1c25080..48cd53d 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -27,6 +27,8 @@
#include "jni.h"
#include <nativehelper/JNIHelp.h>
+#include <android_companion_virtualdevice_flags.h>
+#include <android/companion/virtualnative/IVirtualDeviceManagerNative.h>
#include <android/hardware/drm/1.3/IDrmFactory.h>
#include <binder/Parcel.h>
#include <binder/PersistableBundle.h>
@@ -41,8 +43,10 @@
#include <map>
#include <string>
+using ::android::companion::virtualnative::IVirtualDeviceManagerNative;
using ::android::os::PersistableBundle;
namespace drm = ::android::hardware::drm;
+namespace virtualdevice_flags = android::companion::virtualdevice::flags;
namespace android {
@@ -1045,6 +1049,26 @@
return level;
}
+std::vector<int> getVirtualDeviceIds() {
+ if (!virtualdevice_flags::device_aware_drm()) {
+ ALOGW("Device-aware DRM flag disabled.");
+ return std::vector<int>();
+ }
+
+ sp<IBinder> binder =
+ defaultServiceManager()->checkService(String16("virtualdevice_native"));
+ if (binder != nullptr) {
+ auto vdm = interface_cast<IVirtualDeviceManagerNative>(binder);
+ std::vector<int> deviceIds;
+ const uid_t uid = IPCThreadState::self()->getCallingUid();
+ vdm->getDeviceIdsForUid(uid, &deviceIds);
+ return deviceIds;
+ } else {
+ ALOGW("Cannot get virtualdevice_native service");
+ return std::vector<int>();
+ }
+}
+
static jbyteArray android_media_MediaDrm_getSupportedCryptoSchemesNative(JNIEnv *env) {
sp<IDrm> drm = android::DrmUtils::MakeDrm();
if (drm == NULL) return env->NewByteArray(0);
@@ -1081,6 +1105,15 @@
}
DrmPlugin::SecurityLevel securityLevel = jintToSecurityLevel(jSecurityLevel);
+ if (getVirtualDeviceIds().size() > 0) {
+ // Cap security level at max SECURITY_LEVEL_SW_SECURE_CRYPTO because at
+ // higher security levels decode output cannot be captured and
+ // streamed to virtual devices rendered on virtual displays.
+ if (securityLevel > DrmPlugin::kSecurityLevelSwSecureCrypto) {
+ return false;
+ }
+ }
+
bool isSupported;
status_t err = JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType,
securityLevel, &isSupported);
@@ -1106,6 +1139,16 @@
return NULL;
}
+ if (getVirtualDeviceIds().size() > 0) {
+ // Cap security level at max SECURITY_LEVEL_SW_SECURE_CRYPTO because at
+ // higher security levels decode output cannot be captured and
+ // streamed to virtual devices rendered on virtual displays.
+ if (level == DrmPlugin::kSecurityLevelMax ||
+ level > DrmPlugin::kSecurityLevelSwSecureCrypto) {
+ level = DrmPlugin::kSecurityLevelSwSecureCrypto;
+ }
+ }
+
DrmStatus err = drm->openSession(level, sessionId);
if (throwExceptionAsNecessary(env, drm, err, "Failed to open session")) {
diff --git a/media/jni/android_media_MediaDrm.h b/media/jni/android_media_MediaDrm.h
index a64e3f2..36cba2d 100644
--- a/media/jni/android_media_MediaDrm.h
+++ b/media/jni/android_media_MediaDrm.h
@@ -19,6 +19,8 @@
#include "jni.h"
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
#include <media/stagefright/foundation/ABase.h>
#include <mediadrm/IDrm.h>
#include <mediadrm/IDrmClient.h>
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index c836df3..520b76e 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -361,6 +361,7 @@
* Tests if MR2.SessionCallback.onSessionCreated is called
* when a route is selected from MR2Manager.
*/
+ @Ignore // Ignored due to flakiness. No plans to fix though, in favor of removal (b/334970551).
@Test
public void testRouterOnSessionCreated() throws Exception {
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
@@ -512,6 +513,7 @@
/**
* Tests select, transfer, release of routes of a provider
*/
+ @Ignore // Ignored due to flakiness. No plans to fix though, in favor of removal (b/334970551).
@Test
public void testSelectAndTransferAndRelease() throws Exception {
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
@@ -908,6 +910,7 @@
* Tests if getSelectableRoutes and getDeselectableRoutes filter routes based on
* selected routes
*/
+ @Ignore // Ignored due to flakiness. No plans to fix though, in favor of removal (b/334970551).
@Test
public void testGetSelectableRoutes_notReturnsSelectedRoutes() throws Exception {
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index b57d548..7cd7e7ab 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -35,6 +35,7 @@
import android.nfc.INfcWlcStateListener;
import android.nfc.NfcAntennaInfo;
import android.nfc.WlcListenerDeviceInfo;
+import android.nfc.cardemulation.PollingFrame;
import android.os.Bundle;
/**
@@ -101,7 +102,7 @@
void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags);
- void notifyPollingLoop(in Bundle frame);
+ void notifyPollingLoop(in PollingFrame frame);
void notifyHceDeactivated();
int sendVendorNciMessage(int mt, int gid, int oid, in byte[] payload);
void registerVendorExtensionCallback(in INfcVendorNciCallback callbacks);
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index b44a71b..29867d9 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -2803,12 +2803,11 @@
@TestApi
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public void notifyPollingLoop(@NonNull PollingFrame pollingFrame) {
- Bundle frame = pollingFrame.toBundle();
try {
if (sService == null) {
attemptDeadServiceRecovery(null);
}
- sService.notifyPollingLoop(frame);
+ sService.notifyPollingLoop(pollingFrame);
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
// Try one more time
@@ -2817,7 +2816,7 @@
return;
}
try {
- sService.notifyPollingLoop(frame);
+ sService.notifyPollingLoop(pollingFrame);
} catch (RemoteException ee) {
Log.e(TAG, "Failed to recover NFC Service.");
}
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index 61037a2..f674b06a 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -325,15 +325,12 @@
}
break;
case MSG_POLLING_LOOP:
- ArrayList<Bundle> frames =
- msg.getData().getParcelableArrayList(KEY_POLLING_LOOP_FRAMES_BUNDLE,
- Bundle.class);
- ArrayList<PollingFrame> pollingFrames =
- new ArrayList<PollingFrame>(frames.size());
- for (Bundle frame : frames) {
- pollingFrames.add(new PollingFrame(frame));
+ if (android.nfc.Flags.nfcReadPollingLoop()) {
+ ArrayList<PollingFrame> pollingFrames =
+ msg.getData().getParcelableArrayList(
+ KEY_POLLING_LOOP_FRAMES_BUNDLE, PollingFrame.class);
+ processPollingFrames(pollingFrames);
}
- processPollingFrames(pollingFrames);
break;
default:
super.handleMessage(msg);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt b/nfc/java/android/nfc/cardemulation/PollingFrame.aidl
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt
copy to nfc/java/android/nfc/cardemulation/PollingFrame.aidl
index 7ca7030..8e09f8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,6 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.dagger
+package android.nfc.cardemulation;
-import javax.inject.Qualifier
-
-/** Wifi logs from [WifiRepositoryViaTrackerLib] in table format. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class WifiTrackerLibTableLog
+parcelable PollingFrame;
\ No newline at end of file
diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java
index c6861bf..b52faba 100644
--- a/nfc/java/android/nfc/cardemulation/PollingFrame.java
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java
@@ -101,47 +101,37 @@
/**
* KEY_POLLING_LOOP_TYPE is the Bundle key for the type of
* polling loop frame in the Bundle included in MSG_POLLING_LOOP.
- *
- * @hide
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
+ private static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
/**
* KEY_POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
* the polling loop frame in the Bundle included in MSG_POLLING_LOOP.
- *
- * @hide
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
+ private static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
/**
* KEY_POLLING_LOOP_GAIN is the Bundle key for the field strength of
* the polling loop frame in the Bundle included in MSG_POLLING_LOOP.
- *
- * @hide
- */
+ */
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
+ private static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
/**
* KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for the timestamp of
* the polling loop frame in the Bundle included in MSG_POLLING_LOOP.
- *
- * @hide
- */
+ */
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
+ private static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
/**
* KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for whether this polling frame triggered
* autoTransact in the Bundle included in MSG_POLLING_LOOP.
- *
- * @hide
- */
+ */
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT =
+ private static final String KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT =
"android.nfc.cardemulation.TRIGGERED_AUTOTRANSACT";
@@ -151,7 +141,7 @@
private final int mGain;
@DurationMillisLong
private final long mTimestamp;
- private final boolean mTriggeredAutoTransact;
+ private boolean mTriggeredAutoTransact;
public static final @NonNull Parcelable.Creator<PollingFrame> CREATOR =
new Parcelable.Creator<>() {
@@ -166,7 +156,7 @@
}
};
- PollingFrame(Bundle frame) {
+ private PollingFrame(Bundle frame) {
mType = frame.getInt(KEY_POLLING_LOOP_TYPE);
byte[] data = frame.getByteArray(KEY_POLLING_LOOP_DATA);
mData = (data == null) ? new byte[0] : data;
@@ -239,6 +229,13 @@
}
/**
+ * @hide
+ */
+ public void setTriggeredAutoTransact(boolean triggeredAutoTransact) {
+ mTriggeredAutoTransact = triggeredAutoTransact;
+ }
+
+ /**
* Returns whether this frame triggered the device to automatically disable observe mode and
* allow one transaction.
*/
@@ -257,11 +254,9 @@
}
/**
- *
- * @hide
* @return a Bundle representing this frame
*/
- public Bundle toBundle() {
+ private Bundle toBundle() {
Bundle frame = new Bundle();
frame.putInt(KEY_POLLING_LOOP_TYPE, getType());
if (getVendorSpecificGain() != -1) {
diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml
index 97dfba1..8fe771c 100644
--- a/packages/CompanionDeviceManager/AndroidManifest.xml
+++ b/packages/CompanionDeviceManager/AndroidManifest.xml
@@ -41,7 +41,7 @@
android:supportsRtl="true">
<activity
- android:name=".CompanionDeviceActivity"
+ android:name=".CompanionAssociationActivity"
android:exported="true"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
diff --git a/packages/CompanionDeviceManager/res/values-hi/strings.xml b/packages/CompanionDeviceManager/res/values-hi/strings.xml
index 2363886..92d94f3 100644
--- a/packages/CompanionDeviceManager/res/values-hi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hi/strings.xml
@@ -59,7 +59,7 @@
<string name="permission_microphone" msgid="2152206421428732949">"माइक्रोफ़ोन"</string>
<string name="permission_call_logs" msgid="5546761417694586041">"कॉल लॉग"</string>
<string name="permission_nearby_devices" msgid="7530973297737123481">"आस-पास मौजूद डिवाइस"</string>
- <string name="permission_media_routing_control" msgid="5498639511586715253">"मीडिया आउटपुट बदलें"</string>
+ <string name="permission_media_routing_control" msgid="5498639511586715253">"मीडिया आउटपुट में बदलाव करे"</string>
<string name="permission_storage" msgid="6831099350839392343">"फ़ोटो और मीडिया"</string>
<string name="permission_notifications" msgid="4099418516590632909">"सूचनाएं"</string>
<string name="permission_app_streaming" msgid="6009695219091526422">"ऐप्लिकेशन"</string>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
similarity index 88%
rename from packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
rename to packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index 1231b63..bf81d3f 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -65,7 +65,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.text.Spanned;
-import android.util.Log;
+import android.util.Slog;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -91,9 +91,8 @@
* nearby devices to be associated with.
*/
@SuppressLint("LongLogTag")
-public class CompanionDeviceActivity extends FragmentActivity implements
+public class CompanionAssociationActivity extends FragmentActivity implements
CompanionVendorHelperDialogFragment.CompanionVendorHelperDialogListener {
- private static final boolean DEBUG = false;
private static final String TAG = "CDM_CompanionDeviceActivity";
// Keep the following constants in sync with
@@ -183,11 +182,11 @@
@Override
public void onCreate(Bundle savedInstanceState) {
- if (DEBUG) Log.d(TAG, "onCreate()");
- boolean forceCancelDialog = getIntent().getBooleanExtra("cancel_confirmation", false);
+ boolean forceCancelDialog = getIntent().getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION,
+ false);
// Must handle the force cancel request in onNewIntent.
if (forceCancelDialog) {
- Log.i(TAG, "The confirmation does not exist, skipping the cancel request");
+ Slog.i(TAG, "The confirmation does not exist, skipping the cancel request");
finish();
}
@@ -198,13 +197,13 @@
@Override
protected void onStart() {
super.onStart();
- if (DEBUG) Log.d(TAG, "onStart()");
final Intent intent = getIntent();
- mRequest = intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST);
+ mRequest = intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST, AssociationRequest.class);
mAppCallback = IAssociationRequestCallback.Stub.asInterface(
intent.getExtras().getBinder(EXTRA_APPLICATION_CALLBACK));
- mCdmServiceReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER);
+ mCdmServiceReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER,
+ ResultReceiver.class);
requireNonNull(mRequest);
requireNonNull(mAppCallback);
@@ -221,29 +220,22 @@
initUI();
}
- @SuppressWarnings("MissingSuperCall") // TODO: Fix me
@Override
- protected void onNewIntent(Intent intent) {
+ protected void onNewIntent(@NonNull Intent intent) {
+ super.onNewIntent(intent);
+
// Force cancels the CDM dialog if this activity receives another intent with
// EXTRA_FORCE_CANCEL_CONFIRMATION.
boolean forCancelDialog = intent.getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION, false);
-
if (forCancelDialog) {
- Log.i(TAG, "Cancelling the user confirmation");
-
- cancel(/* discoveryTimeOut */ false,
- /* userRejected */ false, /* internalError */ false);
+ Slog.i(TAG, "Cancelling the user confirmation");
+ cancel(/* discoveryTimeOut */ false, /* userRejected */ false,
+ /* internalError */ false);
return;
}
// Handle another incoming request (while we are not done with the original - mRequest -
- // yet).
- final AssociationRequest request = requireNonNull(
- intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST));
-
- if (DEBUG) Log.d(TAG, "onNewIntent(), request=" + request);
-
- // We can only "process" one request at a time.
+ // yet). We can only "process" one request at a time.
final IAssociationRequestCallback appCallback = IAssociationRequestCallback.Stub
.asInterface(intent.getExtras().getBinder(EXTRA_APPLICATION_CALLBACK));
try {
@@ -255,7 +247,6 @@
@Override
protected void onStop() {
super.onStop();
- if (DEBUG) Log.d(TAG, "onStop(), finishing=" + isFinishing());
// TODO: handle config changes without cancelling.
if (!isDone()) {
@@ -264,26 +255,8 @@
}
}
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (DEBUG) Log.d(TAG, "onDestroy()");
- }
-
- @Override
- public void onBackPressed() {
- if (DEBUG) Log.d(TAG, "onBackPressed()");
- super.onBackPressed();
- }
-
- @Override
- public void finish() {
- if (DEBUG) Log.d(TAG, "finish()", new Exception("Stack Trace Dump"));
- super.finish();
- }
-
private void initUI() {
- if (DEBUG) Log.d(TAG, "initUI(), request=" + mRequest);
+ Slog.d(TAG, "initUI(), request=" + mRequest);
final String packageName = mRequest.getPackageName();
final int userId = mRequest.getUserId();
@@ -292,7 +265,7 @@
try {
appLabel = getApplicationLabel(this, packageName, userId);
} catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Package u" + userId + "/" + packageName + " not found.");
+ Slog.w(TAG, "Package u" + userId + "/" + packageName + " not found.");
CompanionDeviceDiscoveryService.stop(this);
setResultAndFinish(null, RESULT_INTERNAL_ERROR);
@@ -341,9 +314,9 @@
if (mRequest.isSelfManaged()) {
initUiForSelfManagedAssociation();
} else if (mRequest.isSingleDevice()) {
- initUiForSingleDevice(appLabel);
+ initUiForSingleDevice();
} else {
- initUiForMultipleDevices(appLabel);
+ initUiForMultipleDevices();
}
}
@@ -364,12 +337,12 @@
private void onAssociationApproved(@Nullable MacAddress macAddress) {
if (isDone()) {
- if (DEBUG) Log.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
+ Slog.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
return;
}
mApproved = true;
- if (DEBUG) Log.i(TAG, "onAssociationApproved() macAddress=" + macAddress);
+ Slog.i(TAG, "onAssociationApproved() macAddress=" + macAddress);
if (!mRequest.isSelfManaged()) {
requireNonNull(macAddress);
@@ -390,17 +363,8 @@
}
private void cancel(boolean discoveryTimeout, boolean userRejected, boolean internalError) {
- if (DEBUG) {
- Log.i(TAG, "cancel(), discoveryTimeout="
- + discoveryTimeout
- + ", userRejected="
- + userRejected
- + ", internalError="
- + internalError, new Exception("Stack Trace Dump"));
- }
-
if (isDone()) {
- if (DEBUG) Log.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
+ Slog.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
return;
}
mCancelled = true;
@@ -428,6 +392,7 @@
// First send callback to the app directly...
try {
+ Slog.i(TAG, "Sending onFailure to app due to reason=" + cancelReason);
mAppCallback.onFailure(cancelReason);
} catch (RemoteException ignore) {
}
@@ -437,7 +402,7 @@
}
private void setResultAndFinish(@Nullable AssociationInfo association, int resultCode) {
- Log.i(TAG, "setResultAndFinish(), association="
+ Slog.i(TAG, "setResultAndFinish(), association="
+ (association == null ? "null" : association)
+ "resultCode=" + resultCode);
@@ -454,7 +419,7 @@
}
private void initUiForSelfManagedAssociation() {
- if (DEBUG) Log.i(TAG, "initUiFor_SelfManaged_Association()");
+ Slog.d(TAG, "initUiForSelfManagedAssociation()");
final CharSequence deviceName = mRequest.getDisplayName();
final String deviceProfile = mRequest.getDeviceProfile();
@@ -477,7 +442,7 @@
mVendorHeaderImage.setColorFilter(getResources().getColor(color, /* Theme= */null));
}
} catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
+ Slog.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
cancel(/* discoveryTimeout */ false,
/* userRejected */ false, /* internalError */ true);
return;
@@ -506,8 +471,8 @@
mBorderBottom.setVisibility(View.GONE);
}
- private void initUiForSingleDevice(CharSequence appLabel) {
- if (DEBUG) Log.i(TAG, "initUiFor_SingleDevice()");
+ private void initUiForSingleDevice() {
+ Slog.d(TAG, "initUiForSingleDevice()");
final String deviceProfile = mRequest.getDeviceProfile();
@@ -515,9 +480,16 @@
throw new RuntimeException("Unsupported profile " + deviceProfile);
}
- CompanionDeviceDiscoveryService.getScanResult().observe(this,
- deviceFilterPairs -> updateSingleDeviceUi(
- deviceFilterPairs, deviceProfile, appLabel));
+ final Drawable profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile));
+ mProfileIcon.setImageDrawable(profileIcon);
+
+ CompanionDeviceDiscoveryService.getScanResult().observe(this, deviceFilterPairs -> {
+ if (deviceFilterPairs.isEmpty()) {
+ return;
+ }
+ mSelectedDevice = requireNonNull(deviceFilterPairs.get(0));
+ updateSingleDeviceUi();
+ });
mSingleDeviceSpinner.setVisibility(View.VISIBLE);
// Hide permission list and confirmation dialog first before the
@@ -527,33 +499,8 @@
mAssociationConfirmationDialog.setVisibility(View.GONE);
}
- private void updateSingleDeviceUi(List<DeviceFilterPair<?>> deviceFilterPairs,
- String deviceProfile, CharSequence appLabel) {
- // Ignore "empty" scan reports.
- if (deviceFilterPairs.isEmpty()) return;
-
- mSelectedDevice = requireNonNull(deviceFilterPairs.get(0));
-
- final Drawable profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile));
-
- // No need to show permission consent dialog if it is a isSkipPrompt(true)
- // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
- if (mRequest.isSkipPrompt()) {
- Log.d(TAG, "Skipping the permission consent dialog.");
- mSingleDeviceSpinner.setVisibility(View.GONE);
- onUserSelectedDevice(mSelectedDevice);
- return;
- }
-
- updatePermissionUi();
-
- mProfileIcon.setImageDrawable(profileIcon);
- mAssociationConfirmationDialog.setVisibility(View.VISIBLE);
- mSingleDeviceSpinner.setVisibility(View.GONE);
- }
-
- private void initUiForMultipleDevices(CharSequence appLabel) {
- if (DEBUG) Log.i(TAG, "initUiFor_MultipleDevices()");
+ private void initUiForMultipleDevices() {
+ Slog.d(TAG, "initUiForMultipleDevices()");
final Drawable profileIcon;
final Spanned title;
@@ -566,7 +513,7 @@
profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile));
if (deviceProfile == null) {
- title = getHtmlFromResources(this, R.string.chooser_title_non_profile, appLabel);
+ title = getHtmlFromResources(this, R.string.chooser_title_non_profile, mAppLabel);
mButtonNotAllowMultipleDevices.setText(R.string.consent_no);
} else {
title = getHtmlFromResources(this,
@@ -606,7 +553,7 @@
final DeviceFilterPair<?> selectedDevice = mDeviceAdapter.getItem(position);
// To prevent double tap on the selected device.
if (mSelectedDevice != null) {
- if (DEBUG) Log.w(TAG, "Already selected.");
+ Slog.w(TAG, "Already selected.");
return;
}
// Notify the adapter to highlight the selected item.
@@ -614,17 +561,9 @@
mSelectedDevice = requireNonNull(selectedDevice);
- Log.d(TAG, "onDeviceClicked(): " + mSelectedDevice.toShortString());
+ Slog.d(TAG, "onDeviceClicked(): " + mSelectedDevice.toShortString());
- // No need to show permission consent dialog if it is a isSkipPrompt(true)
- // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
- if (mRequest.isSkipPrompt()) {
- Log.d(TAG, "Skipping the permission consent dialog.");
- onUserSelectedDevice(mSelectedDevice);
- return;
- }
-
- updatePermissionUi();
+ updateSingleDeviceUi();
mSummary.setVisibility(View.VISIBLE);
mButtonAllow.setVisibility(View.VISIBLE);
@@ -633,7 +572,18 @@
mNotAllowMultipleDevicesLayout.setVisibility(View.GONE);
}
- private void updatePermissionUi() {
+ private void updateSingleDeviceUi() {
+ // No need to show permission consent dialog if it is a isSkipPrompt(true)
+ // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
+ if (mRequest.isSkipPrompt()) {
+ Slog.d(TAG, "Skipping the permission consent dialog.");
+ onUserSelectedDevice(mSelectedDevice);
+ return;
+ }
+
+ mSingleDeviceSpinner.setVisibility(View.GONE);
+ mAssociationConfirmationDialog.setVisibility(View.VISIBLE);
+
final String deviceProfile = mRequest.getDeviceProfile();
final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile);
final String remoteDeviceName = mSelectedDevice.getDisplayName();
@@ -658,7 +608,7 @@
}
private void onPositiveButtonClick(View v) {
- if (DEBUG) Log.d(TAG, "on_Positive_ButtonClick()");
+ Slog.d(TAG, "onPositiveButtonClick()");
// Disable the button, to prevent more clicks.
v.setEnabled(false);
@@ -671,7 +621,7 @@
}
private void onNegativeButtonClick(View v) {
- if (DEBUG) Log.d(TAG, "on_Negative_ButtonClick()");
+ Slog.d(TAG, "onNegativeButtonClick()");
// Disable the button, to prevent more clicks.
v.setEnabled(false);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index 65bbb6fc..a5bb34f 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -55,7 +55,7 @@
import android.os.Parcelable;
import android.os.SystemProperties;
import android.text.TextUtils;
-import android.util.Log;
+import android.util.Slog;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
@@ -71,7 +71,6 @@
*/
@SuppressLint("LongLogTag")
public class CompanionDeviceDiscoveryService extends Service {
- private static final boolean DEBUG = false;
private static final String TAG = "CDM_CompanionDeviceDiscoveryService";
private static final String SYS_PROP_DEBUG_TIMEOUT = "debug.cdm.discovery_timeout";
@@ -147,7 +146,6 @@
@Override
public void onCreate() {
super.onCreate();
- if (DEBUG) Log.d(TAG, "onCreate()");
mBtManager = getSystemService(BluetoothManager.class);
mBtAdapter = mBtManager.getAdapter();
@@ -158,7 +156,6 @@
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
final String action = intent.getAction();
- if (DEBUG) Log.d(TAG, "onStartCommand() action=" + action);
switch (action) {
case ACTION_START_DISCOVERY:
@@ -174,15 +171,9 @@
return START_NOT_STICKY;
}
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (DEBUG) Log.d(TAG, "onDestroy()");
- }
-
@MainThread
private void startDiscovery(@NonNull AssociationRequest request) {
- if (DEBUG) Log.i(TAG, "startDiscovery() request=" + request);
+ Slog.d(TAG, "startDiscovery() request=" + request);
requireNonNull(request);
if (mDiscoveryStarted) throw new RuntimeException("Discovery in progress.");
@@ -197,7 +188,12 @@
filter(allFilters, BluetoothLeDeviceFilter.class);
final List<WifiDeviceFilter> wifiFilters = filter(allFilters, WifiDeviceFilter.class);
- checkBoundDevicesIfNeeded(request, btFilters);
+ // No need to startDiscovery if the device is already bound or connected for
+ // singleDevice dialog.
+ if (checkBoundDevicesIfNeeded(request, btFilters)) {
+ stopSelf();
+ return;
+ }
// If no filters are specified: look for everything.
final boolean forceStartScanningAll = isEmpty(allFilters);
@@ -213,7 +209,7 @@
@MainThread
private void stopDiscoveryAndFinish(boolean timeout) {
- if (DEBUG) Log.i(TAG, "stopDiscovery()");
+ Slog.d(TAG, "stopDiscoveryAndFinish(" + timeout + ")");
if (!mDiscoveryStarted) {
stopSelf();
@@ -257,42 +253,45 @@
stopSelf();
}
- private void checkBoundDevicesIfNeeded(@NonNull AssociationRequest request,
+ private boolean checkBoundDevicesIfNeeded(@NonNull AssociationRequest request,
@NonNull List<BluetoothDeviceFilter> btFilters) {
// If filtering to get single device by mac address, also search in the set of already
// bonded devices to allow linking those directly
- if (btFilters.isEmpty() || !request.isSingleDevice()) return;
+ if (btFilters.isEmpty() || !request.isSingleDevice()) return false;
final BluetoothDeviceFilter singleMacAddressFilter =
find(btFilters, filter -> !TextUtils.isEmpty(filter.getAddress()));
- if (singleMacAddressFilter == null) return;
+ if (singleMacAddressFilter == null) return false;
- findAndReportMatches(mBtAdapter.getBondedDevices(), btFilters);
- findAndReportMatches(mBtManager.getConnectedDevices(BluetoothProfile.GATT), btFilters);
- findAndReportMatches(
- mBtManager.getConnectedDevices(BluetoothProfile.GATT_SERVER), btFilters);
+ return findAndReportMatches(mBtAdapter.getBondedDevices(), btFilters)
+ || findAndReportMatches(mBtManager.getConnectedDevices(
+ BluetoothProfile.GATT), btFilters)
+ || findAndReportMatches(mBtManager.getConnectedDevices(
+ BluetoothProfile.GATT_SERVER), btFilters);
}
- private void findAndReportMatches(@Nullable Collection<BluetoothDevice> devices,
+ private boolean findAndReportMatches(@Nullable Collection<BluetoothDevice> devices,
@NonNull List<BluetoothDeviceFilter> filters) {
- if (devices == null) return;
+ if (devices == null) return false;
for (BluetoothDevice device : devices) {
final DeviceFilterPair<BluetoothDevice> match = findMatch(device, filters);
if (match != null) {
onDeviceFound(match);
+ return true;
}
}
+
+ return false;
}
private BluetoothBroadcastReceiver startBtScanningIfNeeded(
List<BluetoothDeviceFilter> filters, boolean force) {
if (isEmpty(filters) && !force) return null;
- if (DEBUG) Log.d(TAG, "registerReceiver(BluetoothDevice.ACTION_FOUND)");
+ Slog.d(TAG, "registerReceiver(BluetoothDevice.ACTION_FOUND)");
final BluetoothBroadcastReceiver receiver = new BluetoothBroadcastReceiver(filters);
-
final IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(receiver, intentFilter);
@@ -304,7 +303,7 @@
private WifiBroadcastReceiver startWifiScanningIfNeeded(
List<WifiDeviceFilter> filters, boolean force) {
if (isEmpty(filters) && !force) return null;
- if (DEBUG) Log.d(TAG, "registerReceiver(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)");
+ Slog.d(TAG, "registerReceiver(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)");
final WifiBroadcastReceiver receiver = new WifiBroadcastReceiver(filters);
@@ -320,10 +319,10 @@
private ScanCallback startBleScanningIfNeeded(
List<BluetoothLeDeviceFilter> filters, boolean force) {
if (isEmpty(filters) && !force) return null;
- if (DEBUG) Log.d(TAG, "BLEScanner.startScan");
+ Slog.d(TAG, "BLEScanner.startScan");
if (mBleScanner == null) {
- Log.w(TAG, "BLE Scanner is not available.");
+ Slog.w(TAG, "BLE Scanner is not available.");
return null;
}
@@ -341,18 +340,13 @@
private void onDeviceFound(@NonNull DeviceFilterPair<?> device) {
runOnMainThread(() -> {
- if (DEBUG) Log.v(TAG, "onDeviceFound() " + device);
if (mDiscoveryStopped) return;
if (mDevicesFound.contains(device)) {
// TODO: update the device instead of ignoring (new found device may contain
// additional/updated info, eg. name of the device).
- if (DEBUG) {
- Log.d(TAG, "onDeviceFound() " + device.toShortString()
- + " - Already seen: ignore.");
- }
return;
}
- Log.i(TAG, "onDeviceFound() " + device.toShortString() + " - New device.");
+ Slog.i(TAG, "onDeviceFound() " + device.toShortString() + " - New device.");
// First: make change.
mDevicesFound.add(device);
@@ -367,7 +361,7 @@
private void onDeviceLost(@NonNull DeviceFilterPair<?> device) {
runOnMainThread(() -> {
- Log.i(TAG, "onDeviceLost(), device=" + device.toShortString());
+ Slog.i(TAG, "onDeviceLost(), device=" + device.toShortString());
// First: make change.
mDevicesFound.remove(device);
@@ -386,13 +380,10 @@
timeout = max(timeout, TIMEOUT_MIN); // should be >= 1 sec (TIMEOUT_MIN)
}
- if (DEBUG) Log.d(TAG, "scheduleTimeout(), timeout=" + timeout);
-
Handler.getMain().postDelayed(mTimeoutRunnable, timeout);
}
private void timeout() {
- if (DEBUG) Log.i(TAG, "timeout()");
stopDiscoveryAndFinish(/* timeout */ true);
}
@@ -410,10 +401,6 @@
@Override
public void onScanResult(int callbackType, ScanResult result) {
- if (DEBUG) {
- Log.v(TAG, "BLE.onScanResult() callback=" + callbackType + ", result=" + result);
- }
-
final DeviceFilterPair<ScanResult> match = findMatch(result, mFilters);
if (match == null) return;
@@ -438,8 +425,6 @@
final String action = intent.getAction();
final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- if (DEBUG) Log.v(TAG, action + ", device=" + device);
-
if (action == null) return;
final DeviceFilterPair<BluetoothDevice> match = findMatch(device, mFilters);
@@ -468,10 +453,6 @@
}
final List<android.net.wifi.ScanResult> scanResults = mWifiManager.getScanResults();
- if (DEBUG) {
- Log.v(TAG, "WifiManager.SCAN_RESULTS_AVAILABLE_ACTION, results:\n "
- + TextUtils.join("\n ", scanResults));
- }
for (int i = 0; i < scanResults.size(); i++) {
final android.net.wifi.ScanResult scanResult = scanResults.get(i);
@@ -496,9 +477,7 @@
DeviceFilterPair<T> result = matchingFilter != null
? new DeviceFilterPair<>(dev, matchingFilter) : null;
- if (DEBUG) {
- Log.v(TAG, "findMatch(dev=" + dev + ", filters=" + filters + ") -> " + result);
- }
+
return result;
}
}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
deleted file mode 100644
index fa4d6af..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.utils;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.io.File;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
- *
- * @hide
- */
-public class ArrayUtils {
- private ArrayUtils() { /* cannot be instantiated */ }
- public static final File[] EMPTY_FILE = new File[0];
-
-
- /**
- * Return first index of {@code value} in {@code array}, or {@code -1} if
- * not found.
- */
- public static <T> int indexOf(@Nullable T[] array, T value) {
- if (array == null) return -1;
- for (int i = 0; i < array.length; i++) {
- if (Objects.equals(array[i], value)) return i;
- }
- return -1;
- }
-
- /** @hide */
- public static @NonNull File[] defeatNullable(@Nullable File[] val) {
- return (val != null) ? val : EMPTY_FILE;
- }
-
- /**
- * Checks if given array is null or has zero elements.
- */
- public static boolean isEmpty(@Nullable int[] array) {
- return array == null || array.length == 0;
- }
-
- /**
- * True if the byte array is null or has length 0.
- */
- public static boolean isEmpty(@Nullable byte[] array) {
- return array == null || array.length == 0;
- }
-
- /**
- * Converts from List of bytes to byte array
- * @param list
- * @return byte[]
- */
- public static byte[] toPrimitive(List<byte[]> list) {
- if (list.size() == 0) {
- return new byte[0];
- }
- int byteLen = list.get(0).length;
- byte[] array = new byte[list.size() * byteLen];
- for (int i = 0; i < list.size(); i++) {
- for (int j = 0; j < list.get(i).length; j++) {
- array[i * byteLen + j] = list.get(i)[j];
- }
- }
- return array;
- }
-
- /**
- * Adds value to given array if not already present, providing set-like
- * behavior.
- */
- public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
- return appendInt(cur, val, false);
- }
-
- /**
- * Adds value to given array.
- */
- public static @NonNull int[] appendInt(@Nullable int[] cur, int val,
- boolean allowDuplicates) {
- if (cur == null) {
- return new int[] { val };
- }
- final int n = cur.length;
- if (!allowDuplicates) {
- for (int i = 0; i < n; i++) {
- if (cur[i] == val) {
- return cur;
- }
- }
- }
- int[] ret = new int[n + 1];
- System.arraycopy(cur, 0, ret, 0, n);
- ret[n] = val;
- return ret;
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
deleted file mode 100644
index afcf689..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.utils;
-
-import android.annotation.NonNull;
-import android.os.Handler;
-import android.os.HandlerThread;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.concurrent.Executor;
-
-/**
- * Thread for asynchronous event processing. This thread is configured as
- * {@link android.os.Process#THREAD_PRIORITY_BACKGROUND}, which means fewer CPU
- * resources will be dedicated to it, and it will "have less chance of impacting
- * the responsiveness of the user interface."
- * <p>
- * This thread is best suited for tasks that the user is not actively waiting
- * for, or for tasks that the user expects to be executed eventually.
- *
- * @see com.android.internal.os.BackgroundThread
- *
- * TODO: b/326916057 depend on modules-utils-backgroundthread instead
- * @hide
- */
-public final class BackgroundThread extends HandlerThread {
- private static final Object sLock = new Object();
-
- @GuardedBy("sLock")
- private static BackgroundThread sInstance;
- @GuardedBy("sLock")
- private static Handler sHandler;
- @GuardedBy("sLock")
- private static HandlerExecutor sHandlerExecutor;
-
- private BackgroundThread() {
- super(BackgroundThread.class.getName(), android.os.Process.THREAD_PRIORITY_BACKGROUND);
- }
-
- @GuardedBy("sLock")
- private static void ensureThreadLocked() {
- if (sInstance == null) {
- sInstance = new BackgroundThread();
- sInstance.start();
- sHandler = new Handler(sInstance.getLooper());
- sHandlerExecutor = new HandlerExecutor(sHandler);
- }
- }
-
- /**
- * Get the singleton instance of this class.
- *
- * @return the singleton instance of this class
- */
- @NonNull
- public static BackgroundThread get() {
- synchronized (sLock) {
- ensureThreadLocked();
- return sInstance;
- }
- }
-
- /**
- * Get the singleton {@link Handler} for this class.
- *
- * @return the singleton {@link Handler} for this class.
- */
- @NonNull
- public static Handler getHandler() {
- synchronized (sLock) {
- ensureThreadLocked();
- return sHandler;
- }
- }
-
- /**
- * Get the singleton {@link Executor} for this class.
- *
- * @return the singleton {@link Executor} for this class.
- */
- @NonNull
- public static Executor getExecutor() {
- synchronized (sLock) {
- ensureThreadLocked();
- return sHandlerExecutor;
- }
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
deleted file mode 100644
index e4923bf..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.utils;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Bits and pieces copied from hidden API of android.os.FileUtils.
- *
- * @hide
- */
-public class FileUtils {
- /**
- * Read a text file into a String, optionally limiting the length.
- *
- * @param file to read (will not seek, so things like /proc files are OK)
- * @param max length (positive for head, negative of tail, 0 for no limit)
- * @param ellipsis to add of the file was truncated (can be null)
- * @return the contents of the file, possibly truncated
- * @throws IOException if something goes wrong reading the file
- * @hide
- */
- public static @Nullable String readTextFile(@Nullable File file, @Nullable int max,
- @Nullable String ellipsis) throws IOException {
- InputStream input = new FileInputStream(file);
- // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
- // input stream, bytes read not equal to buffer size is not necessarily the correct
- // indication for EOF; but it is true for BufferedInputStream due to its implementation.
- BufferedInputStream bis = new BufferedInputStream(input);
- try {
- long size = file.length();
- if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes
- if (size > 0 && (max == 0 || size < max)) max = (int) size;
- byte[] data = new byte[max + 1];
- int length = bis.read(data);
- if (length <= 0) return "";
- if (length <= max) return new String(data, 0, length);
- if (ellipsis == null) return new String(data, 0, max);
- return new String(data, 0, max) + ellipsis;
- } else if (max < 0) { // "tail" mode: keep the last N
- int len;
- boolean rolled = false;
- byte[] last = null;
- byte[] data = null;
- do {
- if (last != null) rolled = true;
- byte[] tmp = last;
- last = data;
- data = tmp;
- if (data == null) data = new byte[-max];
- len = bis.read(data);
- } while (len == data.length);
-
- if (last == null && len <= 0) return "";
- if (last == null) return new String(data, 0, len);
- if (len > 0) {
- rolled = true;
- System.arraycopy(last, len, last, 0, last.length - len);
- System.arraycopy(data, 0, last, last.length - len, len);
- }
- if (ellipsis == null || !rolled) return new String(last);
- return ellipsis + new String(last);
- } else { // "cat" mode: size unknown, read it all in streaming fashion
- ByteArrayOutputStream contents = new ByteArrayOutputStream();
- int len;
- byte[] data = new byte[1024];
- do {
- len = bis.read(data);
- if (len > 0) contents.write(data, 0, len);
- } while (len == data.length);
- return contents.toString();
- }
- } finally {
- bis.close();
- input.close();
- }
- }
-
- /**
- * Perform an fsync on the given FileOutputStream. The stream at this
- * point must be flushed but not yet closed.
- *
- * @hide
- */
- public static boolean sync(FileOutputStream stream) {
- try {
- if (stream != null) {
- stream.getFD().sync();
- }
- return true;
- } catch (IOException e) {
- }
- return false;
- }
-
- /**
- * List the files in the directory or return empty file.
- *
- * @hide
- */
- public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
- return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
- : ArrayUtils.EMPTY_FILE;
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
deleted file mode 100644
index fdb15e2..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.utils;
-
-import android.annotation.NonNull;
-import android.os.Handler;
-
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.concurrent.RejectedExecutionException;
-
-/**
- * An adapter {@link Executor} that posts all executed tasks onto the given
- * {@link Handler}.
- *
- * TODO: b/326916057 depend on modules-utils-backgroundthread instead
- * @hide
- */
-public class HandlerExecutor implements Executor {
- private final Handler mHandler;
-
- public HandlerExecutor(@NonNull Handler handler) {
- mHandler = Objects.requireNonNull(handler);
- }
-
- @Override
- public void execute(Runnable command) {
- if (!mHandler.post(command)) {
- throw new RejectedExecutionException(mHandler + " is shutting down");
- }
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
deleted file mode 100644
index 5cdc253..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.utils;
-
-import libcore.util.EmptyArray;
-
-import java.util.NoSuchElementException;
-
-/**
- * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java
- *
- * @hide
- */
-public class LongArrayQueue {
-
- private long[] mValues;
- private int mSize;
- private int mHead;
- private int mTail;
-
- private long[] newUnpaddedLongArray(int num) {
- return new long[num];
- }
- /**
- * Initializes a queue with the given starting capacity.
- *
- * @param initialCapacity the capacity.
- */
- public LongArrayQueue(int initialCapacity) {
- if (initialCapacity == 0) {
- mValues = EmptyArray.LONG;
- } else {
- mValues = newUnpaddedLongArray(initialCapacity);
- }
- mSize = 0;
- mHead = mTail = 0;
- }
-
- /**
- * Initializes a queue with default starting capacity.
- */
- public LongArrayQueue() {
- this(16);
- }
-
- /** @hide */
- public static int growSize(int currentSize) {
- return currentSize <= 4 ? 8 : currentSize * 2;
- }
-
- private void grow() {
- if (mSize < mValues.length) {
- throw new IllegalStateException("Queue not full yet!");
- }
- final int newSize = growSize(mSize);
- final long[] newArray = newUnpaddedLongArray(newSize);
- final int r = mValues.length - mHead; // Number of elements on and to the right of head.
- System.arraycopy(mValues, mHead, newArray, 0, r);
- System.arraycopy(mValues, 0, newArray, r, mHead);
- mValues = newArray;
- mHead = 0;
- mTail = mSize;
- }
-
- /**
- * Returns the number of elements in the queue.
- */
- public int size() {
- return mSize;
- }
-
- /**
- * Removes all elements from this queue.
- */
- public void clear() {
- mSize = 0;
- mHead = mTail = 0;
- }
-
- /**
- * Adds a value to the tail of the queue.
- *
- * @param value the value to be added.
- */
- public void addLast(long value) {
- if (mSize == mValues.length) {
- grow();
- }
- mValues[mTail] = value;
- mTail = (mTail + 1) % mValues.length;
- mSize++;
- }
-
- /**
- * Removes an element from the head of the queue.
- *
- * @return the element at the head of the queue.
- * @throws NoSuchElementException if the queue is empty.
- */
- public long removeFirst() {
- if (mSize == 0) {
- throw new NoSuchElementException("Queue is empty!");
- }
- final long ret = mValues[mHead];
- mHead = (mHead + 1) % mValues.length;
- mSize--;
- return ret;
- }
-
- /**
- * Returns the element at the given position from the head of the queue, where 0 represents the
- * head of the queue.
- *
- * @param position the position from the head of the queue.
- * @return the element found at the given position.
- * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
- * {@code position} >= {@link #size()}
- */
- public long get(int position) {
- if (position < 0 || position >= mSize) {
- throw new IndexOutOfBoundsException("Index " + position
- + " not valid for a queue of size " + mSize);
- }
- final int index = (mHead + position) % mValues.length;
- return mValues[index];
- }
-
- /**
- * Returns the element at the head of the queue, without removing it.
- *
- * @return the element at the head of the queue.
- * @throws NoSuchElementException if the queue is empty
- */
- public long peekFirst() {
- if (mSize == 0) {
- throw new NoSuchElementException("Queue is empty!");
- }
- return mValues[mHead];
- }
-
- /**
- * Returns the element at the tail of the queue.
- *
- * @return the element at the tail of the queue.
- * @throws NoSuchElementException if the queue is empty.
- */
- public long peekLast() {
- if (mSize == 0) {
- throw new NoSuchElementException("Queue is empty!");
- }
- final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
- return mValues[index];
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public String toString() {
- if (mSize <= 0) {
- return "{}";
- }
-
- final StringBuilder buffer = new StringBuilder(mSize * 64);
- buffer.append('{');
- buffer.append(get(0));
- for (int i = 1; i < mSize; i++) {
- buffer.append(", ");
- buffer.append(get(i));
- }
- buffer.append('}');
- return buffer.toString();
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
deleted file mode 100644
index dbbef61..0000000
--- a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.utils;
-
-import android.annotation.NonNull;
-import android.system.ErrnoException;
-import android.system.Os;
-
-import com.android.modules.utils.TypedXmlPullParser;
-
-import libcore.util.XmlObjectFactory;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.BufferedInputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Copied over partly from frameworks/base/core/java/com/android/internal/util/XmlUtils.java
- *
- * @hide
- */
-public class XmlUtils {
-
- private static final String STRING_ARRAY_SEPARATOR = ":";
-
- /** @hide */
- public static final void beginDocument(XmlPullParser parser, String firstElementName)
- throws XmlPullParserException, IOException {
- int type;
- while ((type = parser.next()) != parser.START_TAG
- && type != parser.END_DOCUMENT) {
- // Do nothing
- }
-
- if (type != parser.START_TAG) {
- throw new XmlPullParserException("No start tag found");
- }
-
- if (!parser.getName().equals(firstElementName)) {
- throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
- + ", expected " + firstElementName);
- }
- }
-
- /** @hide */
- public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
- throws IOException, XmlPullParserException {
- for (;;) {
- int type = parser.next();
- if (type == XmlPullParser.END_DOCUMENT
- || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
- return false;
- }
- if (type == XmlPullParser.START_TAG
- && parser.getDepth() == outerDepth + 1) {
- return true;
- }
- }
- }
-
- private static XmlPullParser newPullParser() {
- try {
- XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
- return parser;
- } catch (XmlPullParserException e) {
- throw new AssertionError();
- }
- }
-
- /** @hide */
- public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
- throws IOException {
- final byte[] magic = new byte[4];
- if (in instanceof FileInputStream) {
- try {
- Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
- } catch (ErrnoException e) {
- throw e.rethrowAsIOException();
- }
- } else {
- if (!in.markSupported()) {
- in = new BufferedInputStream(in);
- }
- in.mark(8);
- in.read(magic);
- in.reset();
- }
-
- final TypedXmlPullParser xml;
- xml = (TypedXmlPullParser) newPullParser();
- try {
- xml.setInput(in, "UTF_8");
- } catch (XmlPullParserException e) {
- throw new IOException(e);
- }
- return xml;
- }
-}
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index e998fe8..2aff2c3 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -34,7 +34,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_alignParentStart="true"
- android:paddingLeft="@dimen/autofill_view_left_padding"
+ android:paddingStart="@dimen/autofill_view_left_padding"
app:tint="?androidprv:attr/materialColorOnSurface"
android:background="@null"/>
diff --git a/packages/CredentialManager/res/values-be/strings.xml b/packages/CredentialManager/res/values-be/strings.xml
index d27f68b..0b8ade7 100644
--- a/packages/CredentialManager/res/values-be/strings.xml
+++ b/packages/CredentialManager/res/values-be/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Стварыць ключ доступу для ўваходу ў праграму \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Захаваць пароль для ўваходу ў праграму \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Захаваць даныя для ўваходу ў праграму \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Выкарыстаць сродак разблакіроўкі экрана, каб стварыць ключ доступу для праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Выкарыстаць сродак разблакіроўкі экрана, каб стварыць пароль для праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Выкарыстаць сродак разблакіроўкі экрана, каб захаваць даныя для ўваходу для праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
<string name="passkey" msgid="632353688396759522">"ключ доступу"</string>
<string name="password" msgid="6738570945182936667">"пароль"</string>
<string name="passkeys" msgid="5733880786866559847">"ключы доступу"</string>
diff --git a/packages/CredentialManager/res/values-bg/strings.xml b/packages/CredentialManager/res/values-bg/strings.xml
index 780699b..6a79b11 100644
--- a/packages/CredentialManager/res/values-bg/strings.xml
+++ b/packages/CredentialManager/res/values-bg/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Искате ли да създадете ключ за достъп, с който да влизате в(ъв) <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Искате ли да запазите паролата за влизане в(ъв) <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Искате ли да запазите данните за вход за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Искате ли да използвате опцията си за заключване на екрана, за да създадете ключ за достъп за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Искате ли да използвате опцията си за заключване на екрана, за да създадете парола за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Искате ли да използвате опцията си за заключване на екрана, за да запазите данните за вход за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"код за достъп"</string>
<string name="password" msgid="6738570945182936667">"парола"</string>
<string name="passkeys" msgid="5733880786866559847">"ключове за достъп"</string>
diff --git a/packages/CredentialManager/res/values-bn/strings.xml b/packages/CredentialManager/res/values-bn/strings.xml
index 9d429d4..76fc0a7 100644
--- a/packages/CredentialManager/res/values-bn/strings.xml
+++ b/packages/CredentialManager/res/values-bn/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপে সাইন-ইন করার জন্য পাসকী তৈরি করবেন?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপে সাইন-ইন করার জন্য পাসওয়ার্ড সেভ করবেন?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপের জন্য সাইন-ইন সংক্রান্ত তথ্য সেভ করবেন?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপের জন্য পাসকী তৈরি করতে আপনার স্ক্রিন লক ব্যবহার করতে চান?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপের জন্য পাসওয়ার্ড তৈরি করতে আপনার স্ক্রিন লক ব্যবহার করতে চান?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপের জন্য সাইন-ইন সংক্রান্ত তথ্য সেভ করতে আপনার স্ক্রিন লক ব্যবহার করতে চান?"</string>
<string name="passkey" msgid="632353688396759522">"পাসকী"</string>
<string name="password" msgid="6738570945182936667">"পাসওয়ার্ড"</string>
<string name="passkeys" msgid="5733880786866559847">"পাসকী"</string>
diff --git a/packages/CredentialManager/res/values-bs/strings.xml b/packages/CredentialManager/res/values-bs/strings.xml
index 00c211b..6d3a676 100644
--- a/packages/CredentialManager/res/values-bs/strings.xml
+++ b/packages/CredentialManager/res/values-bs/strings.xml
@@ -42,9 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Kreirati pristupni ključ da se prijavite u aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Sačuvati lozinku da se prijavite u aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Sačuvati podatke za prijavu u aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Želite li upotrijebiti zaključavanje zaslona za izradu pristupnog ključa za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Želite li upotrijebiti zaključavanje zaslona za izradu zaporke za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Želite li upotrijebiti zaključavanje zaslona za spremanje podataka za prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Koristiti zaključavanje ekrana da kreirate pristupni ključ za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Koristiti zaključavanje ekrana da kreirate lozinku za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Koristiti zaključavanje ekrana da sačuvate podatke za prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"pristupni ključ"</string>
<string name="password" msgid="6738570945182936667">"lozinka"</string>
<string name="passkeys" msgid="5733880786866559847">"pristupni ključevi"</string>
diff --git a/packages/CredentialManager/res/values-ca/strings.xml b/packages/CredentialManager/res/values-ca/strings.xml
index cf08741..28762e7 100644
--- a/packages/CredentialManager/res/values-ca/strings.xml
+++ b/packages/CredentialManager/res/values-ca/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Vols crear una clau d\'accés per iniciar la sessió a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Vols desar la contrasenya per iniciar la sessió a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Vols desar la informació d\'inici de sessió per a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Vols fer servir el bloqueig de pantalla per crear una clau d\'accés per a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Vols fer servir el bloqueig de pantalla per crear una contrasenya per a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Vols fer servir el bloqueig de pantalla per desar la informació d\'inici de sessió de <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"clau d\'accés"</string>
<string name="password" msgid="6738570945182936667">"contrasenya"</string>
<string name="passkeys" msgid="5733880786866559847">"claus d\'accés"</string>
diff --git a/packages/CredentialManager/res/values-cs/strings.xml b/packages/CredentialManager/res/values-cs/strings.xml
index 5e0d42e..a3ff390 100644
--- a/packages/CredentialManager/res/values-cs/strings.xml
+++ b/packages/CredentialManager/res/values-cs/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Vytvořit přístupový klíč k přihlašování do aplikace <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Uložit heslo k přihlašování do aplikace <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Uložit přihlašovací údaje pro aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Chcete pomocí zámku obrazovky vytvořit přístupový klíč pro aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Chcete pomocí zámku obrazovky vytvořit heslo pro aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Chcete pomocí zámku obrazovky uložit přihlašovací údaje pro aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"přístupový klíč"</string>
<string name="password" msgid="6738570945182936667">"heslo"</string>
<string name="passkeys" msgid="5733880786866559847">"přístupové klíče"</string>
diff --git a/packages/CredentialManager/res/values-da/strings.xml b/packages/CredentialManager/res/values-da/strings.xml
index e978b8e..b61b81c 100644
--- a/packages/CredentialManager/res/values-da/strings.xml
+++ b/packages/CredentialManager/res/values-da/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Vil du oprette en adgangsnøgle for at logge ind på <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Vil du gemme adgangskoden for at logge ind på <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Vil du gemme loginoplysningerne til <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Vil du bruge din skærmlås til at oprette en adgangsnøgle til <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Vil du bruge din skærmlås til at oprette en adgangskode til <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Vil du bruge din skærmlås til at gemme loginoplysningerne til <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"adgangsnøgle"</string>
<string name="password" msgid="6738570945182936667">"adgangskode"</string>
<string name="passkeys" msgid="5733880786866559847">"adgangsnøgler"</string>
diff --git a/packages/CredentialManager/res/values-de/strings.xml b/packages/CredentialManager/res/values-de/strings.xml
index b338b1d..9897443 100644
--- a/packages/CredentialManager/res/values-de/strings.xml
+++ b/packages/CredentialManager/res/values-de/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Passkey zur Anmeldung in <xliff:g id="APP_NAME">%1$s</xliff:g> erstellen?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Passwort zur Anmeldung in <xliff:g id="APP_NAME">%1$s</xliff:g> speichern?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Anmeldedaten für <xliff:g id="APP_NAME">%1$s</xliff:g> speichern?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Displaysperre verwenden, um einen Passkey für <xliff:g id="APP_NAME">%1$s</xliff:g> zu erstellen?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Displaysperre verwenden, um ein Passwort für <xliff:g id="APP_NAME">%1$s</xliff:g> zu erstellen?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Displaysperre verwenden, um Anmeldedaten für <xliff:g id="APP_NAME">%1$s</xliff:g> zu speichern?"</string>
<string name="passkey" msgid="632353688396759522">"Passkey"</string>
<string name="password" msgid="6738570945182936667">"Passwort"</string>
<string name="passkeys" msgid="5733880786866559847">"Passkeys"</string>
diff --git a/packages/CredentialManager/res/values-el/strings.xml b/packages/CredentialManager/res/values-el/strings.xml
index 29ad569..b1c3506 100644
--- a/packages/CredentialManager/res/values-el/strings.xml
+++ b/packages/CredentialManager/res/values-el/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Δημιουργία κλειδιού πρόσβασης για σύνδεση στην εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Αποθήκευση κωδικού πρόσβασης για σύνδεση στην εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Αποθήκευση πληροφοριών σύνδεσης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Θέλετε να χρησιμοποιήσετε το κλείδωμα οθόνης για τη δημιουργία ενός κλειδιού πρόσβασης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Θέλετε να χρησιμοποιήσετε το κλείδωμα οθόνης για τη δημιουργία ενός κωδικού πρόσβασης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Θέλετε να χρησιμοποιήσετε το κλείδωμα οθόνης για την αποθήκευση πληροφοριών σύνδεσης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
<string name="passkey" msgid="632353688396759522">"κλειδί πρόσβασης"</string>
<string name="password" msgid="6738570945182936667">"κωδικός πρόσβασης"</string>
<string name="passkeys" msgid="5733880786866559847">"κλειδιά πρόσβασης"</string>
diff --git a/packages/CredentialManager/res/values-en-rAU/strings.xml b/packages/CredentialManager/res/values-en-rAU/strings.xml
index b8bef99..1afd5f6 100644
--- a/packages/CredentialManager/res/values-en-rAU/strings.xml
+++ b/packages/CredentialManager/res/values-en-rAU/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Create passkey to sign in to <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Save password to sign in to <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Save sign-in info for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Use your screen lock to create a passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Use your screen lock to create a password for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Use your screen lock to save sign-in info for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
<string name="passkeys" msgid="5733880786866559847">"passkeys"</string>
diff --git a/packages/CredentialManager/res/values-en-rGB/strings.xml b/packages/CredentialManager/res/values-en-rGB/strings.xml
index b8bef99..1afd5f6 100644
--- a/packages/CredentialManager/res/values-en-rGB/strings.xml
+++ b/packages/CredentialManager/res/values-en-rGB/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Create passkey to sign in to <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Save password to sign in to <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Save sign-in info for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Use your screen lock to create a passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Use your screen lock to create a password for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Use your screen lock to save sign-in info for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
<string name="passkeys" msgid="5733880786866559847">"passkeys"</string>
diff --git a/packages/CredentialManager/res/values-en-rIN/strings.xml b/packages/CredentialManager/res/values-en-rIN/strings.xml
index b8bef99..1afd5f6 100644
--- a/packages/CredentialManager/res/values-en-rIN/strings.xml
+++ b/packages/CredentialManager/res/values-en-rIN/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Create passkey to sign in to <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Save password to sign in to <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Save sign-in info for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Use your screen lock to create a passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Use your screen lock to create a password for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Use your screen lock to save sign-in info for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
<string name="passkeys" msgid="5733880786866559847">"passkeys"</string>
diff --git a/packages/CredentialManager/res/values-es-rUS/strings.xml b/packages/CredentialManager/res/values-es-rUS/strings.xml
index 3b4392d..e007ab7 100644
--- a/packages/CredentialManager/res/values-es-rUS/strings.xml
+++ b/packages/CredentialManager/res/values-es-rUS/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"¿Quieres crear una llave de acceso para acceder a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"¿Quieres guardar la contraseña para acceder a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"¿Quieres guardar la información de acceso para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"¿Quieres usar el bloqueo de pantalla para crear una llave de acceso para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"¿Quieres usar el bloqueo de pantalla para crear una contraseña para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"¿Quieres usar el bloqueo de pantalla para guardar la información de acceso para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"llave de acceso"</string>
<string name="password" msgid="6738570945182936667">"contraseña"</string>
<string name="passkeys" msgid="5733880786866559847">"llaves de acceso"</string>
diff --git a/packages/CredentialManager/res/values-es/strings.xml b/packages/CredentialManager/res/values-es/strings.xml
index 427ed1d..e82f331 100644
--- a/packages/CredentialManager/res/values-es/strings.xml
+++ b/packages/CredentialManager/res/values-es/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"¿Crear llave de acceso para iniciar sesión en <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"¿Guardar contraseña para iniciar sesión en <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"¿Guardar la información de inicio de sesión de <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"¿Usar tu bloqueo de pantalla para crear una llave de acceso para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"¿Usar tu bloqueo de pantalla para crear una contraseña para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"¿Usar tu bloqueo de pantalla para guardar tu información de inicio de sesión para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"llave de acceso"</string>
<string name="password" msgid="6738570945182936667">"contraseña"</string>
<string name="passkeys" msgid="5733880786866559847">"llaves de acceso"</string>
diff --git a/packages/CredentialManager/res/values-et/strings.xml b/packages/CredentialManager/res/values-et/strings.xml
index 44b907e..a4c3438 100644
--- a/packages/CredentialManager/res/values-et/strings.xml
+++ b/packages/CredentialManager/res/values-et/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Kas luua rakendusse <xliff:g id="APP_NAME">%1$s</xliff:g> sisselogimiseks pääsuvõti?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Kas salvestada rakendusse <xliff:g id="APP_NAME">%1$s</xliff:g> sisselogimiseks parool?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Kas salvestada rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks sisselogimisteave?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Kas kasutada ekraanilukku, et luua rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks pääsuvõti?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Kas kasutada ekraanilukku, et luua rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks parool?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Kas kasutada ekraanilukku, et salvestada rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks sisselogimisteave?"</string>
<string name="passkey" msgid="632353688396759522">"pääsuvõti"</string>
<string name="password" msgid="6738570945182936667">"parool"</string>
<string name="passkeys" msgid="5733880786866559847">"pääsuvõtmed"</string>
diff --git a/packages/CredentialManager/res/values-eu/strings.xml b/packages/CredentialManager/res/values-eu/strings.xml
index afc91ea..2f62ba3 100644
--- a/packages/CredentialManager/res/values-eu/strings.xml
+++ b/packages/CredentialManager/res/values-eu/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioan saioa hasteko sarbide-gako bat sortu nahi duzu?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioan saioa hasteko pasahitza gorde nahi duzu?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioan saioa hasteko informazioa gorde nahi duzu?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Pantailaren blokeoa erabili nahi duzu <xliff:g id="APP_NAME">%1$s</xliff:g> aplikaziorako sarbide-gako bat sortzeko?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Pantailaren blokeoa erabili nahi duzu <xliff:g id="APP_NAME">%1$s</xliff:g> aplikaziorako pasahitz bat sortzeko?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Pantailaren blokeoa erabili nahi duzu <xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioan saioa hasteko informazioa gordetzeko?"</string>
<string name="passkey" msgid="632353688396759522">"sarbide-gakoa"</string>
<string name="password" msgid="6738570945182936667">"pasahitza"</string>
<string name="passkeys" msgid="5733880786866559847">"sarbide-gakoak"</string>
diff --git a/packages/CredentialManager/res/values-fa/strings.xml b/packages/CredentialManager/res/values-fa/strings.xml
index 095cb06..6266ed2 100644
--- a/packages/CredentialManager/res/values-fa/strings.xml
+++ b/packages/CredentialManager/res/values-fa/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"برای ورود به سیستم <xliff:g id="APP_NAME">%1$s</xliff:g>، گذرکلید ایجاد شود؟"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"برای ورود به سیستم <xliff:g id="APP_NAME">%1$s</xliff:g>، گذرواژه ذخیره شود؟"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"اطلاعات ورود به سیستم <xliff:g id="APP_NAME">%1$s</xliff:g> ذخیره شود؟"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"برای ایجاد گذرکلید برای <xliff:g id="APP_NAME">%1$s</xliff:g> از قفل صفحه استفاده شود؟"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"برای ایجاد گذرواژه برای <xliff:g id="APP_NAME">%1$s</xliff:g> از قفل صفحه استفاده شود؟"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"برای ذخیرهسازی اطلاعات ورود به سیستم <xliff:g id="APP_NAME">%1$s</xliff:g> از قفل صفحه استفاده شود؟"</string>
<string name="passkey" msgid="632353688396759522">"گذرکلید"</string>
<string name="password" msgid="6738570945182936667">"گذرواژه"</string>
<string name="passkeys" msgid="5733880786866559847">"گذرکلیدها"</string>
diff --git a/packages/CredentialManager/res/values-fi/strings.xml b/packages/CredentialManager/res/values-fi/strings.xml
index 7de1151..838d6d9 100644
--- a/packages/CredentialManager/res/values-fi/strings.xml
+++ b/packages/CredentialManager/res/values-fi/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Luodaanko avainkoodi sisäänkirjautumista (<xliff:g id="APP_NAME">%1$s</xliff:g>) varten?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Tallennetaanko salasana sisäänkirjautumista (<xliff:g id="APP_NAME">%1$s</xliff:g>) varten?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Tallennetaanko kirjautumistiedot (<xliff:g id="APP_NAME">%1$s</xliff:g>)?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Käytetäänkö näytön lukitusta aivankoodin luomiseen sovellukselle (<xliff:g id="APP_NAME">%1$s</xliff:g>)?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Käytetäänkö näytön lukitusta salasanan luomiseen sovellukselle (<xliff:g id="APP_NAME">%1$s</xliff:g>)?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Käytetäänkö näytön lukitusta sovelluksen (<xliff:g id="APP_NAME">%1$s</xliff:g>) sisäänkirjautumistietojen tallentamiseen?"</string>
<string name="passkey" msgid="632353688396759522">"avainkoodi"</string>
<string name="password" msgid="6738570945182936667">"salasana"</string>
<string name="passkeys" msgid="5733880786866559847">"avainkoodit"</string>
diff --git a/packages/CredentialManager/res/values-fr-rCA/strings.xml b/packages/CredentialManager/res/values-fr-rCA/strings.xml
index 519b14a..834dffe 100644
--- a/packages/CredentialManager/res/values-fr-rCA/strings.xml
+++ b/packages/CredentialManager/res/values-fr-rCA/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Créer une clé d\'accès pour se connecter à <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Enregistrer un mot de passe pour se connecter à <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Enregistrer les renseignements de connexion pour <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Utiliser le Verrouillage de l\'écran pour créer une clé d\'accès pour <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Utiliser le Verrouillage de l\'écran pour créer un mot de passe pour <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Utiliser le Verrouillage de l\'écran pour enregistrer les renseignements de connexion pour <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"clé d\'accès"</string>
<string name="password" msgid="6738570945182936667">"mot de passe"</string>
<string name="passkeys" msgid="5733880786866559847">"clés d\'accès"</string>
diff --git a/packages/CredentialManager/res/values-fr/strings.xml b/packages/CredentialManager/res/values-fr/strings.xml
index f63c7c2..1c86c9b 100644
--- a/packages/CredentialManager/res/values-fr/strings.xml
+++ b/packages/CredentialManager/res/values-fr/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Créer une clé d\'accès pour se connecter à <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Enregistrer un mot de passe pour se connecter à <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Enregistrer les informations de connexion pour <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Utiliser le verrouillage de l\'écran afin de créer une clé d\'accès pour <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Utiliser le verrouillage de l\'écran afin de créer un mot de passe pour <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Utiliser le verrouillage de l\'écran afin d\'enregistrer les informations de connexion pour <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
<string name="passkey" msgid="632353688396759522">"clé d\'accès"</string>
<string name="password" msgid="6738570945182936667">"mot de passe"</string>
<string name="passkeys" msgid="5733880786866559847">"clés d\'accès"</string>
diff --git a/packages/CredentialManager/res/values-gl/strings.xml b/packages/CredentialManager/res/values-gl/strings.xml
index d756200c..a95f6b9 100644
--- a/packages/CredentialManager/res/values-gl/strings.xml
+++ b/packages/CredentialManager/res/values-gl/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Queres crear unha clave de acceso para iniciar sesión en <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Queres gardar o contrasinal para iniciar sesión en <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Queres gardar a información de inicio de sesión de <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Queres usar o bloqueo de pantalla para crear unha clave de acceso para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Queres usar o bloqueo de pantalla para crear un contrasinal para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Queres usar o bloqueo de pantalla para gardar a información de inicio de sesión para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"clave de acceso"</string>
<string name="password" msgid="6738570945182936667">"contrasinal"</string>
<string name="passkeys" msgid="5733880786866559847">"claves de acceso"</string>
diff --git a/packages/CredentialManager/res/values-hu/strings.xml b/packages/CredentialManager/res/values-hu/strings.xml
index fe749f61..46b80da 100644
--- a/packages/CredentialManager/res/values-hu/strings.xml
+++ b/packages/CredentialManager/res/values-hu/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Létrehoz azonosítókulcsot a következőbe való bejelentkezéshez: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Menti a jelszót a következőbe való bejelentkezéshez: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Menti a bejelentkezési adatokat a következőhöz: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Képernyőzár használatával hoz létre azonosítókulcsot a következőhöz: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Képernyőzár használatával hoz létre jelszót a következőhöz: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Képernyőzár használatával menti a bejelentkezési adatokat a következőhöz: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"azonosítókulcs"</string>
<string name="password" msgid="6738570945182936667">"jelszó"</string>
<string name="passkeys" msgid="5733880786866559847">"azonosítókulcsait"</string>
diff --git a/packages/CredentialManager/res/values-hy/strings.xml b/packages/CredentialManager/res/values-hy/strings.xml
index 452acb7..2a8784c 100644
--- a/packages/CredentialManager/res/values-hy/strings.xml
+++ b/packages/CredentialManager/res/values-hy/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Ստեղծե՞լ անցաբառ՝ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելված մուտք գործելու համար"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Պահե՞լ գաղտնաբառը՝ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելված մուտք գործելու համար"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Պահե՞լ «<xliff:g id="APP_NAME">%1$s</xliff:g>» հավելվածի մուտքի տվյալները"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Օգտագործե՞լ էկրանի կողպումը՝ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի մուտքի բանալի ստեղծելու համար"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Օգտագործե՞լ էկրանի կողպումը՝ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի գաղտնաբառ ստեղծելու համար"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Օգտագործե՞լ էկրանի կողպումը՝ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի մուտքի տվյալները պահելու համար"</string>
<string name="passkey" msgid="632353688396759522">"անցաբառ"</string>
<string name="password" msgid="6738570945182936667">"գաղտնաբառ"</string>
<string name="passkeys" msgid="5733880786866559847">"անցաբառեր"</string>
diff --git a/packages/CredentialManager/res/values-it/strings.xml b/packages/CredentialManager/res/values-it/strings.xml
index ce04dbc..0d6f839 100644
--- a/packages/CredentialManager/res/values-it/strings.xml
+++ b/packages/CredentialManager/res/values-it/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Creare passkey per accedere all\'app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Salvare password per accedere all\'app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Vuoi salvare i dati di accesso di <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Usare il blocco schermo per creare una passkey per <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Usare il blocco schermo per creare una password per <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Usare il blocco schermo per salvare le informazioni di accesso per <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
<string name="passkeys" msgid="5733880786866559847">"passkey"</string>
diff --git a/packages/CredentialManager/res/values-iw/strings.xml b/packages/CredentialManager/res/values-iw/strings.xml
index 07b26b7..7d24825 100644
--- a/packages/CredentialManager/res/values-iw/strings.xml
+++ b/packages/CredentialManager/res/values-iw/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"ליצור מפתח גישה כדי להיכנס לחשבון ב-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"לשמור את הסיסמה כדי להיכנס לחשבון ב-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"לשמור את פרטי הכניסה של <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"להשתמש בנעילת המסך כדי ליצור מפתח גישה בשביל <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"להשתמש בנעילת המסך כדי ליצור סיסמה בשביל <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"להשתמש בנעילת המסך כדי לשמור את פרטי הכניסה בשביל <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"מפתח גישה"</string>
<string name="password" msgid="6738570945182936667">"סיסמה"</string>
<string name="passkeys" msgid="5733880786866559847">"מפתחות גישה"</string>
diff --git a/packages/CredentialManager/res/values-ja/strings.xml b/packages/CredentialManager/res/values-ja/strings.xml
index f12bb5e..16e3195 100644
--- a/packages/CredentialManager/res/values-ja/strings.xml
+++ b/packages/CredentialManager/res/values-ja/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"<xliff:g id="APP_NAME">%1$s</xliff:g> にログインするためにパスキーを作成しますか?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"<xliff:g id="APP_NAME">%1$s</xliff:g> にログインするためにパスワードを保存しますか?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"<xliff:g id="APP_NAME">%1$s</xliff:g> のログイン情報を保存しますか?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"画面ロックを使用して <xliff:g id="APP_NAME">%1$s</xliff:g> のパスキーを作成しますか?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"画面ロックを使用して <xliff:g id="APP_NAME">%1$s</xliff:g> のパスワードを作成しますか?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"画面ロックを使用して <xliff:g id="APP_NAME">%1$s</xliff:g> のログイン情報を保存しますか?"</string>
<string name="passkey" msgid="632353688396759522">"パスキー"</string>
<string name="password" msgid="6738570945182936667">"パスワード"</string>
<string name="passkeys" msgid="5733880786866559847">"パスキー"</string>
diff --git a/packages/CredentialManager/res/values-ka/strings.xml b/packages/CredentialManager/res/values-ka/strings.xml
index 4c61540..a852f1c 100644
--- a/packages/CredentialManager/res/values-ka/strings.xml
+++ b/packages/CredentialManager/res/values-ka/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"შესასვლელად წვდომის გასაღების შექმნა: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"შესასვლელი პაროლის შენახვა: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"აპში შესვლის ინფორმაციის შენახვა: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"გსურთ თქვენი ეკრანის დაბლოკვის გამოყენება <xliff:g id="APP_NAME">%1$s</xliff:g>-ისთვის წვდომის გასაღების შესაქმნელად?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"გსურთ თქვენი ეკრანის დაბლოკვის გამოყენება <xliff:g id="APP_NAME">%1$s</xliff:g>-ისთვის პაროლის შესაქმნელად?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"გსურთ თქვენი ეკრანის დაბლოკვის გამოყენება შესვლის ინფორმაციის შესანახად <xliff:g id="APP_NAME">%1$s</xliff:g>-ისთვის?"</string>
<string name="passkey" msgid="632353688396759522">"წვდომის გასაღები"</string>
<string name="password" msgid="6738570945182936667">"პაროლი"</string>
<string name="passkeys" msgid="5733880786866559847">"წვდომის გასაღები"</string>
diff --git a/packages/CredentialManager/res/values-kk/strings.xml b/packages/CredentialManager/res/values-kk/strings.xml
index 5b76ac8..03eb9d2 100644
--- a/packages/CredentialManager/res/values-kk/strings.xml
+++ b/packages/CredentialManager/res/values-kk/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасына кіру үшін кіру кілті жасалсын ба?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасына кіру үшін құпия сөз сақталсын ба?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"<xliff:g id="APP_NAME">%1$s</xliff:g> үшін кіру мәліметін сақтау керек пе?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"<xliff:g id="APP_NAME">%1$s</xliff:g> кіру кілтін жасау үшін экран құлпын пайдаланасыз ба?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"<xliff:g id="APP_NAME">%1$s</xliff:g> құпия сөзін жасау үшін экран құлпын пайдаланасыз ба?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасындағы аккаунтқа кіру ақпаратын сақтау үшін экран құлпын пайдаланасыз ба?"</string>
<string name="passkey" msgid="632353688396759522">"Кіру кілті"</string>
<string name="password" msgid="6738570945182936667">"құпия сөз"</string>
<string name="passkeys" msgid="5733880786866559847">"кіру кілттері"</string>
diff --git a/packages/CredentialManager/res/values-kn/strings.xml b/packages/CredentialManager/res/values-kn/strings.xml
index 01d0f59..2f090e4 100644
--- a/packages/CredentialManager/res/values-kn/strings.xml
+++ b/packages/CredentialManager/res/values-kn/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಲು ಪಾಸ್ಕೀ ಯನ್ನು ರಚಿಸಬೇಕೇ?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಲು ಪಾಸ್ವರ್ಡ್ ಅನ್ನು ಸೇವ್ ಮಾಡಬೇಕೇ?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಸೈನ್-ಇನ್ ಮಾಹಿತಿಯನ್ನು ಸೇವ್ ಮಾಡಬೇಕೇ?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಪಾಸ್ಕೀಯನ್ನು ರಚಿಸಲು ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಅನ್ನು ಬಳಸುವುದೇ?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಪಾಸ್ವರ್ಡ್ ಅನ್ನು ರಚಿಸಲು ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಅನ್ನು ಬಳಸುವುದೇ?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಸೈನ್ ಇನ್ ಮಾಹಿತಿಯನ್ನು ಸೇವ್ ಮಾಡಲು ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಅನ್ನು ಬಳಸುವುದೇ?"</string>
<string name="passkey" msgid="632353688396759522">"ಪಾಸ್ಕೀ"</string>
<string name="password" msgid="6738570945182936667">"ಪಾಸ್ವರ್ಡ್"</string>
<string name="passkeys" msgid="5733880786866559847">"ಪಾಸ್ಕೀಗಳು"</string>
diff --git a/packages/CredentialManager/res/values-ko/strings.xml b/packages/CredentialManager/res/values-ko/strings.xml
index 1f1fa29..f2ead85 100644
--- a/packages/CredentialManager/res/values-ko/strings.xml
+++ b/packages/CredentialManager/res/values-ko/strings.xml
@@ -20,7 +20,7 @@
<string name="app_name" msgid="4539824758261855508">"인증서 관리자"</string>
<string name="string_cancel" msgid="6369133483981306063">"취소"</string>
<string name="string_continue" msgid="1346732695941131882">"계속"</string>
- <string name="string_more_options" msgid="2763852250269945472">"다른 방법 저장하기"</string>
+ <string name="string_more_options" msgid="2763852250269945472">"다른 방법으로 저장하기"</string>
<string name="string_learn_more" msgid="4541600451688392447">"자세히 알아보기"</string>
<string name="content_description_show_password" msgid="3283502010388521607">"비밀번호 표시"</string>
<string name="content_description_hide_password" msgid="6841375971631767996">"비밀번호 숨기기"</string>
@@ -39,15 +39,12 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"비밀번호 없는 미래로 나아가는 과정에서 비밀번호는 여전히 패스키와 함께 사용될 것입니다."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> 저장 위치 선택"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"정보를 저장해서 다음에 더 빠르게 로그인하려면 비밀번호 관리자를 선택하세요."</string>
- <string name="choose_create_option_passkey_title" msgid="8762295821604276511">"패스키를 생성하여 <xliff:g id="APP_NAME">%1$s</xliff:g>에 로그인하시겠습니까?"</string>
+ <string name="choose_create_option_passkey_title" msgid="8762295821604276511">"<xliff:g id="APP_NAME">%1$s</xliff:g>의 패스키를 생성할까요?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"비밀번호를 저장하여 <xliff:g id="APP_NAME">%1$s</xliff:g>에 로그인하시겠습니까?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"<xliff:g id="APP_NAME">%1$s</xliff:g>의 로그인 정보를 저장하시겠습니까?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"<xliff:g id="APP_NAME">%1$s</xliff:g>용 패스키를 생성하기 위해 화면 잠금을 사용하시겠습니까?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"<xliff:g id="APP_NAME">%1$s</xliff:g>용 패스키를 생성하기 위해 비밀번호를 사용하시겠습니까?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"<xliff:g id="APP_NAME">%1$s</xliff:g>용 로그인 정보를 저장하기 위해 화면 잠금을 사용하시겠습니까?"</string>
<string name="passkey" msgid="632353688396759522">"패스키"</string>
<string name="password" msgid="6738570945182936667">"비밀번호"</string>
<string name="passkeys" msgid="5733880786866559847">"패스키"</string>
diff --git a/packages/CredentialManager/res/values-lt/strings.xml b/packages/CredentialManager/res/values-lt/strings.xml
index 3b0124c..5e0fbe0 100644
--- a/packages/CredentialManager/res/values-lt/strings.xml
+++ b/packages/CredentialManager/res/values-lt/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Sukurti prieigos raktą, skirtą prisijungti prie „<xliff:g id="APP_NAME">%1$s</xliff:g>“?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Sukurti slaptažodį, skirtą prisijungti prie „<xliff:g id="APP_NAME">%1$s</xliff:g>“?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Išsaugoti prisijungimo prie „<xliff:g id="APP_NAME">%1$s</xliff:g>“ informaciją?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Sukurti „<xliff:g id="APP_NAME">%1$s</xliff:g>“ prieigos raktą naudojant ekrano užraktą?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Sukurti „<xliff:g id="APP_NAME">%1$s</xliff:g>“ slaptažodį naudojant ekrano užraktą?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Naudoti ekrano užraktą norint išsaugoti „<xliff:g id="APP_NAME">%1$s</xliff:g>“ prisijungimo informaciją?"</string>
<string name="passkey" msgid="632353688396759522">"„passkey“"</string>
<string name="password" msgid="6738570945182936667">"slaptažodis"</string>
<string name="passkeys" msgid="5733880786866559847">"prieigos raktas"</string>
diff --git a/packages/CredentialManager/res/values-lv/strings.xml b/packages/CredentialManager/res/values-lv/strings.xml
index 095fbee..f56d7f1 100644
--- a/packages/CredentialManager/res/values-lv/strings.xml
+++ b/packages/CredentialManager/res/values-lv/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Vai izveidot piekļuves atslēgu, lai pierakstītos lietotnē <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Vai saglabāt paroli, lai pierakstītos lietotnē <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Vai saglabāt pierakstīšanās informāciju lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Vai izmantot ekrāna bloķēšanas opciju, lai izveidotu piekļuves atslēgu lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Vai izmantot ekrāna bloķēšanas opciju, lai izveidotu paroli lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Vai izmantot ekrāna bloķēšanas opciju, lai saglabātu pierakstīšanās informāciju lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"piekļuves atslēga"</string>
<string name="password" msgid="6738570945182936667">"parole"</string>
<string name="passkeys" msgid="5733880786866559847">"piekļuves atslēgas"</string>
diff --git a/packages/CredentialManager/res/values-mk/strings.xml b/packages/CredentialManager/res/values-mk/strings.xml
index 0f22f6f..ec4df56 100644
--- a/packages/CredentialManager/res/values-mk/strings.xml
+++ b/packages/CredentialManager/res/values-mk/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Да се создаде криптографски клуч за најавување на <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Да се зачува лозинката за најавување на <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Да се зачуваат податоците за најавување за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Да се користи заклучувањето екран за создавање криптографски клуч за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Да се користи заклучувањето екран за создавање лозинка за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Да се користи заклучувањето екран за зачувување на податоците за најавување за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"криптографски клуч"</string>
<string name="password" msgid="6738570945182936667">"лозинка"</string>
<string name="passkeys" msgid="5733880786866559847">"криптографски клучеви"</string>
diff --git a/packages/CredentialManager/res/values-mn/strings.xml b/packages/CredentialManager/res/values-mn/strings.xml
index 5554670..093baab 100644
--- a/packages/CredentialManager/res/values-mn/strings.xml
+++ b/packages/CredentialManager/res/values-mn/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д нэвтрэхийн тулд нэвтрэх түлхүүр үүсгэх үү?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д нэвтрэхийн тулд нууц үгийг хадгалах уу?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"<xliff:g id="APP_NAME">%1$s</xliff:g>-н нэвтрэх мэдээллийг хадгалах уу?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д нэвтрэх түлхүүр үүсгэхийн тулд дэлгэцийн түгжээгээ ашиглах уу?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д нууц үг үүсгэхийн тулд дэлгэцийн түгжээгээ ашиглах уу?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д нэвтрэх мэдээлэл хадгалахын тулд дэлгэцийн түгжээгээ ашиглах уу?"</string>
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"нууц үг"</string>
<string name="passkeys" msgid="5733880786866559847">"нэвтрэх түлхүүрүүд"</string>
diff --git a/packages/CredentialManager/res/values-mr/strings.xml b/packages/CredentialManager/res/values-mr/strings.xml
index ce415f5..cc52617 100644
--- a/packages/CredentialManager/res/values-mr/strings.xml
+++ b/packages/CredentialManager/res/values-mr/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"<xliff:g id="APP_NAME">%1$s</xliff:g> मध्ये साइन इन करण्यासाठी पासकी तयार करायची आहे का?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"<xliff:g id="APP_NAME">%1$s</xliff:g> मध्ये साइन इन करण्यासाठी पासवर्ड सेव्ह करायचा आहे का?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी साइन-इनसंबंधित माहिती सेव्ह करायची का?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी पासकी तयार करण्याकरिता तुमचे स्क्रीन लॉक वापरायचे आहे का?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी पासवर्ड तयार करण्याकरिता तुमचे स्क्रीन लॉक वापरायचे आहे का?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी साइन-इनसंबंधित माहिती सेव्ह करण्याकरिता तुमचे स्क्रीन लॉक वापरायचे आहे का?"</string>
<string name="passkey" msgid="632353688396759522">"पासकी"</string>
<string name="password" msgid="6738570945182936667">"पासवर्ड"</string>
<string name="passkeys" msgid="5733880786866559847">"पासकी"</string>
diff --git a/packages/CredentialManager/res/values-my/strings.xml b/packages/CredentialManager/res/values-my/strings.xml
index 5af7b72..aa08aa7 100644
--- a/packages/CredentialManager/res/values-my/strings.xml
+++ b/packages/CredentialManager/res/values-my/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"<xliff:g id="APP_NAME">%1$s</xliff:g> သို့ လက်မှတ်ထိုးဝင်ရန် လျှို့ဝှက်ကီး ပြုလုပ်မလား။"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"<xliff:g id="APP_NAME">%1$s</xliff:g> သို့ လက်မှတ်ထိုးဝင်ရန် စကားဝှက်ကို သိမ်းမလား။"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် လက်မှတ်ထိုးဝင်ရန် အချက်အလက်ကို သိမ်းမလား။"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် လျှို့ဝှက်ကီးပြုလုပ်ရန် သင့်ဖန်သားပြင်လော့ခ်ကို သုံးမလား။"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် စကားဝှက်ပြုလုပ်ရန် သင့်ဖန်သားပြင်လော့ခ်ကို သုံးမလား။"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် လက်မှတ်ထိုးဝင်ရန် အချက်အလက်များ သိမ်းရန် သင့်ဖန်သားပြင်လော့ခ်ကို သုံးမလား။"</string>
<string name="passkey" msgid="632353688396759522">"လျှို့ဝှက်ကီး"</string>
<string name="password" msgid="6738570945182936667">"စကားဝှက်"</string>
<string name="passkeys" msgid="5733880786866559847">"လျှို့ဝှက်ကီးများ"</string>
diff --git a/packages/CredentialManager/res/values-nb/strings.xml b/packages/CredentialManager/res/values-nb/strings.xml
index b6a679a..8cf3444 100644
--- a/packages/CredentialManager/res/values-nb/strings.xml
+++ b/packages/CredentialManager/res/values-nb/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Vil du opprette en passnøkkel for å logge på <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Vil du lagre passordet for å logge på <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Vil du lagre påloggingsinformasjon for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Vil du bruke skjermlåsen til å opprette en passnøkkel for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Vil du bruke skjermlåsen til å opprette et passord for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Vil du bruke skjermlåsen til å lagre påloggingsinformasjon for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"passnøkkel"</string>
<string name="password" msgid="6738570945182936667">"passord"</string>
<string name="passkeys" msgid="5733880786866559847">"passnøkler"</string>
diff --git a/packages/CredentialManager/res/values-nl/strings.xml b/packages/CredentialManager/res/values-nl/strings.xml
index 06ebc08..6707ff0 100644
--- a/packages/CredentialManager/res/values-nl/strings.xml
+++ b/packages/CredentialManager/res/values-nl/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Toegangssleutel maken om in te loggen bij <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Wachtwoord opslaan om in te loggen bij <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Inloggegevens opslaan voor <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Je schermvergrendeling gebruiken om een toegangssleutel voor <xliff:g id="APP_NAME">%1$s</xliff:g> te maken?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Je schermvergrendeling gebruiken om een wachtwoord voor <xliff:g id="APP_NAME">%1$s</xliff:g> te maken?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Je schermvergrendeling gebruiken om inloggegevens voor <xliff:g id="APP_NAME">%1$s</xliff:g> op te slaan?"</string>
<string name="passkey" msgid="632353688396759522">"Toegangssleutel"</string>
<string name="password" msgid="6738570945182936667">"wachtwoord"</string>
<string name="passkeys" msgid="5733880786866559847">"toegangssleutels"</string>
diff --git a/packages/CredentialManager/res/values-pa/strings.xml b/packages/CredentialManager/res/values-pa/strings.xml
index 4cf8e17..a5aceb7 100644
--- a/packages/CredentialManager/res/values-pa/strings.xml
+++ b/packages/CredentialManager/res/values-pa/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਵਿੱਚ ਸਾਈਨ-ਇਨ ਕਰਨ ਲਈ ਪਾਸਕੀ ਬਣਾਉਣੀ ਹੈ?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਵਿੱਚ ਸਾਈਨ-ਇਨ ਕਰਨ ਲਈ ਪਾਸਵਰਡ ਰੱਖਿਅਤ ਕਰਨਾ ਹੈ?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਰੱਖਿਅਤ ਕਰਨੀ ਹੈ?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਪਾਸਕੀ ਬਣਾਉਣ ਵਾਸਤੇ ਆਪਣੇ ਸਕ੍ਰੀਨ ਲਾਕ ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਪਾਸਵਰਡ ਬਣਾਉਣ ਵਾਸਤੇ ਆਪਣੇ ਸਕ੍ਰੀਨ ਲਾਕ ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਰੱਖਿਅਤ ਕਰਨ ਵਾਸਤੇ ਆਪਣੇ ਸਕ੍ਰੀਨ ਲਾਕ ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string>
<string name="passkey" msgid="632353688396759522">"ਪਾਸਕੀ"</string>
<string name="password" msgid="6738570945182936667">"ਪਾਸਵਰਡ"</string>
<string name="passkeys" msgid="5733880786866559847">"ਪਾਸਕੀਆਂ"</string>
diff --git a/packages/CredentialManager/res/values-pt-rBR/strings.xml b/packages/CredentialManager/res/values-pt-rBR/strings.xml
index e493871..b508af9 100644
--- a/packages/CredentialManager/res/values-pt-rBR/strings.xml
+++ b/packages/CredentialManager/res/values-pt-rBR/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Criar chave de acesso para fazer login no app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Salvar senha para fazer login no app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Salvar informações de login do app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Usar o bloqueio de tela para criar uma chave de acesso para o app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Usar o bloqueio de tela para criar uma senha para o app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Usar o bloqueio de tela para salvar as informações de login do app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"chave de acesso"</string>
<string name="password" msgid="6738570945182936667">"senha"</string>
<string name="passkeys" msgid="5733880786866559847">"chaves de acesso"</string>
diff --git a/packages/CredentialManager/res/values-pt/strings.xml b/packages/CredentialManager/res/values-pt/strings.xml
index e493871..b508af9 100644
--- a/packages/CredentialManager/res/values-pt/strings.xml
+++ b/packages/CredentialManager/res/values-pt/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Criar chave de acesso para fazer login no app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Salvar senha para fazer login no app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Salvar informações de login do app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Usar o bloqueio de tela para criar uma chave de acesso para o app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Usar o bloqueio de tela para criar uma senha para o app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Usar o bloqueio de tela para salvar as informações de login do app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"chave de acesso"</string>
<string name="password" msgid="6738570945182936667">"senha"</string>
<string name="passkeys" msgid="5733880786866559847">"chaves de acesso"</string>
diff --git a/packages/CredentialManager/res/values-ro/strings.xml b/packages/CredentialManager/res/values-ro/strings.xml
index 87b551b..ccbf228 100644
--- a/packages/CredentialManager/res/values-ro/strings.xml
+++ b/packages/CredentialManager/res/values-ro/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Creezi o cheie de acces pentru a te conecta la <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Salvezi parola pentru a te conecta la <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Salvezi informațiile de conectare pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Folosești blocarea ecranului ca să creezi o cheie de acces pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Folosești blocarea ecranului ca să creezi o parolă pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Folosești blocarea ecranului ca să salvezi informațiile de conectare pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"cheia de acces"</string>
<string name="password" msgid="6738570945182936667">"parolă"</string>
<string name="passkeys" msgid="5733880786866559847">"cheile de acces"</string>
diff --git a/packages/CredentialManager/res/values-si/strings.xml b/packages/CredentialManager/res/values-si/strings.xml
index ab78c06..0acc655 100644
--- a/packages/CredentialManager/res/values-si/strings.xml
+++ b/packages/CredentialManager/res/values-si/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"<xliff:g id="APP_NAME">%1$s</xliff:g> වෙත පුරනය වීමට මුරයතුරක් තනන්න ද?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"<xliff:g id="APP_NAME">%1$s</xliff:g> වෙත පුරනය වීමට මුරපදය සුරකින්න ද?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා පුරනය වීමේ තතු සුරකින්න ද?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා මුරපදයක් තැනීමට ඔබේ තිර අගුල භාවිත කරන්න ද?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා මුරපදයක් තැනීමට ඔබේ තිර අගුල භාවිත කරන්න ද?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා පුරනය වීමේ තතු සුරැකීමට ඔබේ තිර අගුල භාවිතා කරන්න ද?"</string>
<string name="passkey" msgid="632353688396759522">"මුරයතුර"</string>
<string name="password" msgid="6738570945182936667">"මුරපදය"</string>
<string name="passkeys" msgid="5733880786866559847">"මුරයතුරු"</string>
diff --git a/packages/CredentialManager/res/values-sq/strings.xml b/packages/CredentialManager/res/values-sq/strings.xml
index 8038cea..722bee6 100644
--- a/packages/CredentialManager/res/values-sq/strings.xml
+++ b/packages/CredentialManager/res/values-sq/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Të krijohet një çelës kalimi për t\'u identifikuar në <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Të ruhet fjalëkalimi për t\'u identifikuar në <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Të ruhen informacionet e identifikimit për <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Të përdoret kyçja e ekranit për të krijuar një çelës kalimi për <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Të përdoret kyçja e ekranit për të krijuar një fjalëkalim për <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Të përdoret kyçja e ekranit për të ruajtur informacionet e identifikimit për <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"çelësin e kalimit"</string>
<string name="password" msgid="6738570945182936667">"fjalëkalimi"</string>
<string name="passkeys" msgid="5733880786866559847">"çelësat e kalimit"</string>
diff --git a/packages/CredentialManager/res/values-sv/strings.xml b/packages/CredentialManager/res/values-sv/strings.xml
index 6379df2..331b124 100644
--- a/packages/CredentialManager/res/values-sv/strings.xml
+++ b/packages/CredentialManager/res/values-sv/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Vill du skapa en nyckel för att logga in i <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Vill du spara lösenordet för att logga in i <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Vill du spara inloggningsuppgifterna för <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Vill du använda skärmlåset för att skapa en nyckel för <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Vill du använda skärmlåset för att skapa ett lösenord för <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Vill du använda skärmlåset för att spara inloggningsuppgifter för <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"nyckel"</string>
<string name="password" msgid="6738570945182936667">"lösenord"</string>
<string name="passkeys" msgid="5733880786866559847">"nycklar"</string>
diff --git a/packages/CredentialManager/res/values-ta/strings.xml b/packages/CredentialManager/res/values-ta/strings.xml
index 008baab..ba1eb60 100644
--- a/packages/CredentialManager/res/values-ta/strings.xml
+++ b/packages/CredentialManager/res/values-ta/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸில் உள்நுழைய கடவுச்சாவியை உருவாக்கவா?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸில் உள்நுழைய கடவுச்சொல்லைச் சேமிக்கவா?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கான உள்நுழைவுத் தகவலைச் சேமிக்கவா?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கான கடவுச்சாவியை உருவாக்க உங்கள் திரைப் பூட்டைப் பயன்படுத்தவா?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கான கடவுச்சொல்லை உருவாக்க உங்கள் திரைப் பூட்டைப் பயன்படுத்தவா?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கான உள்நுழைவுத் தகவலைச் சேமிக்க உங்கள் திரைப் பூட்டைப் பயன்படுத்தவா?"</string>
<string name="passkey" msgid="632353688396759522">"கடவுச்சாவி"</string>
<string name="password" msgid="6738570945182936667">"கடவுச்சொல்"</string>
<string name="passkeys" msgid="5733880786866559847">"கடவுச்சாவிகள்"</string>
diff --git a/packages/CredentialManager/res/values-tr/strings.xml b/packages/CredentialManager/res/values-tr/strings.xml
index 96a1c008..b11ca07 100644
--- a/packages/CredentialManager/res/values-tr/strings.xml
+++ b/packages/CredentialManager/res/values-tr/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasında oturum açmak için geçiş anahtarı oluşturulsun mu?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasında oturum açmak için şifre kaydedilsin mi?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"<xliff:g id="APP_NAME">%1$s</xliff:g> için oturum açma bilgileri kaydedilsin mi?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"<xliff:g id="APP_NAME">%1$s</xliff:g> geçiş anahtarı oluşturmak için ekran kilidiniz kullanılsın mı?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"<xliff:g id="APP_NAME">%1$s</xliff:g> şifresi oluşturmak için ekran kilidiniz kullanılsın mı?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"<xliff:g id="APP_NAME">%1$s</xliff:g> oturum açma bilgilerini kaydetmek için ekran kilidiniz kullanılsın mı?"</string>
<string name="passkey" msgid="632353688396759522">"Geçiş anahtarı"</string>
<string name="password" msgid="6738570945182936667">"Şifre"</string>
<string name="passkeys" msgid="5733880786866559847">"Geçiş anahtarlarınızın"</string>
diff --git a/packages/CredentialManager/res/values-uk/strings.xml b/packages/CredentialManager/res/values-uk/strings.xml
index b867903..cbc67d9 100644
--- a/packages/CredentialManager/res/values-uk/strings.xml
+++ b/packages/CredentialManager/res/values-uk/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Створити ключ доступу для входу в додаток <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Зберегти пароль для входу в додаток <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Зберегти дані для входу для додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Використати спосіб розблокування екрана, щоб створити ключ доступу для додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Використати спосіб розблокування екрана, щоб створити пароль для додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Використати спосіб розблокування екрана, щоб зберегти дані для входу в додаток <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"ключ доступу"</string>
<string name="password" msgid="6738570945182936667">"пароль"</string>
<string name="passkeys" msgid="5733880786866559847">"ключі доступу"</string>
diff --git a/packages/CredentialManager/res/values-uz/strings.xml b/packages/CredentialManager/res/values-uz/strings.xml
index 796bd87..ae7f06e 100644
--- a/packages/CredentialManager/res/values-uz/strings.xml
+++ b/packages/CredentialManager/res/values-uz/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasiga kirish uchun kirish kaliti yaratilsinmi?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasiga kirish uchun parol saqlansinmi?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"<xliff:g id="APP_NAME">%1$s</xliff:g> uchun kirish maʼlumoti saqlansinmi?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasida kirish kaliti yaratish uchun ekranni qulflashdan foydalanilsinmi?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasida parol yaratish uchun ekranni qulflashdan foydalanilsinmi?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasiga kirish axborotlarini saqlash uchun ekranni qulflashdan foydalanilsinmi?"</string>
<string name="passkey" msgid="632353688396759522">"kalit"</string>
<string name="password" msgid="6738570945182936667">"parol"</string>
<string name="passkeys" msgid="5733880786866559847">"kalitlar"</string>
diff --git a/packages/CredentialManager/res/values-vi/strings.xml b/packages/CredentialManager/res/values-vi/strings.xml
index 59bd541..2b59857 100644
--- a/packages/CredentialManager/res/values-vi/strings.xml
+++ b/packages/CredentialManager/res/values-vi/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Tạo khoá truy cập để đăng nhập vào <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Lưu mật khẩu để đăng nhập vào <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Lưu thông tin đăng nhập cho <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Sử dụng phương thức khoá màn hình để tạo khoá truy cập cho ứng dụng <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Sử dụng phương thức khoá màn hình để tạo mật khẩu cho ứng dụng <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Sử dụng phương thức khoá màn hình để lưu thông tin đăng nhập cho ứng dụng <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"khoá đăng nhập"</string>
<string name="password" msgid="6738570945182936667">"mật khẩu"</string>
<string name="passkeys" msgid="5733880786866559847">"khoá truy cập"</string>
diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml
index e9ac45a..9b7ae0d 100644
--- a/packages/CredentialManager/res/values-zh-rCN/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"要创建通行密钥以便登录“<xliff:g id="APP_NAME">%1$s</xliff:g>”吗?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"要保存密码以便登录“<xliff:g id="APP_NAME">%1$s</xliff:g>”吗?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"要保存“<xliff:g id="APP_NAME">%1$s</xliff:g>”的登录信息吗?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"要使用屏锁为“<xliff:g id="APP_NAME">%1$s</xliff:g>”创建通行密钥?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"要使用屏锁为“<xliff:g id="APP_NAME">%1$s</xliff:g>”创建密码?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"要使用屏锁为“<xliff:g id="APP_NAME">%1$s</xliff:g>”保存登录信息?"</string>
<string name="passkey" msgid="632353688396759522">"通行密钥"</string>
<string name="password" msgid="6738570945182936667">"密码"</string>
<string name="passkeys" msgid="5733880786866559847">"通行密钥"</string>
diff --git a/packages/CredentialManager/res/values-zh-rHK/strings.xml b/packages/CredentialManager/res/values-zh-rHK/strings.xml
index 77855b5..4ff00c3 100644
--- a/packages/CredentialManager/res/values-zh-rHK/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rHK/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"要建立密鑰以登入 <xliff:g id="APP_NAME">%1$s</xliff:g> 嗎?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"要儲存密碼以登入 <xliff:g id="APP_NAME">%1$s</xliff:g> 嗎?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"要儲存 <xliff:g id="APP_NAME">%1$s</xliff:g> 的登入資料嗎?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"要使用螢幕鎖定方式建立「<xliff:g id="APP_NAME">%1$s</xliff:g>」的密鑰嗎?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"要使用螢幕鎖定方式建立「<xliff:g id="APP_NAME">%1$s</xliff:g>」的密碼嗎?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"要使用螢幕鎖定方式儲存「<xliff:g id="APP_NAME">%1$s</xliff:g>」的登入資料嗎?"</string>
<string name="passkey" msgid="632353688396759522">"密鑰"</string>
<string name="password" msgid="6738570945182936667">"密碼"</string>
<string name="passkeys" msgid="5733880786866559847">"密鑰"</string>
diff --git a/packages/CredentialManager/res/values-zh-rTW/strings.xml b/packages/CredentialManager/res/values-zh-rTW/strings.xml
index 520d9a8..c8bd87d 100644
--- a/packages/CredentialManager/res/values-zh-rTW/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rTW/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"要建立用於登入「<xliff:g id="APP_NAME">%1$s</xliff:g>」的密碼金鑰嗎?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"要儲存用於登入「<xliff:g id="APP_NAME">%1$s</xliff:g>」的密碼嗎?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"要儲存「<xliff:g id="APP_NAME">%1$s</xliff:g>」的登入資訊嗎?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"要使用螢幕鎖定建立「<xliff:g id="APP_NAME">%1$s</xliff:g>」的密碼金鑰嗎?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"要使用螢幕鎖定建立「<xliff:g id="APP_NAME">%1$s</xliff:g>」的密碼嗎?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"要使用螢幕鎖定儲存「<xliff:g id="APP_NAME">%1$s</xliff:g>」的登入資訊嗎?"</string>
<string name="passkey" msgid="632353688396759522">"密碼金鑰"</string>
<string name="password" msgid="6738570945182936667">"密碼"</string>
<string name="passkeys" msgid="5733880786866559847">"密碼金鑰"</string>
diff --git a/packages/CredentialManager/res/values-zu/strings.xml b/packages/CredentialManager/res/values-zu/strings.xml
index 8cb25cb..7e6300b5 100644
--- a/packages/CredentialManager/res/values-zu/strings.xml
+++ b/packages/CredentialManager/res/values-zu/strings.xml
@@ -42,12 +42,9 @@
<string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Sungula ukhiye wokudlula ukuze ungene ngemvume ku-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="4481366993598649224">"Londoloza iphasiwedi ukuze ungene ngemvume ku-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Londoloza ulwazi lokungena lwe-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for choose_create_single_tap_passkey_title (3872793514041774218) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_password_title (5231871886818921622) -->
- <skip />
- <!-- no translation found for choose_create_single_tap_sign_in_title (256498714574099587) -->
- <skip />
+ <string name="choose_create_single_tap_passkey_title" msgid="3872793514041774218">"Sebenzisa ukukhiya isikrini sakho ukuze usungule ukhiye wokudlula we-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_password_title" msgid="5231871886818921622">"Sebenzisa ukukhiya isikrini sakho ukuze usungule iphasiwedi ye-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_single_tap_sign_in_title" msgid="256498714574099587">"Sebenzisa ukukhiya isikrini sakho ukuze ulondoloze ulwazi lokungena ngemvume lwe-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"ukhiye wokudlula"</string>
<string name="password" msgid="6738570945182936667">"iphasiwedi"</string>
<string name="passkeys" msgid="5733880786866559847">"okhiye bokudlula"</string>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 9fd386f..46a5138 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -68,14 +68,6 @@
<string name="choose_create_option_password_title">Save password to sign in to <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string>
<!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] -->
<string name="choose_create_option_sign_in_title">Save sign-in info for <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string>
- <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create passkey flow. [CHAR LIMIT=200] -->
- <string name="choose_create_single_tap_passkey_title">Use your screen lock to create a passkey for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
- <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create password flow. [CHAR LIMIT=200] -->
- <string name="choose_create_single_tap_password_title">Use your screen lock to create a password for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
- <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create flow when the credential type is others. [CHAR LIMIT=200] -->
- <!-- TODO(b/326243891) : Confirm with team on dynamically setting this based on recent product and ux discussions (does not disrupt e2e) -->
- <string name="choose_create_single_tap_sign_in_title">Use your screen lock to save sign in info for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
- <!-- Types which are inserted as a placeholder as credentialTypes for other strings. [CHAR LIMIT=200] -->
<string name="passkey">passkey</string>
<string name="password">password</string>
<string name="passkeys">passkeys</string>
@@ -126,15 +118,15 @@
<!-- Strings for the get flow. -->
<!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
- <string name="get_dialog_title_use_passkey_for">Use your saved passkey for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
+ <string name="get_dialog_title_use_passkey_for">Use your saved passkey for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved password to sign in to the app. [CHAR LIMIT=200] -->
- <string name="get_dialog_title_use_password_for">Use your saved password for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
- <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the get flow. [CHAR LIMIT=200] -->
- <string name="get_dialog_title_single_tap_for">Use your screen lock to sign in to <xliff:g id="app_name" example="Shrine">%1$s</xliff:g> with <xliff:g id="username" example="[email protected]">%2$s</xliff:g></string>
+ <string name="get_dialog_title_use_password_for">Use your saved password for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user confirmation to use the single user credential (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] -->
- <string name="get_dialog_title_use_sign_in_for">Use your sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
+ <string name="get_dialog_title_use_sign_in_for">Use your account for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+ <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for passkey authentication. [CHAR LIMIT=200] -->
+ <string name="get_dialog_description_single_tap">Use your screen lock to sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with <xliff:g id="username" example="[email protected]">%2$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user confirmation to unlock / authenticate (e.g. via fingerprint, faceId, passcode etc.) so that we can retrieve their sign-in options. [CHAR LIMIT=200] -->
- <string name="get_dialog_title_unlock_options_for">Unlock sign-in options for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
+ <string name="get_dialog_title_unlock_options_for">Unlock sign-in options for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user to make a choice from multiple previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
<string name="get_dialog_title_choose_passkey_for">Choose a saved passkey for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user to make a choice from multiple previously saved passwords to sign in to the app. [CHAR LIMIT=200] -->
@@ -142,7 +134,7 @@
<!-- This appears as the title of the dialog asking for user to make a choice from multiple previously saved credentials to sign in to the app. [CHAR LIMIT=200] -->
<string name="get_dialog_title_choose_saved_sign_in_for">Choose a saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user to make a choice from various available user credentials (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] -->
- <string name="get_dialog_title_choose_sign_in_for">Choose a sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+ <string name="get_dialog_title_choose_sign_in_for">Choose an account for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user to make a choice from options of available user information (e.g. driver's license, vaccination status) to pass to the app. [CHAR LIMIT=200] -->
<string name="get_dialog_title_choose_option_for">Choose an option for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
<!-- This appears as the title of the dialog asking user to send a piece of user information (e.g. driver's license, vaccination status) to the app. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
index ab70394..694e27a 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
@@ -21,7 +21,6 @@
import android.content.Intent
import android.credentials.selection.BaseDialogResult
import android.credentials.selection.BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED
-import android.credentials.selection.Constants
import android.credentials.selection.ProviderPendingIntentResponse
import android.credentials.selection.UserSelectionDialogResult
import android.os.Bundle
@@ -117,21 +116,17 @@
sendCancellationCode(
cancelCode = cancelCode,
requestToken = token,
- resultReceiver = resultReceiver,
- finalResponseReceiver = finalResponseReceiver
+ resultReceiver = resultReceiver
)
}
private fun sendCancellationCode(
cancelCode: Int,
requestToken: IBinder?,
- resultReceiver: ResultReceiver?,
- finalResponseReceiver: ResultReceiver?
+ resultReceiver: ResultReceiver?
) {
if (requestToken != null && resultReceiver != null) {
- val resultData = Bundle().apply {
- putParcelable(Constants.EXTRA_FINAL_RESPONSE_RECEIVER, finalResponseReceiver)
- }
+ val resultData = Bundle()
BaseDialogResult.addToBundle(BaseDialogResult(requestToken), resultData)
resultReceiver.send(cancelCode, resultData)
}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
index 786c441..9242141 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
@@ -54,9 +54,3 @@
Constants.EXTRA_RESULT_RECEIVER,
ResultReceiver::class.java
)
-
-val Intent.finalResponseReceiver: ResultReceiver?
- get() = this.getParcelableExtra(
- Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
- ResultReceiver::class.java
- )
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
index 1683cc4..f1f1f7c 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
@@ -20,7 +20,6 @@
import android.content.Intent
import com.android.credentialmanager.ktx.getCredentialProviderDataList
import com.android.credentialmanager.ktx.requestInfo
-import com.android.credentialmanager.ktx.finalResponseReceiver
import com.android.credentialmanager.ktx.resultReceiver
import com.android.credentialmanager.ktx.toProviderList
import com.android.credentialmanager.model.Request
@@ -29,7 +28,6 @@
return Request.Get(
token = requestInfo?.token,
resultReceiver = resultReceiver,
- finalResponseReceiver = finalResponseReceiver,
providerInfos = getCredentialProviderDataList.toProviderList(context)
)
}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
index fd99275..cb335fc 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
@@ -25,8 +25,7 @@
*/
sealed class Request private constructor(
open val token: IBinder?,
- open val resultReceiver: ResultReceiver? = null,
- open val finalResponseReceiver: ResultReceiver? = null,
+ open val resultReceiver: ResultReceiver? = null
) {
/**
@@ -51,9 +50,8 @@
data class Get(
override val token: IBinder?,
override val resultReceiver: ResultReceiver?,
- override val finalResponseReceiver: ResultReceiver?,
val providerInfos: List<ProviderInfo>,
- ) : Request(token, resultReceiver, finalResponseReceiver)
+ ) : Request(token, resultReceiver)
/**
* Request to start the create credentials flow.
*/
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index b17a98b..3683235 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -58,7 +58,6 @@
private val providerEnabledList: List<ProviderData>
private val providerDisabledList: List<DisabledProviderData>?
val resultReceiver: ResultReceiver?
- val finalResponseReceiver: ResultReceiver?
var initialUiState: UiState
@@ -105,12 +104,6 @@
Constants.EXTRA_RESULT_RECEIVER,
ResultReceiver::class.java
)
-
- finalResponseReceiver = intent.getParcelableExtra(
- Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
- ResultReceiver::class.java
- )
-
isReqForAllOptions = requestInfo?.isShowAllOptionsRequested ?: false
val cancellationRequest = getCancelUiRequest(intent)
@@ -206,7 +199,7 @@
}
fun onCancel(cancelCode: Int) {
- sendCancellationCode(cancelCode, requestInfo?.token, resultReceiver, finalResponseReceiver)
+ sendCancellationCode(cancelCode, requestInfo?.token, resultReceiver)
}
fun onOptionSelected(
@@ -226,9 +219,6 @@
val resultDataBundle = Bundle()
UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultDataBundle)
- resultDataBundle.putParcelable(Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
- finalResponseReceiver)
-
resultReceiver?.send(
BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
resultDataBundle
@@ -296,13 +286,10 @@
fun sendCancellationCode(
cancelCode: Int,
requestToken: IBinder?,
- resultReceiver: ResultReceiver?,
- finalResponseReceiver: ResultReceiver?
+ resultReceiver: ResultReceiver?
) {
if (requestToken != null && resultReceiver != null) {
val resultData = Bundle()
- resultData.putParcelable(Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
- finalResponseReceiver)
BaseDialogResult.addToBundle(BaseDialogResult(requestToken), resultData)
resultReceiver.send(cancelCode, resultData)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index ec0da09..a2f55cd 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -208,18 +208,13 @@
android.credentials.selection.Constants.EXTRA_RESULT_RECEIVER,
ResultReceiver::class.java
)
- val finalResponseResultReceiver = intent.getParcelableExtra(
- android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
- ResultReceiver::class.java
- )
-
val requestInfo = intent.extras?.getParcelable(
RequestInfo.EXTRA_REQUEST_INFO,
RequestInfo::class.java
)
CredentialManagerRepo.sendCancellationCode(
BaseDialogResult.RESULT_CODE_DATA_PARSING_FAILURE,
- requestInfo?.token, resultReceiver, finalResponseResultReceiver
+ requestInfo?.token, resultReceiver
)
this.finish()
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 888777e..7bc3241 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -30,6 +30,7 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
+import com.android.credentialmanager.common.BiometricError
import com.android.credentialmanager.common.BiometricFlowType
import com.android.credentialmanager.common.BiometricPromptState
import com.android.credentialmanager.common.BiometricResult
@@ -128,13 +129,22 @@
uiState = uiState.copy(providerActivityState = ProviderActivityState.PENDING)
val entryIntent = entry.fillInIntent
entryIntent?.putExtra(Constants.IS_AUTO_SELECTED_KEY, uiState.isAutoSelectFlow)
- if (biometricState.biometricResult != null) {
+ if (biometricState.biometricResult != null || biometricState.biometricError != null) {
if (uiState.isAutoSelectFlow) {
Log.w(Constants.LOG_TAG, "Unexpected biometric result exists when " +
"autoSelect is preferred.")
}
- entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_TYPE,
- biometricState.biometricResult.biometricAuthenticationResult.authenticationType)
+ // TODO(b/333445754) : Decide whether to propagate info on prompt launch
+ if (biometricState.biometricResult != null) {
+ entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_RESULT,
+ biometricState.biometricResult.biometricAuthenticationResult
+ .authenticationType)
+ } else if (biometricState.biometricError != null){
+ entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_ERROR_CODE,
+ biometricState.biometricError.errorCode)
+ entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_ERROR_MESSAGE,
+ biometricState.biometricError.errorMessage)
+ }
}
val intentSenderRequest = IntentSenderRequest.Builder(pendingIntent)
.setFillInIntent(entryIntent).build()
@@ -219,7 +229,8 @@
/**************************************************************************/
fun getFlowOnEntrySelected(
entry: EntryInfo,
- authResult: BiometricPrompt.AuthenticationResult? = null
+ authResult: BiometricPrompt.AuthenticationResult? = null,
+ authError: BiometricError? = null,
) {
Log.d(Constants.LOG_TAG, "credential selected: {provider=${entry.providerId}" +
", key=${entry.entryKey}, subkey=${entry.entrySubkey}}")
@@ -227,10 +238,11 @@
uiState.copy(
selectedEntry = entry,
providerActivityState = ProviderActivityState.READY_TO_LAUNCH,
- biometricState = if (authResult == null) uiState.biometricState else uiState
+ biometricState = if (authResult == null && authError == null)
+ uiState.biometricState else if (authResult != null) uiState
.biometricState.copy(biometricResult = BiometricResult(
- biometricAuthenticationResult = authResult)
- )
+ biometricAuthenticationResult = authResult)) else uiState
+ .biometricState.copy(biometricError = authError)
)
} else {
credManRepo.onOptionSelected(entry.providerId, entry.entryKey, entry.entrySubkey)
@@ -258,6 +270,15 @@
)
}
+ fun getFlowOnMoreOptionOnlySelected() {
+ Log.d(Constants.LOG_TAG, "More Option Only selected")
+ uiState = uiState.copy(
+ getCredentialUiState = uiState.getCredentialUiState?.copy(
+ currentScreenState = GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY
+ )
+ )
+ }
+
fun getFlowOnMoreOptionOnSnackBarSelected(isNoAccount: Boolean) {
Log.d(Constants.LOG_TAG, "More Option on snackBar selected")
uiState = uiState.copy(
@@ -296,6 +317,14 @@
)
}
+ fun createFlowOnMoreOptionsOnlySelectedOnCreationSelection() {
+ uiState = uiState.copy(
+ createCredentialUiState = uiState.createCredentialUiState?.copy(
+ currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION_ONLY,
+ )
+ )
+ }
+
fun createFlowOnBackCreationSelectionButtonSelected() {
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
@@ -350,7 +379,8 @@
fun createFlowOnEntrySelected(
selectedEntry: EntryInfo,
- authResult: AuthenticationResult? = null
+ authResult: AuthenticationResult? = null,
+ authError: BiometricError? = null,
) {
val providerId = selectedEntry.providerId
val entryKey = selectedEntry.entryKey
@@ -362,9 +392,11 @@
uiState = uiState.copy(
selectedEntry = selectedEntry,
providerActivityState = ProviderActivityState.READY_TO_LAUNCH,
- biometricState = if (authResult == null) uiState.biometricState else uiState
+ biometricState = if (authResult == null && authError == null)
+ uiState.biometricState else if (authResult != null) uiState
.biometricState.copy(biometricResult = BiometricResult(
- biometricAuthenticationResult = authResult))
+ biometricAuthenticationResult = authResult)) else uiState
+ .biometricState.copy(biometricError = authError)
)
} else {
credManRepo.onOptionSelected(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 1253ce3..4109079 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -32,6 +32,7 @@
import android.os.Bundle
import android.os.CancellationSignal
import android.os.OutcomeReceiver
+import android.os.ResultReceiver
import android.service.autofill.AutofillService
import android.service.autofill.Dataset
import android.service.autofill.Field
@@ -109,17 +110,19 @@
}
val sessionId = clientState.getInt(SESSION_ID_KEY)
val requestId = clientState.getInt(REQUEST_ID_KEY)
+ val resultReceiver = clientState.getParcelable(
+ CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER, ResultReceiver::class.java)
Log.i(TAG, "Autofill sessionId: $sessionId, autofill requestId: $requestId")
- if (sessionId == 0 || requestId == 0) {
- Log.i(TAG, "Session Id or request Id not found")
- callback.onFailure("Session Id or request Id not found")
+ if (sessionId == 0 || requestId == 0 || resultReceiver == null) {
+ Log.i(TAG, "Session Id or request Id or resultReceiver not found")
+ callback.onFailure("Session Id or request Id or resultReceiver not found")
return
}
val responseClientState = Bundle()
responseClientState.putBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, false)
val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
- requestId, responseClientState)
+ requestId, resultReceiver, responseClientState)
// TODO(b/324635774): Use callback for validating. If the request is coming
// directly from the view, there should be a corresponding callback, otherwise
// we should fail fast,
@@ -531,6 +534,7 @@
structure: AssistStructure,
sessionId: Int,
requestId: Int,
+ resultReceiver: ResultReceiver,
responseClientState: Bundle
): GetCredentialRequest? {
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
@@ -540,6 +544,9 @@
val dataBundle = Bundle()
dataBundle.putInt(SESSION_ID_KEY, sessionId)
dataBundle.putInt(REQUEST_ID_KEY, requestId)
+ dataBundle.putParcelable(CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER,
+ resultReceiver)
+
return GetCredentialRequest.Builder(dataBundle)
.setCredentialOptions(credentialOptions)
.build()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index be3e043..0d19a45 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -17,9 +17,12 @@
package com.android.credentialmanager.common
import android.content.Context
+import android.content.DialogInterface
import android.graphics.Bitmap
import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.BiometricManager.Authenticators
import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
import android.os.CancellationSignal
import android.util.Log
import androidx.core.content.ContextCompat.getMainExecutor
@@ -31,7 +34,6 @@
import com.android.credentialmanager.getflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.generateDisplayTitleTextResCode
import com.android.credentialmanager.model.BiometricRequestInfo
-import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.model.creation.CreateOptionInfo
import com.android.credentialmanager.model.get.CredentialEntryInfo
@@ -43,19 +45,23 @@
* Namely, this adds the ability to encapsulate the [providerIcon], the providers icon, the
* [providerName], which represents the name of the provider, the [displayTitleText] which is
* the large text displaying the flow in progress, and the [descriptionForCredential], which
- * describes details of where the credential is being saved, and how.
- * (E.g. assume a hypothetical provider 'Any Provider' for *passkey* flows with [email protected]:
+ * describes details of where the credential is being saved, and how. [displaySubtitleText] is only expected
+ * to be used by the 'create' flow, optionally, and describes the saved name of the creating entity.
+ * (E.g. assume a hypothetical provider 'Any Provider' for *passkey* flows with [email protected] and
+ * name 'Your', and an rp called 'The App'):
*
* 'get' flow:
* - [providerIcon] and [providerName] = 'Any Provider' (and it's icon)
- * - [displayTitleText] = "Use your saved passkey for Any Provider?"
- * - [descriptionForCredential] = "Use your screen lock to sign in to Any Provider with
- * [email protected]"
+ * - [displayTitleText] = "Use your saved passkey for The App?"
+ * - [descriptionForCredential] = "Sign in to The App with your saved passkey for
+ * [email protected]"
*
* 'create' flow:
* - [providerIcon] and [providerName] = 'Any Provider' (and it's icon)
* - [displayTitleText] = "Create passkey to sign in to Any Provider?"
- * - [descriptionForCredential] = "Use your screen lock to create a passkey for Any Provider?"
+ * - [subtitle] = "Your"
+ * - [descriptionForCredential] = "You can use your passkey on other devices. It is saved to
+ * * Google Password Manager for [email protected]."
* ).
*
* The above are examples; the credential type can change depending on scenario.
@@ -65,8 +71,9 @@
val providerIcon: Bitmap,
val providerName: String,
val displayTitleText: String,
- val descriptionForCredential: String,
+ val descriptionForCredential: String?,
val biometricRequestInfo: BiometricRequestInfo,
+ val displaySubtitleText: CharSequence? = null,
)
/**
@@ -76,6 +83,7 @@
*/
data class BiometricState(
val biometricResult: BiometricResult? = null,
+ val biometricError: BiometricError? = null,
val biometricStatus: BiometricPromptState = BiometricPromptState.INACTIVE
)
@@ -84,7 +92,7 @@
* so that should this object exist, the result will be retrievable.
*/
data class BiometricResult(
- val biometricAuthenticationResult: BiometricPrompt.AuthenticationResult
+ val biometricAuthenticationResult: BiometricPrompt.AuthenticationResult,
)
/**
@@ -92,16 +100,7 @@
*/
data class BiometricError(
val errorCode: Int,
- val errString: CharSequence? = null
-)
-
-/**
- * Encapsulates the help callback results to easily manage biometric help states in the flow.
- * To specify, this allows us to parse the onAuthenticationHelp method in the [BiometricPrompt].
- */
-data class BiometricHelp(
- val helpCode: Int,
- var helpString: CharSequence? = null
+ val errorMessage: CharSequence? = null
)
/**
@@ -113,7 +112,7 @@
biometricEntry: EntryInfo,
context: Context,
openMoreOptionsPage: () -> Unit,
- sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+ sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult?, BiometricError?) -> Unit,
onCancelFlowAndFinish: () -> Unit,
onIllegalStateAndFinish: (String) -> Unit,
getBiometricPromptState: () -> BiometricPromptState,
@@ -146,7 +145,7 @@
Log.d(TAG, "The BiometricPrompt API call begins.")
runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
- onBiometricFailureFallback, BiometricFlowType.GET)
+ onBiometricFailureFallback, BiometricFlowType.GET, onCancelFlowAndFinish)
}
/**
@@ -158,7 +157,7 @@
biometricEntry: EntryInfo,
context: Context,
openMoreOptionsPage: () -> Unit,
- sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+ sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult?, BiometricError?) -> Unit,
onCancelFlowAndFinish: () -> Unit,
onIllegalStateAndFinish: (String) -> Unit,
getBiometricPromptState: () -> BiometricPromptState,
@@ -190,14 +189,15 @@
Log.d(TAG, "The BiometricPrompt API call begins.")
runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
- onBiometricFailureFallback, BiometricFlowType.CREATE)
+ onBiometricFailureFallback, BiometricFlowType.CREATE, onCancelFlowAndFinish)
}
/**
* This will handle the logic for integrating credential manager with the biometric prompt for the
* single account biometric experience. This simultaneously handles both the get and create flows,
* by retrieving all the data from credential manager, and properly parsing that data into the
- * biometric prompt.
+ * biometric prompt. It will fallback in cases where the biometric api cannot be called, or when
+ * only device credentials are requested.
*/
private fun runBiometricFlow(
context: Context,
@@ -205,28 +205,97 @@
callback: BiometricPrompt.AuthenticationCallback,
openMoreOptionsPage: () -> Unit,
onBiometricFailureFallback: (BiometricFlowType) -> Unit,
- biometricFlowType: BiometricFlowType
+ biometricFlowType: BiometricFlowType,
+ onCancelFlowAndFinish: () -> Unit
) {
- val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo, openMoreOptionsPage,
- biometricDisplayInfo.biometricRequestInfo, biometricFlowType)
-
- val cancellationSignal = CancellationSignal()
- cancellationSignal.setOnCancelListener {
- Log.d(TAG, "Your cancellation signal was called.")
- // TODO(b/333445112) : Migrate towards passing along the developer cancellation signal
- // or validate the necessity for this
- }
-
- val executor = getMainExecutor(context)
-
try {
- biometricPrompt.authenticate(cancellationSignal, executor, callback)
+ if (!canCallBiometricPrompt(biometricDisplayInfo, context)) {
+ onBiometricFailureFallback(biometricFlowType)
+ return
+ }
+
+ val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo,
+ openMoreOptionsPage, biometricDisplayInfo.biometricRequestInfo, onCancelFlowAndFinish)
+
+ val cancellationSignal = CancellationSignal()
+ cancellationSignal.setOnCancelListener {
+ Log.d(TAG, "Your cancellation signal was called.")
+ // TODO(b/333445112) : Migrate towards passing along the developer cancellation signal
+ // or validate the necessity for this
+ }
+
+ val executor = getMainExecutor(context)
+
+ val cryptoOpId = getCryptoOpId(biometricDisplayInfo)
+ if (cryptoOpId != null) {
+ biometricPrompt.authenticate(
+ BiometricPrompt.CryptoObject(cryptoOpId.toLong()),
+ cancellationSignal, executor, callback)
+ } else {
+ biometricPrompt.authenticate(cancellationSignal, executor, callback)
+ }
} catch (e: IllegalArgumentException) {
Log.w(TAG, "Calling the biometric prompt API failed with: /n${e.localizedMessage}\n")
onBiometricFailureFallback(biometricFlowType)
}
}
+private fun getCryptoOpId(biometricDisplayInfo: BiometricDisplayInfo): Int? {
+ return biometricDisplayInfo.biometricRequestInfo.opId
+}
+
+/**
+ * Determines if, given the allowed authenticators, the flow should fallback early. This has
+ * consistency because for biometrics to exist, **device credentials must exist**. Thus, fallbacks
+ * occur if *only* device credentials are available, to avoid going right into the PIN screen.
+ * Note that if device credential is the only available modality but not requested, or if none
+ * of the requested modalities are available, we fallback to the normal flow to ensure a selector
+ * shows up.
+ * // TODO(b/334197980) : While we already fallback in cases the selector doesn't show, confirm
+ * // final plan.
+ */
+private fun canCallBiometricPrompt(
+ biometricDisplayInfo: BiometricDisplayInfo,
+ context: Context
+): Boolean {
+ val allowedAuthenticators = biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators
+ if (allowedAuthenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) {
+ return false
+ }
+
+ val biometricManager = context.getSystemService(Context.BIOMETRIC_SERVICE) as BiometricManager
+
+ if (biometricManager.canAuthenticate(allowedAuthenticators) !=
+ BiometricManager.BIOMETRIC_SUCCESS) {
+ return false
+ }
+
+ if (ifOnlySupportsAtMostDeviceCredentials(biometricManager)) return false
+
+ return true
+}
+
+private fun ifOnlySupportsAtMostDeviceCredentials(biometricManager: BiometricManager): Boolean {
+ if (biometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK) !=
+ BiometricManager.BIOMETRIC_SUCCESS &&
+ biometricManager.canAuthenticate(Authenticators.BIOMETRIC_STRONG) !=
+ BiometricManager.BIOMETRIC_SUCCESS
+ ) {
+ return true
+ }
+ return false
+}
+
+private fun containsBiometricAuthenticatorWithDeviceCredentials(
+ allowedAuthenticators: Int
+): Boolean {
+ val allowedAuthContainsDeviceCredential = (allowedAuthenticators ==
+ Authenticators.BIOMETRIC_WEAK or Authenticators.DEVICE_CREDENTIAL) ||
+ (allowedAuthenticators ==
+ Authenticators.BIOMETRIC_STRONG or Authenticators.DEVICE_CREDENTIAL)
+ return allowedAuthContainsDeviceCredential
+}
+
/**
* Sets up the biometric prompt with the UI specific bits.
* // TODO(b/333445112) : Pass in opId once dependency is confirmed via CryptoObject
@@ -236,56 +305,41 @@
biometricDisplayInfo: BiometricDisplayInfo,
openMoreOptionsPage: () -> Unit,
biometricRequestInfo: BiometricRequestInfo,
- biometricFlowType: BiometricFlowType,
+ onCancelFlowAndFinish: () -> Unit
): BiometricPrompt {
- val finalAuthenticators = removeDeviceCredential(biometricRequestInfo.allowedAuthenticators)
+ val listener =
+ DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> openMoreOptionsPage() }
- val biometricPrompt = BiometricPrompt.Builder(context)
+ val promptContentViewBuilder = PromptContentViewWithMoreOptionsButton.Builder()
+ .setMoreOptionsButtonListener(context.mainExecutor, listener)
+ biometricDisplayInfo.descriptionForCredential?.let {
+ promptContentViewBuilder.setDescription(it) }
+
+ val biometricPromptBuilder = BiometricPrompt.Builder(context)
.setTitle(biometricDisplayInfo.displayTitleText)
- // TODO(b/333445112) : Migrate to using new methods and strings recently aligned upon
- .setNegativeButton(context.getString(if (biometricFlowType == BiometricFlowType.GET)
- R.string
- .dropdown_presentation_more_sign_in_options_text else R.string.string_more_options),
- getMainExecutor(context)) { _, _ ->
- openMoreOptionsPage()
- }
- .setAllowedAuthenticators(finalAuthenticators)
+ .setAllowedAuthenticators(biometricRequestInfo.allowedAuthenticators)
.setConfirmationRequired(true)
.setLogoBitmap(biometricDisplayInfo.providerIcon)
.setLogoDescription(biometricDisplayInfo.providerName)
- .setDescription(biometricDisplayInfo.descriptionForCredential)
- .build()
+ .setContentView(promptContentViewBuilder.build())
- return biometricPrompt
-}
-
-// TODO(b/333445112) : Remove after larger level alignments made on fallback negative button
-// For the time being, we do not support the pin fallback until UX is decided.
-private fun removeDeviceCredential(requestAllowedAuthenticators: Int): Int {
- var finalAuthenticators = requestAllowedAuthenticators
-
- if (requestAllowedAuthenticators == (BiometricManager.Authenticators.DEVICE_CREDENTIAL or
- BiometricManager.Authenticators.BIOMETRIC_WEAK)) {
- finalAuthenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
+ if (!containsBiometricAuthenticatorWithDeviceCredentials(biometricDisplayInfo
+ .biometricRequestInfo.allowedAuthenticators)) {
+ biometricPromptBuilder.setNegativeButton(context.getString(R.string.string_cancel),
+ getMainExecutor(context)
+ ) { _: DialogInterface?, _: Int -> onCancelFlowAndFinish() }
}
- if (requestAllowedAuthenticators == (BiometricManager.Authenticators.DEVICE_CREDENTIAL or
- BiometricManager.Authenticators.BIOMETRIC_STRONG)) {
- finalAuthenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
- }
+ biometricDisplayInfo.displaySubtitleText?.let { biometricPromptBuilder.setSubtitle(it) }
- if (requestAllowedAuthenticators == (BiometricManager.Authenticators.DEVICE_CREDENTIAL)) {
- finalAuthenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
- }
-
- return finalAuthenticators
+ return biometricPromptBuilder.build()
}
/**
* Sets up the biometric authentication callback.
*/
private fun setupBiometricAuthenticationCallback(
- sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+ sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult?, BiometricError?) -> Unit,
selectedEntry: EntryInfo,
onCancelFlowAndFinish: () -> Unit,
onIllegalStateAndFinish: (String) -> Unit,
@@ -301,7 +355,7 @@
try {
if (authResult != null) {
onBiometricPromptStateChange(BiometricPromptState.COMPLETE)
- sendDataToProvider(selectedEntry, authResult)
+ sendDataToProvider(selectedEntry, authResult, /*authError=*/null)
} else {
onIllegalStateAndFinish("The biometric flow succeeded but unexpectedly " +
"returned a null value.")
@@ -326,8 +380,10 @@
// into the selector, parity applies to the selector's cancellation instead
// of the provider's biometric prompt cancellation.
onCancelFlowAndFinish()
+ } else {
+ sendDataToProvider(selectedEntry, /*authResult=*/null, /*authError=*/
+ BiometricError(errorCode, errString))
}
- // TODO(b/333445772) : Propagate to provider
}
override fun onAuthenticationFailed() {
@@ -414,15 +470,20 @@
}
val singleEntryType = selectedEntry.credentialType
val username = selectedEntry.userName
+
+ // TODO(b/330396140) : Finalize localization and parsing for specific sign in option flows
+ // (fingerprint, face, etc...))
displayTitleText = context.getString(
generateDisplayTitleTextResCode(singleEntryType),
getRequestDisplayInfo.appName
)
+
descriptionText = context.getString(
- R.string.get_dialog_title_single_tap_for,
+ R.string.get_dialog_description_single_tap,
getRequestDisplayInfo.appName,
username
)
+
return BiometricDisplayInfo(providerIcon = icon, providerName = providerName,
displayTitleText = displayTitleText, descriptionForCredential = descriptionText,
biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo)
@@ -448,23 +509,12 @@
getCreateTitleResCode(createRequestDisplayInfo),
createRequestDisplayInfo.appName
)
- val descriptionText: String = context.getString(
- when (createRequestDisplayInfo.type) {
- CredentialType.PASSKEY ->
- R.string.choose_create_single_tap_passkey_title
- CredentialType.PASSWORD ->
- R.string.choose_create_single_tap_password_title
-
- CredentialType.UNKNOWN ->
- R.string.choose_create_single_tap_sign_in_title
- },
- createRequestDisplayInfo.appName,
- )
- // TODO(b/333445112) : Add a subtitle and any other recently aligned ideas
+ // TODO(b/330396140) : If footerDescription is null, determine if we need to fallback
return BiometricDisplayInfo(providerIcon = icon, providerName = providerName,
- displayTitleText = displayTitleText, descriptionForCredential = descriptionText,
- biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo)
+ displayTitleText = displayTitleText, descriptionForCredential = selectedEntry
+ .footerDescription, biometricRequestInfo = selectedEntry.biometricRequest
+ as BiometricRequestInfo, displaySubtitleText = createRequestDisplayInfo.title)
}
/**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
index 7e7a74f..3c80113 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
@@ -22,7 +22,9 @@
const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
"androidx.credentials.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED"
const val IS_AUTO_SELECTED_KEY = "IS_AUTO_SELECTED"
- const val BIOMETRIC_AUTH_TYPE = "BIOMETRIC_AUTH_TYPE"
- const val BIOMETRIC_AUTH_FAILURE = "BIOMETRIC_AUTH_FAILURE"
+ // TODO(b/333445772) : Qualify error codes fully for propagation
+ const val BIOMETRIC_AUTH_RESULT = "BIOMETRIC_AUTH_RESULT"
+ const val BIOMETRIC_AUTH_ERROR_CODE = "BIOMETRIC_AUTH_ERROR_CODE"
+ const val BIOMETRIC_AUTH_ERROR_MESSAGE = "BIOMETRIC_AUTH_ERROR_MESSAGE"
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index d13d86f..2c3c63b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -28,7 +28,6 @@
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -47,7 +46,6 @@
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.input.PasswordVisualTransformation
@@ -55,7 +53,6 @@
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.android.compose.theme.LocalAndroidColorScheme
-import com.android.credentialmanager.R
import com.android.credentialmanager.ui.theme.EntryShape
import com.android.credentialmanager.ui.theme.Shapes
@@ -321,6 +318,8 @@
fun MoreOptionTopAppBar(
text: String,
onNavigationIconClicked: () -> Unit,
+ navigationIcon: ImageVector,
+ navigationIconContentDescription: String,
bottomPadding: Dp,
) {
Row(
@@ -336,10 +335,8 @@
contentAlignment = Alignment.Center,
) {
Icon(
- imageVector = Icons.Filled.ArrowBack,
- contentDescription = stringResource(
- R.string.accessibility_back_arrow_button
- ),
+ imageVector = navigationIcon,
+ contentDescription = navigationIconContentDescription,
modifier = Modifier.size(24.dp).autoMirrored(),
tint = LocalAndroidColorScheme.current.onSurfaceVariant,
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 122b896..282a1b5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -32,6 +32,8 @@
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.NewReleases
import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.outlined.QrCodeScanner
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -46,6 +48,7 @@
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.R
+import com.android.credentialmanager.common.BiometricError
import com.android.credentialmanager.common.BiometricFlowType
import com.android.credentialmanager.common.BiometricPromptState
import com.android.credentialmanager.model.EntryInfo
@@ -106,7 +109,7 @@
onCancelFlowAndFinish = viewModel::onUserCancel,
onIllegalScreenStateAndFinish = viewModel::onIllegalUiState,
onMoreOptionSelected =
- viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
+ viewModel::createFlowOnMoreOptionsOnlySelectedOnCreationSelection,
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
enabledProviderInfo = createCredentialUiState
.activeEntry?.activeProvider!!,
@@ -119,6 +122,41 @@
onBiometricPromptStateChange =
viewModel::onBiometricPromptStateChange
)
+ CreateScreenState.MORE_OPTIONS_SELECTION_ONLY -> MoreOptionsSelectionCard(
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ enabledProviderList = createCredentialUiState.enabledProviders,
+ disabledProviderList = createCredentialUiState.disabledProviders,
+ sortedCreateOptionsPairs =
+ createCredentialUiState.sortedCreateOptionsPairs,
+ onBackCreationSelectionButtonSelected =
+ viewModel::createFlowOnBackCreationSelectionButtonSelected,
+ onOptionSelected =
+ viewModel::createFlowOnEntrySelectedFromMoreOptionScreen,
+ onDisabledProvidersSelected =
+ viewModel::createFlowOnLaunchSettings,
+ onRemoteEntrySelected = viewModel::createFlowOnEntrySelected,
+ onLog = { viewModel.logUiEvent(it) },
+ customTopAppBar = { MoreOptionTopAppBar(
+ text = stringResource(
+ R.string.save_credential_to_title,
+ when (createCredentialUiState.requestDisplayInfo
+ .type) {
+ CredentialType.PASSKEY ->
+ stringResource(R.string.passkey)
+ CredentialType.PASSWORD ->
+ stringResource(R.string.password)
+ CredentialType.UNKNOWN -> stringResource(
+ R.string.sign_in_info)
+ }
+ ),
+ onNavigationIconClicked = viewModel::onUserCancel,
+ bottomPadding = 16.dp,
+ navigationIcon = Icons.Filled.Close,
+ navigationIconContentDescription = stringResource(
+ R.string.accessibility_close_button
+ )
+ )}
+ )
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
enabledProviderList = createCredentialUiState.enabledProviders,
@@ -206,22 +244,31 @@
onDisabledProvidersSelected: () -> Unit,
onRemoteEntrySelected: (EntryInfo) -> Unit,
onLog: @Composable (UiEventEnum) -> Unit,
+ customTopAppBar: (@Composable() () -> Unit)? = null
) {
SheetContainerCard(topAppBar = {
- MoreOptionTopAppBar(
- text = stringResource(
- R.string.save_credential_to_title,
- when (requestDisplayInfo.type) {
- CredentialType.PASSKEY ->
- stringResource(R.string.passkey)
- CredentialType.PASSWORD ->
- stringResource(R.string.password)
- CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
- }
- ),
- onNavigationIconClicked = onBackCreationSelectionButtonSelected,
- bottomPadding = 16.dp,
- )
+ if (customTopAppBar != null) {
+ customTopAppBar()
+ } else {
+ MoreOptionTopAppBar(
+ text = stringResource(
+ R.string.save_credential_to_title,
+ when (requestDisplayInfo.type) {
+ CredentialType.PASSKEY ->
+ stringResource(R.string.passkey)
+ CredentialType.PASSWORD ->
+ stringResource(R.string.password)
+ CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
+ }
+ ),
+ onNavigationIconClicked = onBackCreationSelectionButtonSelected,
+ bottomPadding = 16.dp,
+ navigationIcon = Icons.Filled.ArrowBack,
+ navigationIconContentDescription = stringResource(
+ R.string.accessibility_back_arrow_button
+ )
+ )
+ }
}) {
// bottom padding already
item {
@@ -581,7 +628,11 @@
onMoreOptionSelected: () -> Unit,
requestDisplayInfo: RequestDisplayInfo,
enabledProviderInfo: EnabledProviderInfo,
- onBiometricEntrySelected: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+ onBiometricEntrySelected: (
+ EntryInfo,
+ BiometricPrompt.AuthenticationResult?,
+ BiometricError?
+ ) -> Unit,
onCancelFlowAndFinish: () -> Unit,
onIllegalScreenStateAndFinish: (String) -> Unit,
fallbackToOriginalFlow: (BiometricFlowType) -> Unit,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index ddd4139..130937c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -181,4 +181,5 @@
MORE_OPTIONS_SELECTION,
DEFAULT_PROVIDER_CONFIRMATION,
EXTERNAL_ONLY_SELECTION,
+ MORE_OPTIONS_SELECTION_ONLY,
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 72b7814..c98bb5e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -32,6 +32,8 @@
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.outlined.QrCodeScanner
import androidx.compose.material3.Divider
import androidx.compose.material3.TextButton
@@ -52,6 +54,7 @@
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.R
+import com.android.credentialmanager.common.BiometricError
import com.android.credentialmanager.common.BiometricFlowType
import com.android.credentialmanager.common.BiometricPromptState
import com.android.credentialmanager.common.ProviderActivityState
@@ -147,7 +150,7 @@
.currentScreenState == GetScreenState.BIOMETRIC_SELECTION) {
BiometricSelectionPage(
biometricEntry = getCredentialUiState.activeEntry,
- onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected,
+ onMoreOptionSelected = viewModel::getFlowOnMoreOptionOnlySelected,
onCancelFlowAndFinish = viewModel::onUserCancel,
onIllegalStateAndFinish = viewModel::onIllegalUiState,
requestDisplayInfo = getCredentialUiState.requestDisplayInfo,
@@ -162,6 +165,28 @@
onBiometricPromptStateChange =
viewModel::onBiometricPromptStateChange
)
+ } else if (credmanBiometricApiEnabled() &&
+ getCredentialUiState.currentScreenState
+ == GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY) {
+ AllSignInOptionCard(
+ providerInfoList = getCredentialUiState.providerInfoList,
+ providerDisplayInfo = getCredentialUiState.providerDisplayInfo,
+ onEntrySelected = viewModel::getFlowOnEntrySelected,
+ onBackButtonClicked = viewModel::onUserCancel,
+ onCancel = viewModel::onUserCancel,
+ onLog = { viewModel.logUiEvent(it) },
+ customTopBar = { MoreOptionTopAppBar(
+ text = stringResource(
+ R.string.get_dialog_title_sign_in_options),
+ onNavigationIconClicked = viewModel::onUserCancel,
+ navigationIcon = Icons.Filled.Close,
+ navigationIconContentDescription =
+ stringResource(R.string.accessibility_close_button),
+ bottomPadding = 0.dp
+ ) }
+ )
+ viewModel.uiMetrics.log(GetCredentialEvent
+ .CREDMAN_GET_CRED_SCREEN_ALL_SIGN_IN_OPTIONS)
} else {
AllSignInOptionCard(
providerInfoList = getCredentialUiState.providerInfoList,
@@ -223,7 +248,11 @@
requestDisplayInfo: RequestDisplayInfo,
providerInfoList: List<ProviderInfo>,
providerDisplayInfo: ProviderDisplayInfo,
- onBiometricEntrySelected: (EntryInfo, BiometricPrompt.AuthenticationResult?) -> Unit,
+ onBiometricEntrySelected: (
+ EntryInfo,
+ BiometricPrompt.AuthenticationResult?,
+ BiometricError?
+ ) -> Unit,
fallbackToOriginalFlow: (BiometricFlowType) -> Unit,
getBiometricPromptState: () -> BiometricPromptState,
onBiometricPromptStateChange: (BiometricPromptState) -> Unit,
@@ -520,19 +549,8 @@
R.string.get_dialog_title_choose_password_for
else if (areAllPasskeysOnPrimaryScreen)
R.string.get_dialog_title_choose_passkey_for
- else if (primaryPageLockedEntryList.isNotEmpty() ||
- primaryPageCredentialEntryList.any {
- it.sortedCredentialEntryList.first().credentialType !=
- CredentialType.PASSWORD &&
- it.sortedCredentialEntryList.first().credentialType !=
- CredentialType.PASSKEY
- }
- ) // An unknown typed / locked entry exists, and we can't say it is
- // already saved, strictly speaking. Hence use a different title
- // without the mention of "saved".
+ else
R.string.get_dialog_title_choose_sign_in_for
- else // All entries on the primary screen are passkeys or passwords
- R.string.get_dialog_title_choose_saved_sign_in_for
},
requestDisplayInfo.appName
),
@@ -637,7 +655,13 @@
return providerId
}
-/** Draws the secondary credential selection page, where all sign-in options are listed. */
+/**
+ * Draws the secondary credential selection page, where all sign-in options are listed.
+ *
+ * By default, this card has 'back' navigation whereby user can navigate back to invoke
+ * [onBackButtonClicked]. However if a different top bar with possibly a different navigation
+ * is required, then the caller of this Composable can set a [customTopBar].
+ */
@Composable
fun AllSignInOptionCard(
providerInfoList: List<ProviderInfo>,
@@ -646,16 +670,24 @@
onBackButtonClicked: () -> Unit,
onCancel: () -> Unit,
onLog: @Composable (UiEventEnum) -> Unit,
+ customTopBar: (@Composable() () -> Unit)? = null
) {
val sortedUserNameToCredentialEntryList =
providerDisplayInfo.sortedUserNameToCredentialEntryList
val authenticationEntryList = providerDisplayInfo.authenticationEntryList
SheetContainerCard(topAppBar = {
- MoreOptionTopAppBar(
- text = stringResource(R.string.get_dialog_title_sign_in_options),
- onNavigationIconClicked = onBackButtonClicked,
- bottomPadding = 0.dp,
- )
+ if (customTopBar != null) {
+ customTopBar()
+ } else {
+ MoreOptionTopAppBar(
+ text = stringResource(R.string.get_dialog_title_sign_in_options),
+ onNavigationIconClicked = onBackButtonClicked,
+ bottomPadding = 0.dp,
+ navigationIcon = Icons.Filled.ArrowBack,
+ navigationIconContentDescription = stringResource(
+ R.string.accessibility_back_arrow_button
+ ))
+ }
}) {
var isFirstSection = true
// For username
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index b03407b..8e78861 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -163,7 +163,11 @@
/** The single tap biometric selection page. */
BIOMETRIC_SELECTION,
- /** The secondary credential selection page, where all sign-in options are listed. */
+ /**
+ * The secondary credential selection page, where all sign-in options are listed.
+ *
+ * This state is expected to go back to PRIMARY_SELECTION on back navigation
+ */
ALL_SIGN_IN_OPTIONS,
/** The snackbar only page when there's no account but only a remoteEntry. */
@@ -171,6 +175,14 @@
/** The snackbar when there are only auth entries and all of them turn out to be empty. */
UNLOCKED_AUTH_ENTRIES_ONLY,
+
+ /**
+ * The secondary credential selection page, where all sign-in options are listed.
+ *
+ * This state has no option for the user to navigate back to PRIMARY_SELECTION, and
+ * instead can be terminated independently.
+ */
+ ALL_SIGN_IN_OPTIONS_ONLY,
}
@@ -285,7 +297,7 @@
providerDisplayInfo.remoteEntry != null)
GetScreenState.REMOTE_ONLY
else if (isRequestForAllOptions)
- GetScreenState.ALL_SIGN_IN_OPTIONS
+ GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY
else if (isBiometricFlow(providerDisplayInfo, isFlowAutoSelectable(providerDisplayInfo)))
GetScreenState.BIOMETRIC_SELECTION
else GetScreenState.PRIMARY_SELECTION
diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
index 6c14563..c6013e2 100644
--- a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
+++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
@@ -100,7 +100,6 @@
val getCredentialUiState = Request.Get(
token = null,
resultReceiver = null,
- finalResponseReceiver = null,
providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = true)
assertThat(getCredentialUiState).isEqualTo(
@@ -113,7 +112,6 @@
val getCredentialUiState = Request.Get(
token = null,
resultReceiver = null,
- finalResponseReceiver = null,
providerInfos = listOf(createProviderInfo(listOf(passkeyCredentialEntryInfo,
unknownCredentialEntryInfo)))).toGet(isPrimary = true)
@@ -133,7 +131,6 @@
val getCredentialUiState = Request.Get(
token = null,
resultReceiver = null,
- finalResponseReceiver = null,
providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = false)
assertThat(getCredentialUiState).isEqualTo(
@@ -152,7 +149,6 @@
val getCredentialUiState = Request.Get(
token = null,
resultReceiver = null,
- finalResponseReceiver = null,
providerInfos = listOf(createProviderInfo(credentialList1),
createProviderInfo(credentialList2))).toGet(isPrimary = false)
diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
index b79f34c..cf839f8 100644
--- a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
+++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
@@ -121,7 +121,6 @@
stateFlow.value = Request.Get(
token = null,
resultReceiver = null,
- finalResponseReceiver = null,
providerInfos = emptyList())
mViewModel.back()
@@ -136,7 +135,6 @@
stateFlow.value = Request.Get(
token = null,
resultReceiver = null,
- finalResponseReceiver = null,
providerInfos = emptyList())
mViewModel.back()
diff --git a/packages/InputDevices/res/values-en-rCA/strings.xml b/packages/InputDevices/res/values-en-rCA/strings.xml
index aa4614b..ac8ad0a 100644
--- a/packages/InputDevices/res/values-en-rCA/strings.xml
+++ b/packages/InputDevices/res/values-en-rCA/strings.xml
@@ -50,6 +50,5 @@
<string name="keyboard_layout_belarusian" msgid="7619281752698687588">"Belarusian"</string>
<string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolian"</string>
<string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string>
- <!-- no translation found for keyboard_layout_thai_kedmanee (6637147314580760938) -->
- <skip />
+ <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string>
</resources>
diff --git a/packages/InputDevices/res/values-en-rXC/strings.xml b/packages/InputDevices/res/values-en-rXC/strings.xml
index 58dbc43..159b0e0f 100644
--- a/packages/InputDevices/res/values-en-rXC/strings.xml
+++ b/packages/InputDevices/res/values-en-rXC/strings.xml
@@ -50,6 +50,5 @@
<string name="keyboard_layout_belarusian" msgid="7619281752698687588">"Belarusian"</string>
<string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolian"</string>
<string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string>
- <!-- no translation found for keyboard_layout_thai_kedmanee (6637147314580760938) -->
- <skip />
+ <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string>
</resources>
diff --git a/packages/InputDevices/res/values-pl/strings.xml b/packages/InputDevices/res/values-pl/strings.xml
index 232a505..31a3dac 100644
--- a/packages/InputDevices/res/values-pl/strings.xml
+++ b/packages/InputDevices/res/values-pl/strings.xml
@@ -50,6 +50,5 @@
<string name="keyboard_layout_belarusian" msgid="7619281752698687588">"białoruski"</string>
<string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongolski"</string>
<string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruziński"</string>
- <!-- no translation found for keyboard_layout_thai_kedmanee (6637147314580760938) -->
- <skip />
+ <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"tajski (Kedmanee)"</string>
</resources>
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index bd84b58..79c810c 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -46,6 +46,7 @@
sdk_version: "system_current",
rename_resources_package: false,
static_libs: [
+ "xz-java",
"androidx.leanback_leanback",
"androidx.annotation_annotation",
"androidx.fragment_fragment",
@@ -78,6 +79,7 @@
overrides: ["PackageInstaller"],
static_libs: [
+ "xz-java",
"androidx.leanback_leanback",
"androidx.fragment_fragment",
"androidx.lifecycle_lifecycle-livedata",
@@ -110,6 +112,7 @@
overrides: ["PackageInstaller"],
static_libs: [
+ "xz-java",
"androidx.leanback_leanback",
"androidx.annotation_annotation",
"androidx.fragment_fragment",
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 05f4d69..bf69d3b 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -146,6 +146,17 @@
android:configChanges="mnc|mnc|touchscreen|navigation|screenLayout|screenSize|smallestScreenSize|orientation|locale|keyboard|keyboardHidden|fontScale|uiMode|layoutDirection|density"
android:exported="false" />
+ <!-- Wearable Components -->
+ <service android:name=".wear.WearPackageInstallerService"
+ android:permission="com.google.android.permission.INSTALL_WEARABLE_PACKAGES"
+ android:foregroundServiceType="systemExempted"
+ android:exported="true"/>
+
+ <provider android:name=".wear.WearPackageIconProvider"
+ android:authorities="com.google.android.packageinstaller.wear.provider"
+ android:grantUriPermissions="true"
+ android:exported="false" />
+
<receiver android:name="androidx.profileinstaller.ProfileInstallReceiver"
tools:node="remove" />
diff --git a/packages/PackageInstaller/res/values-af/strings.xml b/packages/PackageInstaller/res/values-af/strings.xml
index 140fa36..13661e3 100644
--- a/packages/PackageInstaller/res/values-af/strings.xml
+++ b/packages/PackageInstaller/res/values-af/strings.xml
@@ -58,7 +58,7 @@
<string name="uninstall_application_title" msgid="4045420072401428123">"Deïnstalleer program"</string>
<string name="uninstall_update_title" msgid="824411791011583031">"Deïnstalleer opdatering"</string>
<string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> is deel van die volgende program:"</string>
- <string name="uninstall_application_text" msgid="3816830743706143980">"Wil jy hierdie program deïnstalleer?"</string>
+ <string name="uninstall_application_text" msgid="3816830743706143980">"Wil jy hierdie app deïnstalleer?"</string>
<string name="archive_application_text" msgid="8482325710714386348">"Jou persoonlike data sal gestoor word"</string>
<string name="archive_application_text_all_users" msgid="3151229641681672580">"Argiveer hierdie app vir alle gebruikers? Jou persoonlike data sal gestoor word"</string>
<string name="archive_application_text_current_user_work_profile" msgid="1450487362134779752">"Argiveer hierdie app op jou werkprofiel? Jou persoonlike data sal gestoor word"</string>
diff --git a/packages/PackageInstaller/res/values-gu/strings.xml b/packages/PackageInstaller/res/values-gu/strings.xml
index f642e145..82d5414 100644
--- a/packages/PackageInstaller/res/values-gu/strings.xml
+++ b/packages/PackageInstaller/res/values-gu/strings.xml
@@ -58,7 +58,7 @@
<string name="uninstall_application_title" msgid="4045420072401428123">"ઍપ્લિકેશન અનઇન્સ્ટૉલ કરો"</string>
<string name="uninstall_update_title" msgid="824411791011583031">"અપડેટ અનઇન્સ્ટૉલ કરો"</string>
<string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g>, નીચેની ઍપ્લિકેશનનો ભાગ છે:"</string>
- <string name="uninstall_application_text" msgid="3816830743706143980">"શું તમે આ ઍપને અનઇન્સ્ટૉલ કરવા માંગો છો?"</string>
+ <string name="uninstall_application_text" msgid="3816830743706143980">"શું તમે આ ઍપને અનઇન્સ્ટૉલ કરવા માગો છો?"</string>
<string name="archive_application_text" msgid="8482325710714386348">"તમારો વ્યક્તિગત ડેટા સાચવવામાં આવશે"</string>
<string name="archive_application_text_all_users" msgid="3151229641681672580">"શું આ ઍપને તમામ વપરાશકર્તાઓ માટે આર્કાઇવ કરીએ? તમારો વ્યક્તિગત ડેટા સાચવવામાં આવશે"</string>
<string name="archive_application_text_current_user_work_profile" msgid="1450487362134779752">"શું આ ઍપને તમારી ઑફિસની પ્રોફાઇલ પર આર્કાઇવ કરીએ? તમારો વ્યક્તિગત ડેટા સાચવવામાં આવશે"</string>
diff --git a/packages/PackageInstaller/res/values-hy/strings.xml b/packages/PackageInstaller/res/values-hy/strings.xml
index f2bc41e..d9fb570 100644
--- a/packages/PackageInstaller/res/values-hy/strings.xml
+++ b/packages/PackageInstaller/res/values-hy/strings.xml
@@ -58,7 +58,7 @@
<string name="uninstall_application_title" msgid="4045420072401428123">"Հավելվածի ապատեղադրում"</string>
<string name="uninstall_update_title" msgid="824411791011583031">"Ապատեղադրել թարմացումը"</string>
<string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> գործողությունը հետևյալ հավելվածի մասն է`"</string>
- <string name="uninstall_application_text" msgid="3816830743706143980">"Ուզո՞ւմ եք ապատեղադրել այս հավելվածը։"</string>
+ <string name="uninstall_application_text" msgid="3816830743706143980">"Ուզում եք ապատեղադրե՞լ այս հավելվածը։"</string>
<string name="archive_application_text" msgid="8482325710714386348">"Ձեր անձնական տվյալները կպահվեն"</string>
<string name="archive_application_text_all_users" msgid="3151229641681672580">"Արխիվացնե՞լ այս հավելվածը բոլոր օգտատերերի համար։ Ձեր անձնական տվյալները կպահվեն"</string>
<string name="archive_application_text_current_user_work_profile" msgid="1450487362134779752">"Արխիվացնե՞լ այս հավելվածը ձեր աշխատանքային պրոֆիլում։ Ձեր անձնական տվյալները կպահվեն"</string>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
new file mode 100644
index 0000000..53a460d
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
@@ -0,0 +1,173 @@
+/*
+ * 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.packageinstaller.wear;
+
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Task that installs an APK. This must not be called on the main thread.
+ * This code is based off the Finsky/Wearsky implementation
+ */
+public class InstallTask {
+ private static final String TAG = "InstallTask";
+
+ private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+ private final Context mContext;
+ private String mPackageName;
+ private ParcelFileDescriptor mParcelFileDescriptor;
+ private PackageInstallerImpl.InstallListener mCallback;
+ private PackageInstaller.Session mSession;
+ private IntentSender mCommitCallback;
+
+ private Exception mException = null;
+ private int mErrorCode = 0;
+ private String mErrorDesc = null;
+
+ public InstallTask(Context context, String packageName,
+ ParcelFileDescriptor parcelFileDescriptor,
+ PackageInstallerImpl.InstallListener callback, PackageInstaller.Session session,
+ IntentSender commitCallback) {
+ mContext = context;
+ mPackageName = packageName;
+ mParcelFileDescriptor = parcelFileDescriptor;
+ mCallback = callback;
+ mSession = session;
+ mCommitCallback = commitCallback;
+ }
+
+ public boolean isError() {
+ return mErrorCode != InstallerConstants.STATUS_SUCCESS || !TextUtils.isEmpty(mErrorDesc);
+ }
+
+ public void execute() {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ throw new IllegalStateException("This method cannot be called from the UI thread.");
+ }
+
+ OutputStream sessionStream = null;
+ try {
+ sessionStream = mSession.openWrite(mPackageName, 0, -1);
+
+ // 2b: Stream the asset to the installer. Note:
+ // Note: writeToOutputStreamFromAsset() always safely closes the input stream
+ writeToOutputStreamFromAsset(sessionStream);
+ mSession.fsync(sessionStream);
+ } catch (Exception e) {
+ mException = e;
+ mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM;
+ mErrorDesc = "Could not write to stream";
+ } finally {
+ if (sessionStream != null) {
+ // 2c: close output stream
+ try {
+ sessionStream.close();
+ } catch (Exception e) {
+ // Ignore otherwise
+ if (mException == null) {
+ mException = e;
+ mErrorCode = InstallerConstants.ERROR_INSTALL_CLOSE_STREAM;
+ mErrorDesc = "Could not close session stream";
+ }
+ }
+ }
+ }
+
+ if (mErrorCode != InstallerConstants.STATUS_SUCCESS) {
+ // An error occurred, we're done
+ Log.e(TAG, "Exception while installing " + mPackageName + ": " + mErrorCode + ", "
+ + mErrorDesc + ", " + mException);
+ mSession.close();
+ mCallback.installFailed(mErrorCode, "[" + mPackageName + "]" + mErrorDesc);
+ } else {
+ // 3. Commit the session (this actually installs it.) Session map
+ // will be cleaned up in the callback.
+ mCallback.installBeginning();
+ mSession.commit(mCommitCallback);
+ mSession.close();
+ }
+ }
+
+ /**
+ * {@code PackageInstaller} works with streams. Get the {@code FileDescriptor}
+ * corresponding to the {@code Asset} and then write the contents into an
+ * {@code OutputStream} that is passed in.
+ * <br>
+ * The {@code FileDescriptor} is closed but the {@code OutputStream} is not closed.
+ */
+ private boolean writeToOutputStreamFromAsset(OutputStream outputStream) {
+ if (outputStream == null) {
+ mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM_EXCEPTION;
+ mErrorDesc = "Got a null OutputStream.";
+ return false;
+ }
+
+ if (mParcelFileDescriptor == null || mParcelFileDescriptor.getFileDescriptor() == null) {
+ mErrorCode = InstallerConstants.ERROR_COULD_NOT_GET_FD;
+ mErrorDesc = "Could not get FD";
+ return false;
+ }
+
+ InputStream inputStream = null;
+ try {
+ byte[] inputBuf = new byte[DEFAULT_BUFFER_SIZE];
+ int bytesRead;
+ inputStream = new ParcelFileDescriptor.AutoCloseInputStream(mParcelFileDescriptor);
+
+ while ((bytesRead = inputStream.read(inputBuf)) > -1) {
+ if (bytesRead > 0) {
+ outputStream.write(inputBuf, 0, bytesRead);
+ }
+ }
+
+ outputStream.flush();
+ } catch (IOException e) {
+ mErrorCode = InstallerConstants.ERROR_INSTALL_APK_COPY_FAILURE;
+ mErrorDesc = "Reading from Asset FD or writing to temp file failed: " + e;
+ return false;
+ } finally {
+ safeClose(inputStream);
+ }
+
+ return true;
+ }
+
+ /**
+ * Quietly close a closeable resource (e.g. a stream or file). The input may already
+ * be closed and it may even be null.
+ */
+ public static void safeClose(Closeable resource) {
+ if (resource != null) {
+ try {
+ resource.close();
+ } catch (IOException ioe) {
+ // Catch and discard the error
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
new file mode 100644
index 0000000..3daf3d8
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
@@ -0,0 +1,59 @@
+/*
+ * 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.packageinstaller.wear;
+
+/**
+ * Constants for Installation / Uninstallation requests.
+ * Using the same values as Finsky/Wearsky code for consistency in user analytics of failures
+ */
+public class InstallerConstants {
+ /** Request succeeded */
+ public static final int STATUS_SUCCESS = 0;
+
+ /**
+ * The new PackageInstaller also returns a small set of less granular error codes, which
+ * we'll remap to the range -500 and below to keep away from existing installer codes
+ * (which run from -1 to -110).
+ */
+ public final static int ERROR_PACKAGEINSTALLER_BASE = -500;
+
+ public static final int ERROR_COULD_NOT_GET_FD = -603;
+ /** This node is not targeted by this request. */
+
+ /** The install did not complete because could not create PackageInstaller session */
+ public final static int ERROR_INSTALL_CREATE_SESSION = -612;
+ /** The install did not complete because could not open PackageInstaller session */
+ public final static int ERROR_INSTALL_OPEN_SESSION = -613;
+ /** The install did not complete because could not open PackageInstaller output stream */
+ public final static int ERROR_INSTALL_OPEN_STREAM = -614;
+ /** The install did not complete because of an exception while streaming bytes */
+ public final static int ERROR_INSTALL_COPY_STREAM_EXCEPTION = -615;
+ /** The install did not complete because of an unexpected exception from PackageInstaller */
+ public final static int ERROR_INSTALL_SESSION_EXCEPTION = -616;
+ /** The install did not complete because of an unexpected userActionRequired callback */
+ public final static int ERROR_INSTALL_USER_ACTION_REQUIRED = -617;
+ /** The install did not complete because of an unexpected broadcast (missing fields) */
+ public final static int ERROR_INSTALL_MALFORMED_BROADCAST = -618;
+ /** The install did not complete because of an error while copying from downloaded file */
+ public final static int ERROR_INSTALL_APK_COPY_FAILURE = -619;
+ /** The install did not complete because of an error while copying to the PackageInstaller
+ * output stream */
+ public final static int ERROR_INSTALL_COPY_STREAM = -620;
+ /** The install did not complete because of an error while closing the PackageInstaller
+ * output stream */
+ public final static int ERROR_INSTALL_CLOSE_STREAM = -621;
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
new file mode 100644
index 0000000..bdc22cf
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
@@ -0,0 +1,36 @@
+/*
+ * 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.packageinstaller.wear;
+
+import android.content.Context;
+
+/**
+ * Factory that creates a Package Installer.
+ */
+public class PackageInstallerFactory {
+ private static PackageInstallerImpl sPackageInstaller;
+
+ /**
+ * Return the PackageInstaller shared object. {@code init} should have already been called.
+ */
+ public synchronized static PackageInstallerImpl getPackageInstaller(Context context) {
+ if (sPackageInstaller == null) {
+ sPackageInstaller = new PackageInstallerImpl(context);
+ }
+ return sPackageInstaller;
+ }
+}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
new file mode 100644
index 0000000..1e37f15
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
@@ -0,0 +1,327 @@
+/*
+ * 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.packageinstaller.wear;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of package manager installation using modern PackageInstaller api.
+ *
+ * Heavily copied from Wearsky/Finsky implementation
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class PackageInstallerImpl {
+ private static final String TAG = "PackageInstallerImpl";
+
+ /** Intent actions used for broadcasts from PackageInstaller back to the local receiver */
+ private static final String ACTION_INSTALL_COMMIT =
+ "com.android.vending.INTENT_PACKAGE_INSTALL_COMMIT";
+
+ private final Context mContext;
+ private final PackageInstaller mPackageInstaller;
+ private final Map<String, PackageInstaller.SessionInfo> mSessionInfoMap;
+ private final Map<String, PackageInstaller.Session> mOpenSessionMap;
+
+ public PackageInstallerImpl(Context context) {
+ mContext = context.getApplicationContext();
+ mPackageInstaller = mContext.getPackageManager().getPackageInstaller();
+
+ // Capture a map of known sessions
+ // This list will be pruned a bit later (stale sessions will be canceled)
+ mSessionInfoMap = new HashMap<String, PackageInstaller.SessionInfo>();
+ List<PackageInstaller.SessionInfo> mySessions = mPackageInstaller.getMySessions();
+ for (int i = 0; i < mySessions.size(); i++) {
+ PackageInstaller.SessionInfo sessionInfo = mySessions.get(i);
+ String packageName = sessionInfo.getAppPackageName();
+ PackageInstaller.SessionInfo oldInfo = mSessionInfoMap.put(packageName, sessionInfo);
+
+ // Checking for old info is strictly for logging purposes
+ if (oldInfo != null) {
+ Log.w(TAG, "Multiple sessions for " + packageName + " found. Removing " + oldInfo
+ .getSessionId() + " & keeping " + mySessions.get(i).getSessionId());
+ }
+ }
+ mOpenSessionMap = new HashMap<String, PackageInstaller.Session>();
+ }
+
+ /**
+ * This callback will be made after an installation attempt succeeds or fails.
+ */
+ public interface InstallListener {
+ /**
+ * This callback signals that preflight checks have succeeded and installation
+ * is beginning.
+ */
+ void installBeginning();
+
+ /**
+ * This callback signals that installation has completed.
+ */
+ void installSucceeded();
+
+ /**
+ * This callback signals that installation has failed.
+ */
+ void installFailed(int errorCode, String errorDesc);
+ }
+
+ /**
+ * This is a placeholder implementation that bundles an entire "session" into a single
+ * call. This will be replaced by more granular versions that allow longer session lifetimes,
+ * download progress tracking, etc.
+ *
+ * This must not be called on main thread.
+ */
+ public void install(final String packageName, ParcelFileDescriptor parcelFileDescriptor,
+ final InstallListener callback) {
+ // 0. Generic try/catch block because I am not really sure what exceptions (other than
+ // IOException) might be thrown by PackageInstaller and I want to handle them
+ // at least slightly gracefully.
+ try {
+ // 1. Create or recover a session, and open it
+ // Try recovery first
+ PackageInstaller.Session session = null;
+ PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
+ if (sessionInfo != null) {
+ // See if it's openable, or already held open
+ session = getSession(packageName);
+ }
+ // If open failed, or there was no session, create a new one and open it.
+ // If we cannot create or open here, the failure is terminal.
+ if (session == null) {
+ try {
+ innerCreateSession(packageName);
+ } catch (IOException ioe) {
+ Log.e(TAG, "Can't create session for " + packageName + ": " + ioe.getMessage());
+ callback.installFailed(InstallerConstants.ERROR_INSTALL_CREATE_SESSION,
+ "Could not create session");
+ mSessionInfoMap.remove(packageName);
+ return;
+ }
+ sessionInfo = mSessionInfoMap.get(packageName);
+ try {
+ session = mPackageInstaller.openSession(sessionInfo.getSessionId());
+ mOpenSessionMap.put(packageName, session);
+ } catch (SecurityException se) {
+ Log.e(TAG, "Can't open session for " + packageName + ": " + se.getMessage());
+ callback.installFailed(InstallerConstants.ERROR_INSTALL_OPEN_SESSION,
+ "Can't open session");
+ mSessionInfoMap.remove(packageName);
+ return;
+ }
+ }
+
+ // 2. Launch task to handle file operations.
+ InstallTask task = new InstallTask( mContext, packageName, parcelFileDescriptor,
+ callback, session,
+ getCommitCallback(packageName, sessionInfo.getSessionId(), callback));
+ task.execute();
+ if (task.isError()) {
+ cancelSession(sessionInfo.getSessionId(), packageName);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Unexpected exception while installing: " + packageName + ": "
+ + e.getMessage());
+ callback.installFailed(InstallerConstants.ERROR_INSTALL_SESSION_EXCEPTION,
+ "Unexpected exception while installing " + packageName);
+ }
+ }
+
+ /**
+ * Retrieve an existing session. Will open if needed, but does not attempt to create.
+ */
+ private PackageInstaller.Session getSession(String packageName) {
+ // Check for already-open session
+ PackageInstaller.Session session = mOpenSessionMap.get(packageName);
+ if (session != null) {
+ try {
+ // Probe the session to ensure that it's still open. This may or may not
+ // throw (if non-open), but it may serve as a canary for stale sessions.
+ session.getNames();
+ return session;
+ } catch (IOException ioe) {
+ Log.e(TAG, "Stale open session for " + packageName + ": " + ioe.getMessage());
+ mOpenSessionMap.remove(packageName);
+ } catch (SecurityException se) {
+ Log.e(TAG, "Stale open session for " + packageName + ": " + se.getMessage());
+ mOpenSessionMap.remove(packageName);
+ }
+ }
+ // Check to see if this is a known session
+ PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
+ if (sessionInfo == null) {
+ return null;
+ }
+ // Try to open it. If we fail here, assume that the SessionInfo was stale.
+ try {
+ session = mPackageInstaller.openSession(sessionInfo.getSessionId());
+ } catch (SecurityException se) {
+ Log.w(TAG, "SessionInfo was stale for " + packageName + " - deleting info");
+ mSessionInfoMap.remove(packageName);
+ return null;
+ } catch (IOException ioe) {
+ Log.w(TAG, "IOException opening old session for " + ioe.getMessage()
+ + " - deleting info");
+ mSessionInfoMap.remove(packageName);
+ return null;
+ }
+ mOpenSessionMap.put(packageName, session);
+ return session;
+ }
+
+ /** This version throws an IOException when the session cannot be created */
+ private void innerCreateSession(String packageName) throws IOException {
+ if (mSessionInfoMap.containsKey(packageName)) {
+ Log.w(TAG, "Creating session for " + packageName + " when one already exists");
+ return;
+ }
+ PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ params.setAppPackageName(packageName);
+
+ // IOException may be thrown at this point
+ int sessionId = mPackageInstaller.createSession(params);
+ PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(sessionId);
+ mSessionInfoMap.put(packageName, sessionInfo);
+ }
+
+ /**
+ * Cancel a session based on its sessionId. Package name is for logging only.
+ */
+ private void cancelSession(int sessionId, String packageName) {
+ // Close if currently held open
+ closeSession(packageName);
+ // Remove local record
+ mSessionInfoMap.remove(packageName);
+ try {
+ mPackageInstaller.abandonSession(sessionId);
+ } catch (SecurityException se) {
+ // The session no longer exists, so we can exit quietly.
+ return;
+ }
+ }
+
+ /**
+ * Close a session if it happens to be held open.
+ */
+ private void closeSession(String packageName) {
+ PackageInstaller.Session session = mOpenSessionMap.remove(packageName);
+ if (session != null) {
+ // Unfortunately close() is not idempotent. Try our best to make this safe.
+ try {
+ session.close();
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error closing session for " + packageName + ": "
+ + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Creates a commit callback for the package install that's underway. This will be called
+ * some time after calling session.commit() (above).
+ */
+ private IntentSender getCommitCallback(final String packageName, final int sessionId,
+ final InstallListener callback) {
+ // Create a single-use broadcast receiver
+ BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mContext.unregisterReceiver(this);
+ handleCommitCallback(intent, packageName, sessionId, callback);
+ }
+ };
+ // Create a matching intent-filter and register the receiver
+ String action = ACTION_INSTALL_COMMIT + "." + packageName;
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(action);
+ mContext.registerReceiver(broadcastReceiver, intentFilter,
+ Context.RECEIVER_EXPORTED);
+
+ // Create a matching PendingIntent and use it to generate the IntentSender
+ Intent broadcastIntent = new Intent(action).setPackage(mContext.getPackageName());
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, packageName.hashCode(),
+ broadcastIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE);
+ return pendingIntent.getIntentSender();
+ }
+
+ /**
+ * Examine the extras to determine information about the package update/install, decode
+ * the result, and call the appropriate callback.
+ *
+ * @param intent The intent, which the PackageInstaller will have added Extras to
+ * @param packageName The package name we created the receiver for
+ * @param sessionId The session Id we created the receiver for
+ * @param callback The callback to report success/failure to
+ */
+ private void handleCommitCallback(Intent intent, String packageName, int sessionId,
+ InstallListener callback) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Installation of " + packageName + " finished with extras "
+ + intent.getExtras());
+ }
+ String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
+ int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, Integer.MIN_VALUE);
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ cancelSession(sessionId, packageName);
+ callback.installSucceeded();
+ } else if (status == -1 /*PackageInstaller.STATUS_USER_ACTION_REQUIRED*/) {
+ // TODO - use the constant when the correct/final name is in the SDK
+ // TODO This is unexpected, so we are treating as failure for now
+ cancelSession(sessionId, packageName);
+ callback.installFailed(InstallerConstants.ERROR_INSTALL_USER_ACTION_REQUIRED,
+ "Unexpected: user action required");
+ } else {
+ cancelSession(sessionId, packageName);
+ int errorCode = getPackageManagerErrorCode(status);
+ Log.e(TAG, "Error " + errorCode + " while installing " + packageName + ": "
+ + statusMessage);
+ callback.installFailed(errorCode, null);
+ }
+ }
+
+ private int getPackageManagerErrorCode(int status) {
+ // This is a hack: because PackageInstaller now reports error codes
+ // with small positive values, we need to remap them into a space
+ // that is more compatible with the existing package manager error codes.
+ // See https://sites.google.com/a/google.com/universal-store/documentation
+ // /android-client/download-error-codes
+ int errorCode;
+ if (status == Integer.MIN_VALUE) {
+ errorCode = InstallerConstants.ERROR_INSTALL_MALFORMED_BROADCAST;
+ } else {
+ errorCode = InstallerConstants.ERROR_PACKAGEINSTALLER_BASE - status;
+ }
+ return errorCode;
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
new file mode 100644
index 0000000..2c289b2
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.wear;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
+/**
+ * Installation Util that contains a list of parameters that are needed for
+ * installing/uninstalling.
+ */
+public class WearPackageArgs {
+ private static final String KEY_PACKAGE_NAME =
+ "com.google.android.clockwork.EXTRA_PACKAGE_NAME";
+ private static final String KEY_ASSET_URI =
+ "com.google.android.clockwork.EXTRA_ASSET_URI";
+ private static final String KEY_START_ID =
+ "com.google.android.clockwork.EXTRA_START_ID";
+ private static final String KEY_PERM_URI =
+ "com.google.android.clockwork.EXTRA_PERM_URI";
+ private static final String KEY_CHECK_PERMS =
+ "com.google.android.clockwork.EXTRA_CHECK_PERMS";
+ private static final String KEY_SKIP_IF_SAME_VERSION =
+ "com.google.android.clockwork.EXTRA_SKIP_IF_SAME_VERSION";
+ private static final String KEY_COMPRESSION_ALG =
+ "com.google.android.clockwork.EXTRA_KEY_COMPRESSION_ALG";
+ private static final String KEY_COMPANION_SDK_VERSION =
+ "com.google.android.clockwork.EXTRA_KEY_COMPANION_SDK_VERSION";
+ private static final String KEY_COMPANION_DEVICE_VERSION =
+ "com.google.android.clockwork.EXTRA_KEY_COMPANION_DEVICE_VERSION";
+ private static final String KEY_SHOULD_CHECK_GMS_DEPENDENCY =
+ "com.google.android.clockwork.EXTRA_KEY_SHOULD_CHECK_GMS_DEPENDENCY";
+ private static final String KEY_SKIP_IF_LOWER_VERSION =
+ "com.google.android.clockwork.EXTRA_SKIP_IF_LOWER_VERSION";
+
+ public static String getPackageName(Bundle b) {
+ return b.getString(KEY_PACKAGE_NAME);
+ }
+
+ public static Bundle setPackageName(Bundle b, String packageName) {
+ b.putString(KEY_PACKAGE_NAME, packageName);
+ return b;
+ }
+
+ public static Uri getAssetUri(Bundle b) {
+ return b.getParcelable(KEY_ASSET_URI);
+ }
+
+ public static Uri getPermUri(Bundle b) {
+ return b.getParcelable(KEY_PERM_URI);
+ }
+
+ public static boolean checkPerms(Bundle b) {
+ return b.getBoolean(KEY_CHECK_PERMS);
+ }
+
+ public static boolean skipIfSameVersion(Bundle b) {
+ return b.getBoolean(KEY_SKIP_IF_SAME_VERSION);
+ }
+
+ public static int getCompanionSdkVersion(Bundle b) {
+ return b.getInt(KEY_COMPANION_SDK_VERSION);
+ }
+
+ public static int getCompanionDeviceVersion(Bundle b) {
+ return b.getInt(KEY_COMPANION_DEVICE_VERSION);
+ }
+
+ public static String getCompressionAlg(Bundle b) {
+ return b.getString(KEY_COMPRESSION_ALG);
+ }
+
+ public static int getStartId(Bundle b) {
+ return b.getInt(KEY_START_ID);
+ }
+
+ public static boolean skipIfLowerVersion(Bundle b) {
+ return b.getBoolean(KEY_SKIP_IF_LOWER_VERSION, false);
+ }
+
+ public static Bundle setStartId(Bundle b, int startId) {
+ b.putInt(KEY_START_ID, startId);
+ return b;
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
new file mode 100644
index 0000000..02b9d29
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.wear;
+
+import android.annotation.TargetApi;
+import android.app.ActivityManager;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.List;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+public class WearPackageIconProvider extends ContentProvider {
+ private static final String TAG = "WearPackageIconProvider";
+ public static final String AUTHORITY = "com.google.android.packageinstaller.wear.provider";
+
+ private static final String REQUIRED_PERMISSION =
+ "com.google.android.permission.INSTALL_WEARABLE_PACKAGES";
+
+ /** MIME types. */
+ public static final String ICON_TYPE = "vnd.android.cursor.item/cw_package_icon";
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException("Query is not supported.");
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI passed in is null.");
+ }
+
+ if (AUTHORITY.equals(uri.getEncodedAuthority())) {
+ return ICON_TYPE;
+ }
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException("Insert is not supported.");
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI passed in is null.");
+ }
+
+ enforcePermissions(uri);
+
+ if (ICON_TYPE.equals(getType(uri))) {
+ final File file = WearPackageUtil.getIconFile(
+ this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
+ if (file != null) {
+ file.delete();
+ }
+ }
+
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("Update is not supported.");
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(
+ Uri uri, @SuppressWarnings("unused") String mode) throws FileNotFoundException {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI passed in is null.");
+ }
+
+ enforcePermissions(uri);
+
+ if (ICON_TYPE.equals(getType(uri))) {
+ final File file = WearPackageUtil.getIconFile(
+ this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
+ if (file != null) {
+ return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ }
+ }
+ return null;
+ }
+
+ public static Uri getUriForPackage(final String packageName) {
+ return Uri.parse("content://" + AUTHORITY + "/icons/" + packageName + ".icon");
+ }
+
+ private String getPackageNameFromUri(Uri uri) {
+ if (uri == null) {
+ return null;
+ }
+ List<String> pathSegments = uri.getPathSegments();
+ String packageName = pathSegments.get(pathSegments.size() - 1);
+
+ if (packageName.endsWith(".icon")) {
+ packageName = packageName.substring(0, packageName.lastIndexOf("."));
+ }
+ return packageName;
+ }
+
+ /**
+ * Make sure the calling app is either a system app or the same app or has the right permission.
+ * @throws SecurityException if the caller has insufficient permissions.
+ */
+ @TargetApi(Build.VERSION_CODES.BASE_1_1)
+ private void enforcePermissions(Uri uri) {
+ // Redo some of the permission check in {@link ContentProvider}. Just add an extra check to
+ // allow System process to access this provider.
+ Context context = getContext();
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final int myUid = android.os.Process.myUid();
+
+ if (uid == myUid || isSystemApp(context, pid)) {
+ return;
+ }
+
+ if (context.checkPermission(REQUIRED_PERMISSION, pid, uid) == PERMISSION_GRANTED) {
+ return;
+ }
+
+ // last chance, check against any uri grants
+ if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ == PERMISSION_GRANTED) {
+ return;
+ }
+
+ throw new SecurityException("Permission Denial: reading "
+ + getClass().getName() + " uri " + uri + " from pid=" + pid
+ + ", uid=" + uid);
+ }
+
+ /**
+ * From the pid of the calling process, figure out whether this is a system app or not. We do
+ * this by checking the application information corresponding to the pid and then checking if
+ * FLAG_SYSTEM is set.
+ */
+ @TargetApi(Build.VERSION_CODES.CUPCAKE)
+ private boolean isSystemApp(Context context, int pid) {
+ // Get the Activity Manager Object
+ ActivityManager aManager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ // Get the list of running Applications
+ List<ActivityManager.RunningAppProcessInfo> rapInfoList =
+ aManager.getRunningAppProcesses();
+ for (ActivityManager.RunningAppProcessInfo rapInfo : rapInfoList) {
+ if (rapInfo.pid == pid) {
+ try {
+ PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(
+ rapInfo.pkgList[0], 0);
+ if (pkgInfo != null && pkgInfo.applicationInfo != null &&
+ (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ Log.d(TAG, pid + " is a system app.");
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Could not find package information.", e);
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
new file mode 100644
index 0000000..ae0f4ec
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
@@ -0,0 +1,621 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.wear;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import androidx.annotation.Nullable;
+import com.android.packageinstaller.DeviceUtils;
+import com.android.packageinstaller.PackageUtil;
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.common.EventResultPersister;
+import com.android.packageinstaller.common.UninstallEventReceiver;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Service that will install/uninstall packages. It will check for permissions and features as well.
+ *
+ * -----------
+ *
+ * Debugging information:
+ *
+ * Install Action example:
+ * adb shell am startservice -a com.android.packageinstaller.wear.INSTALL_PACKAGE \
+ * -d package://com.google.android.gms \
+ * --eu com.google.android.clockwork.EXTRA_ASSET_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/wearable/com.google.android.gms/apk \
+ * --es android.intent.extra.INSTALLER_PACKAGE_NAME com.google.android.gms \
+ * --ez com.google.android.clockwork.EXTRA_CHECK_PERMS false \
+ * --eu com.google.android.clockwork.EXTRA_PERM_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/permissions \
+ * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ *
+ * Uninstall Action example:
+ * adb shell am startservice -a com.android.packageinstaller.wear.UNINSTALL_PACKAGE \
+ * -d package://com.google.android.gms \
+ * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ *
+ * Retry GMS:
+ * adb shell am startservice -a com.android.packageinstaller.wear.RETRY_GMS \
+ * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ */
+public class WearPackageInstallerService extends Service
+ implements EventResultPersister.EventResultObserver {
+ private static final String TAG = "WearPkgInstallerService";
+
+ private static final String WEAR_APPS_CHANNEL = "wear_app_install_uninstall";
+ private static final String BROADCAST_ACTION =
+ "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
+
+ private final int START_INSTALL = 1;
+ private final int START_UNINSTALL = 2;
+
+ private int mInstallNotificationId = 1;
+ private final Map<String, Integer> mNotifIdMap = new ArrayMap<>();
+ private final Map<Integer, UninstallParams> mServiceIdToParams = new HashMap<>();
+
+ private class UninstallParams {
+ public String mPackageName;
+ public PowerManager.WakeLock mLock;
+
+ UninstallParams(String packageName, PowerManager.WakeLock lock) {
+ mPackageName = packageName;
+ mLock = lock;
+ }
+ }
+
+ private final class ServiceHandler extends Handler {
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case START_INSTALL:
+ installPackage(msg.getData());
+ break;
+ case START_UNINSTALL:
+ uninstallPackage(msg.getData());
+ break;
+ }
+ }
+ }
+ private ServiceHandler mServiceHandler;
+ private NotificationChannel mNotificationChannel;
+ private static volatile PowerManager.WakeLock lockStatic = null;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ HandlerThread thread = new HandlerThread("PackageInstallerThread",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+
+ mServiceHandler = new ServiceHandler(thread.getLooper());
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (!DeviceUtils.isWear(this)) {
+ Log.w(TAG, "Not running on wearable.");
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+
+ if (intent == null) {
+ Log.w(TAG, "Got null intent.");
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Got install/uninstall request " + intent);
+ }
+
+ Uri packageUri = intent.getData();
+ if (packageUri == null) {
+ Log.e(TAG, "No package URI in intent");
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+
+ final String packageName = WearPackageUtil.getSanitizedPackageName(packageUri);
+ if (packageName == null) {
+ Log.e(TAG, "Invalid package name in URI (expected package:<pkgName>): " + packageUri);
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+
+ PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+ if (!lock.isHeld()) {
+ lock.acquire();
+ }
+
+ Bundle intentBundle = intent.getExtras();
+ if (intentBundle == null) {
+ intentBundle = new Bundle();
+ }
+ WearPackageArgs.setStartId(intentBundle, startId);
+ WearPackageArgs.setPackageName(intentBundle, packageName);
+ Message msg;
+ String notifTitle;
+ if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())) {
+ msg = mServiceHandler.obtainMessage(START_INSTALL);
+ notifTitle = getString(R.string.installing);
+ } else if (Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) {
+ msg = mServiceHandler.obtainMessage(START_UNINSTALL);
+ notifTitle = getString(R.string.uninstalling);
+ } else {
+ Log.e(TAG, "Unknown action : " + intent.getAction());
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+ Pair<Integer, Notification> notifPair = buildNotification(packageName, notifTitle);
+ startForeground(notifPair.first, notifPair.second);
+ msg.setData(intentBundle);
+ mServiceHandler.sendMessage(msg);
+ return START_NOT_STICKY;
+ }
+
+ private void installPackage(Bundle argsBundle) {
+ int startId = WearPackageArgs.getStartId(argsBundle);
+ final String packageName = WearPackageArgs.getPackageName(argsBundle);
+ final Uri assetUri = WearPackageArgs.getAssetUri(argsBundle);
+ final Uri permUri = WearPackageArgs.getPermUri(argsBundle);
+ boolean checkPerms = WearPackageArgs.checkPerms(argsBundle);
+ boolean skipIfSameVersion = WearPackageArgs.skipIfSameVersion(argsBundle);
+ int companionSdkVersion = WearPackageArgs.getCompanionSdkVersion(argsBundle);
+ int companionDeviceVersion = WearPackageArgs.getCompanionDeviceVersion(argsBundle);
+ String compressionAlg = WearPackageArgs.getCompressionAlg(argsBundle);
+ boolean skipIfLowerVersion = WearPackageArgs.skipIfLowerVersion(argsBundle);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Installing package: " + packageName + ", assetUri: " + assetUri +
+ ",permUri: " + permUri + ", startId: " + startId + ", checkPerms: " +
+ checkPerms + ", skipIfSameVersion: " + skipIfSameVersion +
+ ", compressionAlg: " + compressionAlg + ", companionSdkVersion: " +
+ companionSdkVersion + ", companionDeviceVersion: " + companionDeviceVersion +
+ ", skipIfLowerVersion: " + skipIfLowerVersion);
+ }
+ final PackageManager pm = getPackageManager();
+ File tempFile = null;
+ PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+ boolean messageSent = false;
+ try {
+ PackageInfo existingPkgInfo = null;
+ try {
+ existingPkgInfo = pm.getPackageInfo(packageName,
+ PackageManager.MATCH_ANY_USER | PackageManager.GET_PERMISSIONS);
+ if (existingPkgInfo != null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Replacing package:" + packageName);
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Ignore this exception. We could not find the package, will treat as a new
+ // installation.
+ }
+ // TODO(28021618): This was left as a temp file due to the fact that this code is being
+ // deprecated and that we need the bare minimum to continue working moving forward
+ // If this code is used as reference, this permission logic might want to be
+ // reworked to use a stream instead of a file so that we don't need to write a
+ // file at all. Note that there might be some trickiness with opening a stream
+ // for multiple users.
+ ParcelFileDescriptor parcelFd = getContentResolver()
+ .openFileDescriptor(assetUri, "r");
+ tempFile = WearPackageUtil.getFileFromFd(WearPackageInstallerService.this,
+ parcelFd, packageName, compressionAlg);
+ if (tempFile == null) {
+ Log.e(TAG, "Could not create a temp file from FD for " + packageName);
+ return;
+ }
+ PackageInfo pkgInfo = PackageUtil.getPackageInfo(this, tempFile,
+ PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS);
+ if (pkgInfo == null) {
+ Log.e(TAG, "Could not parse apk information for " + packageName);
+ return;
+ }
+
+ if (!pkgInfo.packageName.equals(packageName)) {
+ Log.e(TAG, "Wearable Package Name has to match what is provided for " +
+ packageName);
+ return;
+ }
+
+ ApplicationInfo appInfo = pkgInfo.applicationInfo;
+ appInfo.sourceDir = tempFile.getPath();
+ appInfo.publicSourceDir = tempFile.getPath();
+ getLabelAndUpdateNotification(packageName,
+ getString(R.string.installing_app, appInfo.loadLabel(pm)));
+
+ List<String> wearablePerms = Arrays.asList(pkgInfo.requestedPermissions);
+
+ // Log if the installed pkg has a higher version number.
+ if (existingPkgInfo != null) {
+ long longVersionCode = pkgInfo.getLongVersionCode();
+ if (existingPkgInfo.getLongVersionCode() == longVersionCode) {
+ if (skipIfSameVersion) {
+ Log.w(TAG, "Version number (" + longVersionCode +
+ ") of new app is equal to existing app for " + packageName +
+ "; not installing due to versionCheck");
+ return;
+ } else {
+ Log.w(TAG, "Version number of new app (" + longVersionCode +
+ ") is equal to existing app for " + packageName);
+ }
+ } else if (existingPkgInfo.getLongVersionCode() > longVersionCode) {
+ if (skipIfLowerVersion) {
+ // Starting in Feldspar, we are not going to allow downgrades of any app.
+ Log.w(TAG, "Version number of new app (" + longVersionCode +
+ ") is lower than existing app ( "
+ + existingPkgInfo.getLongVersionCode() +
+ ") for " + packageName + "; not installing due to versionCheck");
+ return;
+ } else {
+ Log.w(TAG, "Version number of new app (" + longVersionCode +
+ ") is lower than existing app ( "
+ + existingPkgInfo.getLongVersionCode() + ") for " + packageName);
+ }
+ }
+
+ // Following the Android Phone model, we should only check for permissions for any
+ // newly defined perms.
+ if (existingPkgInfo.requestedPermissions != null) {
+ for (int i = 0; i < existingPkgInfo.requestedPermissions.length; ++i) {
+ // If the permission is granted, then we will not ask to request it again.
+ if ((existingPkgInfo.requestedPermissionsFlags[i] &
+ PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, existingPkgInfo.requestedPermissions[i] +
+ " is already granted for " + packageName);
+ }
+ wearablePerms.remove(existingPkgInfo.requestedPermissions[i]);
+ }
+ }
+ }
+ }
+
+ // Check that the wearable has all the features.
+ boolean hasAllFeatures = true;
+ for (FeatureInfo feature : pkgInfo.reqFeatures) {
+ if (feature.name != null && !pm.hasSystemFeature(feature.name) &&
+ (feature.flags & FeatureInfo.FLAG_REQUIRED) != 0) {
+ Log.e(TAG, "Wearable does not have required feature: " + feature +
+ " for " + packageName);
+ hasAllFeatures = false;
+ }
+ }
+
+ if (!hasAllFeatures) {
+ return;
+ }
+
+ // Check permissions on both the new wearable package and also on the already installed
+ // wearable package.
+ // If the app is targeting API level 23, we will also start a service in ClockworkHome
+ // which will ultimately prompt the user to accept/reject permissions.
+ if (checkPerms && !checkPermissions(pkgInfo, companionSdkVersion,
+ companionDeviceVersion, permUri, wearablePerms, tempFile)) {
+ Log.w(TAG, "Wearable does not have enough permissions.");
+ return;
+ }
+
+ // Finally install the package.
+ ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(assetUri, "r");
+ PackageInstallerFactory.getPackageInstaller(this).install(packageName, fd,
+ new PackageInstallListener(this, lock, startId, packageName));
+
+ messageSent = true;
+ Log.i(TAG, "Sent installation request for " + packageName);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Could not find the file with URI " + assetUri, e);
+ } finally {
+ if (!messageSent) {
+ // Some error happened. If the message has been sent, we can wait for the observer
+ // which will finish the service.
+ if (tempFile != null) {
+ tempFile.delete();
+ }
+ finishService(lock, startId);
+ }
+ }
+ }
+
+ // TODO: This was left using the old PackageManager API due to the fact that this code is being
+ // deprecated and that we need the bare minimum to continue working moving forward
+ // If this code is used as reference, this logic should be reworked to use the new
+ // PackageInstaller APIs similar to how installPackage was reworked
+ private void uninstallPackage(Bundle argsBundle) {
+ int startId = WearPackageArgs.getStartId(argsBundle);
+ final String packageName = WearPackageArgs.getPackageName(argsBundle);
+
+ PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+
+ UninstallParams params = new UninstallParams(packageName, lock);
+ mServiceIdToParams.put(startId, params);
+
+ final PackageManager pm = getPackageManager();
+ try {
+ PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
+ getLabelAndUpdateNotification(packageName,
+ getString(R.string.uninstalling_app, pkgInfo.applicationInfo.loadLabel(pm)));
+
+ int uninstallId = UninstallEventReceiver.addObserver(this,
+ EventResultPersister.GENERATE_NEW_ID, this);
+
+ Intent broadcastIntent = new Intent(BROADCAST_ACTION);
+ broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId);
+ broadcastIntent.putExtra(EventResultPersister.EXTRA_SERVICE_ID, startId);
+ broadcastIntent.setPackage(getPackageName());
+
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId,
+ broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE);
+
+ // Found package, send uninstall request.
+ pm.getPackageInstaller().uninstall(
+ new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+ PackageManager.DELETE_ALL_USERS,
+ pendingIntent.getIntentSender());
+
+ Log.i(TAG, "Sent delete request for " + packageName);
+ } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) {
+ // Couldn't find the package, no need to call uninstall.
+ Log.w(TAG, "Could not find package, not deleting " + packageName, e);
+ finishService(lock, startId);
+ } catch (EventResultPersister.OutOfIdsException e) {
+ Log.e(TAG, "Fails to start uninstall", e);
+ finishService(lock, startId);
+ }
+ }
+
+ @Override
+ public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
+ if (mServiceIdToParams.containsKey(serviceId)) {
+ UninstallParams params = mServiceIdToParams.get(serviceId);
+ try {
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ Log.i(TAG, "Package " + params.mPackageName + " was uninstalled.");
+ } else {
+ Log.e(TAG, "Package uninstall failed " + params.mPackageName
+ + ", returnCode " + legacyStatus);
+ }
+ } finally {
+ finishService(params.mLock, serviceId);
+ }
+ }
+ }
+
+ private boolean checkPermissions(PackageInfo pkgInfo, int companionSdkVersion,
+ int companionDeviceVersion, Uri permUri, List<String> wearablePermissions,
+ File apkFile) {
+ // Assumption: We are running on Android O.
+ // If the Phone App is targeting M, all permissions may not have been granted to the phone
+ // app. If the Wear App is then not targeting M, there may be permissions that are not
+ // granted on the Phone app (by the user) right now and we cannot just grant it for the Wear
+ // app.
+ if (pkgInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
+ // Install the app if Wear App is ready for the new perms model.
+ return true;
+ }
+
+ if (!doesWearHaveUngrantedPerms(pkgInfo.packageName, permUri, wearablePermissions)) {
+ // All permissions requested by the watch are already granted on the phone, no need
+ // to do anything.
+ return true;
+ }
+
+ // Log an error if Wear is targeting < 23 and phone is targeting >= 23.
+ if (companionSdkVersion == 0 || companionSdkVersion >= Build.VERSION_CODES.M) {
+ Log.e(TAG, "MNC: Wear app's targetSdkVersion should be at least 23, if "
+ + "phone app is targeting at least 23, will continue.");
+ }
+
+ return false;
+ }
+
+ /**
+ * Given a {@string packageName} corresponding to a phone app, query the provider for all the
+ * perms that are granted.
+ *
+ * @return true if the Wear App has any perms that have not been granted yet on the phone side.
+ * @return true if there is any error cases.
+ */
+ private boolean doesWearHaveUngrantedPerms(String packageName, Uri permUri,
+ List<String> wearablePermissions) {
+ if (permUri == null) {
+ Log.e(TAG, "Permission URI is null");
+ // Pretend there is an ungranted permission to avoid installing for error cases.
+ return true;
+ }
+ Cursor permCursor = getContentResolver().query(permUri, null, null, null, null);
+ if (permCursor == null) {
+ Log.e(TAG, "Could not get the cursor for the permissions");
+ // Pretend there is an ungranted permission to avoid installing for error cases.
+ return true;
+ }
+
+ Set<String> grantedPerms = new HashSet<>();
+ Set<String> ungrantedPerms = new HashSet<>();
+ while(permCursor.moveToNext()) {
+ // Make sure that the MatrixCursor returned by the ContentProvider has 2 columns and
+ // verify their types.
+ if (permCursor.getColumnCount() == 2
+ && Cursor.FIELD_TYPE_STRING == permCursor.getType(0)
+ && Cursor.FIELD_TYPE_INTEGER == permCursor.getType(1)) {
+ String perm = permCursor.getString(0);
+ Integer granted = permCursor.getInt(1);
+ if (granted == 1) {
+ grantedPerms.add(perm);
+ } else {
+ ungrantedPerms.add(perm);
+ }
+ }
+ }
+ permCursor.close();
+
+ boolean hasUngrantedPerm = false;
+ for (String wearablePerm : wearablePermissions) {
+ if (!grantedPerms.contains(wearablePerm)) {
+ hasUngrantedPerm = true;
+ if (!ungrantedPerms.contains(wearablePerm)) {
+ // This is an error condition. This means that the wearable has permissions that
+ // are not even declared in its host app. This is a developer error.
+ Log.e(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm
+ + "\" that is not defined in the host application's manifest.");
+ } else {
+ Log.w(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm +
+ "\" that is not granted in the host application.");
+ }
+ }
+ }
+ return hasUngrantedPerm;
+ }
+
+ /** Finishes the service after fulfilling obligation to call startForeground. */
+ private void finishServiceEarly(int startId) {
+ Pair<Integer, Notification> notifPair = buildNotification(
+ getApplicationContext().getPackageName(), "");
+ startForeground(notifPair.first, notifPair.second);
+ finishService(null, startId);
+ }
+
+ private void finishService(PowerManager.WakeLock lock, int startId) {
+ if (lock != null && lock.isHeld()) {
+ lock.release();
+ }
+ stopSelf(startId);
+ }
+
+ private synchronized PowerManager.WakeLock getLock(Context context) {
+ if (lockStatic == null) {
+ PowerManager mgr =
+ (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ lockStatic = mgr.newWakeLock(
+ PowerManager.PARTIAL_WAKE_LOCK, context.getClass().getSimpleName());
+ lockStatic.setReferenceCounted(true);
+ }
+ return lockStatic;
+ }
+
+ private class PackageInstallListener implements PackageInstallerImpl.InstallListener {
+ private Context mContext;
+ private PowerManager.WakeLock mWakeLock;
+ private int mStartId;
+ private String mApplicationPackageName;
+ private PackageInstallListener(Context context, PowerManager.WakeLock wakeLock,
+ int startId, String applicationPackageName) {
+ mContext = context;
+ mWakeLock = wakeLock;
+ mStartId = startId;
+ mApplicationPackageName = applicationPackageName;
+ }
+
+ @Override
+ public void installBeginning() {
+ Log.i(TAG, "Package " + mApplicationPackageName + " is being installed.");
+ }
+
+ @Override
+ public void installSucceeded() {
+ try {
+ Log.i(TAG, "Package " + mApplicationPackageName + " was installed.");
+
+ // Delete tempFile from the file system.
+ File tempFile = WearPackageUtil.getTemporaryFile(mContext, mApplicationPackageName);
+ if (tempFile != null) {
+ tempFile.delete();
+ }
+ } finally {
+ finishService(mWakeLock, mStartId);
+ }
+ }
+
+ @Override
+ public void installFailed(int errorCode, String errorDesc) {
+ Log.e(TAG, "Package install failed " + mApplicationPackageName
+ + ", errorCode " + errorCode);
+ finishService(mWakeLock, mStartId);
+ }
+ }
+
+ private synchronized Pair<Integer, Notification> buildNotification(final String packageName,
+ final String title) {
+ int notifId;
+ if (mNotifIdMap.containsKey(packageName)) {
+ notifId = mNotifIdMap.get(packageName);
+ } else {
+ notifId = mInstallNotificationId++;
+ mNotifIdMap.put(packageName, notifId);
+ }
+
+ if (mNotificationChannel == null) {
+ mNotificationChannel = new NotificationChannel(WEAR_APPS_CHANNEL,
+ getString(R.string.wear_app_channel), NotificationManager.IMPORTANCE_MIN);
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(mNotificationChannel);
+ }
+ return new Pair<>(notifId, new Notification.Builder(this, WEAR_APPS_CHANNEL)
+ .setSmallIcon(R.drawable.ic_file_download)
+ .setContentTitle(title)
+ .build());
+ }
+
+ private void getLabelAndUpdateNotification(String packageName, String title) {
+ // Update notification since we have a label now.
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ Pair<Integer, Notification> notifPair = buildNotification(packageName, title);
+ notificationManager.notify(notifPair.first, notifPair.second);
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
new file mode 100644
index 0000000..6a9145d
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.wear;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.tukaani.xz.LZMAInputStream;
+import org.tukaani.xz.XZInputStream;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class WearPackageUtil {
+ private static final String TAG = "WearablePkgInstaller";
+
+ private static final String COMPRESSION_LZMA = "lzma";
+ private static final String COMPRESSION_XZ = "xz";
+
+ public static File getTemporaryFile(Context context, String packageName) {
+ try {
+ File newFileDir = new File(context.getFilesDir(), "tmp");
+ newFileDir.mkdirs();
+ Os.chmod(newFileDir.getAbsolutePath(), 0771);
+ File newFile = new File(newFileDir, packageName + ".apk");
+ return newFile;
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to open.", e);
+ return null;
+ }
+ }
+
+ public static File getIconFile(final Context context, final String packageName) {
+ try {
+ File newFileDir = new File(context.getFilesDir(), "images/icons");
+ newFileDir.mkdirs();
+ Os.chmod(newFileDir.getAbsolutePath(), 0771);
+ return new File(newFileDir, packageName + ".icon");
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to open.", e);
+ return null;
+ }
+ }
+
+ /**
+ * In order to make sure that the Wearable Asset Manager has a reasonable apk that can be used
+ * by the PackageManager, we will parse it before sending it to the PackageManager.
+ * Unfortunately, ParsingPackageUtils needs a file to parse. So, we have to temporarily convert
+ * the fd to a File.
+ *
+ * @param context
+ * @param fd FileDescriptor to convert to File
+ * @param packageName Name of package, will define the name of the file
+ * @param compressionAlg Can be null. For ALT mode the APK will be compressed. We will
+ * decompress it here
+ */
+ public static File getFileFromFd(Context context, ParcelFileDescriptor fd,
+ String packageName, String compressionAlg) {
+ File newFile = getTemporaryFile(context, packageName);
+ if (fd == null || fd.getFileDescriptor() == null) {
+ return null;
+ }
+ InputStream fr = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+ try {
+ if (TextUtils.equals(compressionAlg, COMPRESSION_XZ)) {
+ fr = new XZInputStream(fr);
+ } else if (TextUtils.equals(compressionAlg, COMPRESSION_LZMA)) {
+ fr = new LZMAInputStream(fr);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Compression was set to " + compressionAlg + ", but could not decode ", e);
+ return null;
+ }
+
+ int nRead;
+ byte[] data = new byte[1024];
+ try {
+ final FileOutputStream fo = new FileOutputStream(newFile);
+ while ((nRead = fr.read(data, 0, data.length)) != -1) {
+ fo.write(data, 0, nRead);
+ }
+ fo.flush();
+ fo.close();
+ Os.chmod(newFile.getAbsolutePath(), 0644);
+ return newFile;
+ } catch (IOException e) {
+ Log.e(TAG, "Reading from Asset FD or writing to temp file failed ", e);
+ return null;
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Could not set permissions on file ", e);
+ return null;
+ } finally {
+ try {
+ fr.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close the file from FD ", e);
+ }
+ }
+ }
+
+ /**
+ * @return com.google.com from expected formats like
+ * Uri: package:com.google.com, package:/com.google.com, package://com.google.com
+ */
+ public static String getSanitizedPackageName(Uri packageUri) {
+ String packageName = packageUri.getEncodedSchemeSpecificPart();
+ if (packageName != null) {
+ return packageName.replaceAll("^/+", "");
+ }
+ return packageName;
+ }
+}
diff --git a/packages/PrintSpooler/res/values-night/themes.xml b/packages/PrintSpooler/res/values-night/themes.xml
index 4428dbb..3cc64a6 100644
--- a/packages/PrintSpooler/res/values-night/themes.xml
+++ b/packages/PrintSpooler/res/values-night/themes.xml
@@ -30,6 +30,7 @@
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
</resources>
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index 4dcad10..bd96025 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -31,6 +31,7 @@
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowLightStatusBar">true</item>
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
</resources>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml
new file mode 100644
index 0000000..fadcf7b
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
+ <item name="elevationOverlayEnabled">true</item>
+ <item name="elevationOverlayColor">?attr/colorPrimary</item>
+ <item name="colorPrimary">@color/settingslib_materialColorOnSurfaceInverse</item>
+ <item name="colorAccent">@color/settingslib_materialColorPrimaryFixed</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml
new file mode 100644
index 0000000..0c20287
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <style name="CollapsingToolbarTitle.Collapsed" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+ <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
+ <item name="android:textSize">20dp</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
+ </style>
+
+ <style name="CollapsingToolbarTitle.Expanded" parent="CollapsingToolbarTitle.Collapsed">
+ <item name="android:textSize">36dp</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml
new file mode 100644
index 0000000..7c9d1a47
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
+ <item name="elevationOverlayEnabled">true</item>
+ <item name="elevationOverlayColor">?attr/colorPrimary</item>
+ <item name="colorPrimary">@color/settingslib_materialColorOnSurfaceInverse</item>
+ <item name="colorAccent">@color/settingslib_materialColorPrimary</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/DataStore/Android.bp b/packages/SettingsLib/DataStore/Android.bp
index 9fafcab..86c8f0da 100644
--- a/packages/SettingsLib/DataStore/Android.bp
+++ b/packages/SettingsLib/DataStore/Android.bp
@@ -2,12 +2,17 @@
default_applicable_licenses: ["frameworks_base_license"],
}
+filegroup {
+ name: "SettingsLibDataStore-srcs",
+ srcs: ["src/**/*"],
+}
+
android_library {
name: "SettingsLibDataStore",
defaults: [
"SettingsLintDefaults",
],
- srcs: ["src/**/*"],
+ srcs: [":SettingsLibDataStore-srcs"],
static_libs: [
"androidx.annotation_annotation",
"androidx.collection_collection-ktx",
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt
index 9d3fb66..7644bc9 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt
@@ -19,6 +19,7 @@
import android.app.backup.BackupDataInputStream
import android.content.Context
import android.util.Log
+import androidx.annotation.VisibleForTesting
import java.io.File
import java.io.InputStream
import java.io.OutputStream
@@ -33,11 +34,9 @@
*/
internal class BackupRestoreFileArchiver(
private val context: Context,
- private val fileStorages: List<BackupRestoreFileStorage>,
+ @get:VisibleForTesting internal val fileStorages: List<BackupRestoreFileStorage>,
+ override val name: String,
) : BackupRestoreStorage() {
- override val name: String
- get() = "file_archiver"
-
override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
fileStorages.map { it.toBackupRestoreEntity() }
@@ -88,7 +87,8 @@
}
}
-private fun BackupRestoreFileStorage.toBackupRestoreEntity() =
+@VisibleForTesting
+internal fun BackupRestoreFileStorage.toBackupRestoreEntity() =
object : BackupRestoreEntity {
override val key: String
get() = storageFilePath
@@ -107,7 +107,7 @@
Log.i(LOG_TAG, "[$name] $key not exist")
return EntityBackupResult.DELETE
}
- val codec = codec() ?: defaultCodec()
+ val codec = defaultCodec()
// MUST close to flush the data
wrapBackupOutputStream(codec, outputStream).use { stream ->
val bytesCopied = file.inputStream().use { it.copyTo(stream) }
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
index c4c00cb..935f9cc 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
@@ -22,6 +22,7 @@
import android.app.backup.BackupHelper
import android.os.ParcelFileDescriptor
import android.util.Log
+import androidx.annotation.VisibleForTesting
import androidx.collection.MutableScatterMap
import com.google.common.io.ByteStreams
import java.io.ByteArrayOutputStream
@@ -60,10 +61,11 @@
*
* Map key is the entity key, map value is the checksum of backup data.
*/
- protected val entityStates = MutableScatterMap<String, Long>()
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ val entityStates = MutableScatterMap<String, Long>()
/** Entities created by [createBackupRestoreEntities]. This field is for restore only. */
- private var entities: List<BackupRestoreEntity>? = null
+ @VisibleForTesting internal var entities: List<BackupRestoreEntity>? = null
/** Entities to back up and restore. */
abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity>
@@ -76,7 +78,7 @@
data: BackupDataOutput,
newState: ParcelFileDescriptor,
) {
- oldState.readEntityStates(entityStates)
+ readEntityStates(oldState, entityStates)
val backupContext = BackupContext(data)
if (!enableBackup(backupContext)) {
Log.i(LOG_TAG, "[$name] Backup disabled")
@@ -94,7 +96,10 @@
val codec = entity.codec() ?: defaultCodec()
val result =
try {
- entity.backup(backupContext, wrapBackupOutputStream(codec, checkedOutputStream))
+ // MUST close to flush all data
+ wrapBackupOutputStream(codec, checkedOutputStream).use {
+ entity.backup(backupContext, it)
+ }
} catch (exception: Exception) {
Log.e(LOG_TAG, "[$name] Fail to backup entity $key", exception)
continue
@@ -191,9 +196,13 @@
/** Callbacks when restore finished. */
open fun onRestoreFinished() {}
- private fun ParcelFileDescriptor?.readEntityStates(state: MutableScatterMap<String, Long>) {
+ @VisibleForTesting
+ internal fun readEntityStates(
+ parcelFileDescriptor: ParcelFileDescriptor?,
+ state: MutableScatterMap<String, Long>,
+ ) {
state.clear()
- if (this == null) return
+ val fileDescriptor = parcelFileDescriptor?.fileDescriptor ?: return
// do not close the streams
val fileInputStream = FileInputStream(fileDescriptor)
val dataInputStream = DataInputStream(fileInputStream)
@@ -233,6 +242,7 @@
dataOutputStream.writeUTF(key)
dataOutputStream.writeLong(value)
}
+ dataOutputStream.flush()
} catch (exception: Exception) {
Log.e(LOG_TAG, "[$name] Fail to write state file", exception)
}
@@ -241,7 +251,7 @@
}
companion object {
- private const val STATE_VERSION: Byte = 0
+ internal const val STATE_VERSION: Byte = 0
/** Checksum for entity backup data. */
fun createChecksum(): Checksum = CRC32()
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
index cfdcaff..8242347 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
@@ -21,23 +21,32 @@
import android.app.backup.BackupManager
import android.content.Context
import android.util.Log
+import androidx.annotation.VisibleForTesting
import com.google.common.util.concurrent.MoreExecutors
import java.util.concurrent.ConcurrentHashMap
/** Manager of [BackupRestoreStorage]. */
class BackupRestoreStorageManager private constructor(private val application: Application) {
- private val storageWrappers = ConcurrentHashMap<String, StorageWrapper>()
+ @VisibleForTesting internal val storageWrappers = ConcurrentHashMap<String, StorageWrapper>()
private val executor = MoreExecutors.directExecutor()
/**
* Adds all the registered [BackupRestoreStorage] as the helpers of given [BackupAgentHelper].
*
- * All [BackupRestoreFileStorage]s will be wrapped as a single [BackupRestoreFileArchiver].
+ * All [BackupRestoreFileStorage]s will be wrapped as a single [BackupRestoreFileArchiver],
+ * specify [fileArchiverName] to avoid key prefix conflict if needed.
*
+ * @param backupAgentHelper backup agent helper to add helpers
+ * @param fileArchiverName key prefix of the [BackupRestoreFileArchiver], the value must not be
+ * changed in future
* @see BackupAgentHelper.addHelper
*/
- fun addBackupAgentHelpers(backupAgentHelper: BackupAgentHelper) {
+ @JvmOverloads
+ fun addBackupAgentHelpers(
+ backupAgentHelper: BackupAgentHelper,
+ fileArchiverName: String = "file_archiver",
+ ) {
val fileStorages = mutableListOf<BackupRestoreFileStorage>()
for ((keyPrefix, storageWrapper) in storageWrappers) {
val storage = storageWrapper.storage
@@ -48,7 +57,7 @@
}
}
// Always add file archiver even fileStorages is empty to handle forward compatibility
- val fileArchiver = BackupRestoreFileArchiver(application, fileStorages)
+ val fileArchiver = BackupRestoreFileArchiver(application, fileStorages, fileArchiverName)
backupAgentHelper.addHelper(fileArchiver.name, fileArchiver)
}
@@ -106,7 +115,8 @@
/** Returns storage with given name, exception is raised if not found. */
fun getOrThrow(name: String): BackupRestoreStorage = storageWrappers[name]!!.storage
- private inner class StorageWrapper(val storage: BackupRestoreStorage) :
+ @VisibleForTesting
+ internal inner class StorageWrapper(val storage: BackupRestoreStorage) :
Observer, KeyedObserver<Any?> {
init {
when (storage) {
@@ -139,7 +149,7 @@
LOG_TAG,
"Notify BackupManager dataChanged: storage=$name key=$key reason=$reason"
)
- BackupManager.dataChanged(application.packageName)
+ BackupManager(application).dataChanged()
}
fun removeObserver() {
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt
index 0c1b417..9f9c0d8 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt
@@ -20,9 +20,11 @@
import android.content.SharedPreferences
import android.os.Build
import android.util.Log
-import androidx.core.content.ContextCompat
+import androidx.annotation.VisibleForTesting
import java.io.File
+private fun defaultVerbose() = Build.TYPE == "eng"
+
/**
* [SharedPreferences] based storage.
*
@@ -43,24 +45,35 @@
* @param verbose Verbose logging on key/value pairs during backup/restore. Enable for dev only!
* @param filter Filter of key/value pairs for backup and restore.
*/
-class SharedPreferencesStorage
+open class SharedPreferencesStorage
@JvmOverloads
constructor(
context: Context,
override val name: String,
- mode: Int,
- private val verbose: Boolean = (Build.TYPE == "eng"),
+ @get:VisibleForTesting internal val sharedPreferences: SharedPreferences,
+ private val codec: BackupCodec? = null,
+ private val verbose: Boolean = defaultVerbose(),
private val filter: (String, Any?) -> Boolean = { _, _ -> true },
) :
BackupRestoreFileStorage(context, context.getSharedPreferencesFilePath(name)),
KeyedObservable<String> by KeyedDataObservable() {
- private val sharedPreferences = context.getSharedPreferences(name, mode)
+ @JvmOverloads
+ constructor(
+ context: Context,
+ name: String,
+ mode: Int,
+ codec: BackupCodec? = null,
+ verbose: Boolean = defaultVerbose(),
+ filter: (String, Any?) -> Boolean = { _, _ -> true },
+ ) : this(context, name, context.getSharedPreferences(name, mode), codec, verbose, filter)
/** Name of the intermediate SharedPreferences. */
- private val intermediateName: String
+ @VisibleForTesting
+ internal val intermediateName: String
get() = "_br_$name"
+ @Suppress("DEPRECATION")
private val intermediateSharedPreferences: SharedPreferences
get() {
// use MODE_MULTI_PROCESS to ensure a reload
@@ -82,12 +95,15 @@
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
}
+ override fun defaultCodec() = codec ?: super.defaultCodec()
+
override val backupFile: File
// use a different file to avoid multi-thread file write
get() = context.getSharedPreferencesFile(intermediateName)
override fun prepareBackup(file: File) {
- val editor = intermediateSharedPreferences.merge(sharedPreferences.all, "Backup")
+ val editor =
+ mergeSharedPreferences(intermediateSharedPreferences, sharedPreferences.all, "Backup")
// commit to ensure data is write to disk synchronously
if (!editor.commit()) {
Log.w(LOG_TAG, "[$name] fail to commit")
@@ -104,8 +120,8 @@
// observers consistently once restore finished.
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferencesListener)
val restored = intermediateSharedPreferences
- val editor = sharedPreferences.merge(restored.all, "Restore")
- editor.apply() // apply to avoid blocking
+ val editor = mergeSharedPreferences(sharedPreferences, restored.all, "Restore")
+ editor.commit() // commit to avoid race condition
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
// clear the intermediate SharedPreferences
restored.delete(intermediateName)
@@ -115,7 +131,7 @@
if (deleteSharedPreferences(name)) {
Log.i(LOG_TAG, "SharedPreferences $name deleted")
} else {
- edit().clear().apply()
+ edit().clear().commit() // commit to avoid potential race condition
}
}
@@ -126,11 +142,13 @@
false
}
- private fun SharedPreferences.merge(
+ @VisibleForTesting
+ internal open fun mergeSharedPreferences(
+ sharedPreferences: SharedPreferences,
entries: Map<String, Any?>,
- operation: String
+ operation: String,
): SharedPreferences.Editor {
- val editor = edit()
+ val editor = sharedPreferences.edit()
for ((key, value) in entries) {
if (!filter.invoke(key, value)) {
if (verbose) Log.v(LOG_TAG, "[$name] $operation skips $key=$value")
@@ -184,7 +202,7 @@
companion object {
private fun Context.getSharedPreferencesFilePath(name: String): String {
val file = getSharedPreferencesFile(name)
- return file.relativeTo(ContextCompat.getDataDir(this)!!).toString()
+ return file.relativeTo(dataDirCompat).toString()
}
/** Returns the absolute path of shared preferences file. */
diff --git a/packages/SettingsLib/DataStore/tests/Android.bp b/packages/SettingsLib/DataStore/tests/Android.bp
index 8770dfa..5d000eb 100644
--- a/packages/SettingsLib/DataStore/tests/Android.bp
+++ b/packages/SettingsLib/DataStore/tests/Android.bp
@@ -9,11 +9,16 @@
android_robolectric_test {
name: "SettingsLibDataStoreTest",
- srcs: ["src/**/*"],
+ srcs: [
+ ":SettingsLibDataStore-srcs", // b/240432457
+ "src/**/*",
+ ],
static_libs: [
- "SettingsLibDataStore",
+ "androidx.collection_collection-ktx",
+ "androidx.core_core-ktx",
"androidx.test.ext.junit",
"guava",
+ "kotlin-test",
"mockito-robolectric-prebuilt", // mockito deps order matters!
"mockito-kotlin2",
],
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupCodecTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupCodecTest.kt
new file mode 100644
index 0000000..867831b
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupCodecTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import kotlin.random.Random
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests of [BackupCodec]. */
+@RunWith(AndroidJUnit4::class)
+class BackupCodecTest {
+ @Test
+ fun name() {
+ val names = mutableSetOf<String>()
+ for (codec in allCodecs()) {
+ assertThat(names).doesNotContain(codec.name)
+ names.add(codec.name)
+ }
+ }
+
+ @Test
+ fun fromId() {
+ for (codec in allCodecs()) {
+ assertThat(BackupCodec.fromId(codec.id)).isInstanceOf(codec::class.java)
+ }
+ }
+
+ @Test
+ fun fromId_unknownId() {
+ assertFailsWith(IllegalArgumentException::class) { BackupCodec.fromId(-1) }
+ }
+
+ @Test
+ fun encode_decode() {
+ val random = Random.Default
+ fun test(codec: BackupCodec, size: Int) {
+ val data = random.nextBytes(size)
+
+ // encode
+ val outputStream = ByteArrayOutputStream()
+ codec.encode(outputStream).use { it.write(data) }
+
+ // decode
+ val inputStream = ByteArrayInputStream(outputStream.toByteArray())
+ val result = codec.decode(inputStream).use { it.readBytes() }
+
+ assertWithMessage("$size bytes: $data").that(result).isEqualTo(data)
+ }
+
+ for (codec in allCodecs()) {
+ test(codec, 0)
+ repeat(10) { test(codec, random.nextInt(1, 1024)) }
+ }
+ }
+}
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreContextTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreContextTest.kt
new file mode 100644
index 0000000..911665a
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreContextTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.app.backup.BackupDataOutput
+import android.os.Build
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+/** Tests of [BackupContext] and [RestoreContext]. */
+@RunWith(AndroidJUnit4::class)
+class BackupRestoreContextTest {
+ @Test
+ fun backupContext_quota() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
+ val data = mock<BackupDataOutput> { on { quota } doReturn 10L }
+ assertThat(BackupContext(data).quota).isEqualTo(10)
+ }
+
+ @Test
+ fun backupContext_transportFlags() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return
+ val data = mock<BackupDataOutput> { on { transportFlags } doReturn 5 }
+ assertThat(BackupContext(data).transportFlags).isEqualTo(5)
+ }
+}
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreFileArchiverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreFileArchiverTest.kt
new file mode 100644
index 0000000..6cce453
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreFileArchiverTest.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.app.Application
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.IOException
+import java.io.InputStream
+import kotlin.random.Random
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+/** Tests of [BackupRestoreFileArchiver]. */
+@RunWith(AndroidJUnit4::class)
+class BackupRestoreFileArchiverTest {
+ private val random = Random.Default
+ private val application: Application = getApplicationContext()
+ @get:Rule val temporaryFolder = TemporaryFolder(application.dataDirCompat)
+
+ @Test
+ fun createBackupRestoreEntities() {
+ val fileStorages = mutableListOf<BackupRestoreFileStorage>()
+ for (count in 0 until 3) {
+ val fileArchiver = BackupRestoreFileArchiver(application, fileStorages, "")
+ fileArchiver.createBackupRestoreEntities().apply {
+ assertThat(this).hasSize(fileStorages.size)
+ for (index in 0 until count) {
+ assertThat(get(index).key).isEqualTo(fileStorages[index].storageFilePath)
+ }
+ }
+ fileStorages.add(FileStorage("storage", "path$count"))
+ }
+ }
+
+ @Test
+ fun wrapBackupOutputStream() {
+ val fileArchiver = BackupRestoreFileArchiver(application, listOf(), "")
+ val outputStream = ByteArrayOutputStream()
+ assertThat(fileArchiver.wrapBackupOutputStream(BackupZipCodec.BEST_SPEED, outputStream))
+ .isSameInstanceAs(outputStream)
+ }
+
+ @Test
+ fun wrapRestoreInputStream() {
+ val fileArchiver = BackupRestoreFileArchiver(application, listOf(), "")
+ val inputStream = ByteArrayInputStream(byteArrayOf())
+ assertThat(fileArchiver.wrapRestoreInputStream(BackupZipCodec.BEST_SPEED, inputStream))
+ .isSameInstanceAs(inputStream)
+ }
+
+ @Test
+ fun restoreEntity_disabled() {
+ val file = temporaryFolder.newFile()
+ val key = file.name
+ val fileStorage = FileStorage("fs", key, restoreEnabled = false)
+
+ BackupRestoreFileArchiver(application, listOf(fileStorage), "archiver").apply {
+ restoreEntity(newBackupDataInputStream(key, byteArrayOf()))
+ assertThat(entityStates.asMap()).isEmpty()
+ }
+ }
+
+ @Test
+ fun restoreEntity_raiseIOException() {
+ val key = "key"
+ val fileStorage = FileStorage("fs", key)
+ BackupRestoreFileArchiver(application, listOf(fileStorage), "archiver").apply {
+ restoreEntity(newBackupDataInputStream(key, byteArrayOf(), IOException()))
+ assertThat(entityStates.asMap()).isEmpty()
+ }
+ }
+
+ @Test
+ fun restoreEntity_onRestoreFinished_raiseException() {
+ val key = "key"
+ val fileStorage = FileStorage("fs", key, restoreException = IllegalStateException())
+ BackupRestoreFileArchiver(application, listOf(fileStorage), "archiver").apply {
+ val data = random.nextBytes(random.nextInt(10))
+ val outputStream = ByteArrayOutputStream()
+ fileStorage.wrapBackupOutputStream(fileStorage.defaultCodec(), outputStream).use {
+ it.write(data)
+ }
+ val payload = outputStream.toByteArray()
+ restoreEntity(newBackupDataInputStream(key, payload))
+ assertThat(entityStates.asMap()).isEmpty()
+ }
+ }
+
+ @Test
+ fun restoreEntity_forwardCompatibility() {
+ val key = "key"
+ val fileStorage = FileStorage("fs", key)
+ for (codec in allCodecs()) {
+ BackupRestoreFileArchiver(application, listOf(), "archiver").apply {
+ val data = random.nextBytes(random.nextInt(MAX_DATA_SIZE))
+ val outputStream = ByteArrayOutputStream()
+ fileStorage.wrapBackupOutputStream(codec, outputStream).use { it.write(data) }
+ val payload = outputStream.toByteArray()
+
+ restoreEntity(newBackupDataInputStream(key, payload))
+
+ assertThat(entityStates.asMap()).apply {
+ hasSize(1)
+ containsKey(key)
+ }
+ assertThat(fileStorage.restoreFile.readBytes()).isEqualTo(data)
+ }
+ }
+ }
+
+ @Test
+ fun restoreEntity() {
+ val folder = File(application.dataDirCompat, "backup")
+ val file = File(folder, "file")
+ val key = "${folder.name}${File.separator}${file.name}"
+ fun test(codec: BackupCodec, size: Int) {
+ val fileStorage = FileStorage("fs", key, if (size % 2 == 0) codec else null)
+ val data = random.nextBytes(size)
+ val outputStream = ByteArrayOutputStream()
+ fileStorage.wrapBackupOutputStream(codec, outputStream).use { it.write(data) }
+ val payload = outputStream.toByteArray()
+
+ val fileArchiver =
+ BackupRestoreFileArchiver(application, listOf(fileStorage), "archiver")
+ fileArchiver.restoreEntity(newBackupDataInputStream(key, payload))
+
+ assertThat(fileArchiver.entityStates.asMap()).apply {
+ hasSize(1)
+ containsKey(key)
+ }
+ assertThat(file.readBytes()).isEqualTo(data)
+ }
+
+ for (codec in allCodecs()) {
+ for (size in 0 until 100) test(codec, size)
+ repeat(10) { test(codec, random.nextInt(100, MAX_DATA_SIZE)) }
+ }
+ }
+
+ @Test
+ fun onRestoreFinished() {
+ val fileStorage = mock<BackupRestoreFileStorage>()
+ val fileArchiver = BackupRestoreFileArchiver(application, listOf(fileStorage), "")
+
+ fileArchiver.onRestoreFinished()
+
+ verify(fileStorage).onRestoreFinished()
+ }
+
+ @Test
+ fun toBackupRestoreEntity_backup_disabled() {
+ val context = BackupContext(mock())
+ val fileStorage =
+ mock<BackupRestoreFileStorage> { on { enableBackup(context) } doReturn false }
+
+ assertThat(fileStorage.toBackupRestoreEntity().backup(context, ByteArrayOutputStream()))
+ .isEqualTo(EntityBackupResult.INTACT)
+
+ verify(fileStorage, never()).prepareBackup(any())
+ }
+
+ @Test
+ fun toBackupRestoreEntity_backup_fileNotExist() {
+ val context = BackupContext(mock())
+ val file = File("NotExist")
+ val fileStorage =
+ mock<BackupRestoreFileStorage> {
+ on { enableBackup(context) } doReturn true
+ on { backupFile } doReturn file
+ }
+
+ assertThat(fileStorage.toBackupRestoreEntity().backup(context, ByteArrayOutputStream()))
+ .isEqualTo(EntityBackupResult.DELETE)
+
+ verify(fileStorage).prepareBackup(file)
+ verify(fileStorage, never()).defaultCodec()
+ }
+
+ @Test
+ fun toBackupRestoreEntity_backup() {
+ val context = BackupContext(mock())
+ val file = temporaryFolder.newFile()
+
+ fun test(codec: BackupCodec, size: Int) {
+ val data = random.nextBytes(size)
+ file.outputStream().use { it.write(data) }
+
+ val outputStream = ByteArrayOutputStream()
+ val fileStorage =
+ mock<BackupRestoreFileStorage> {
+ on { enableBackup(context) } doReturn true
+ on { backupFile } doReturn file
+ on { defaultCodec() } doReturn codec
+ on { wrapBackupOutputStream(any(), any()) }.thenCallRealMethod()
+ on { wrapRestoreInputStream(any(), any()) }.thenCallRealMethod()
+ on { prepareBackup(any()) }.thenCallRealMethod()
+ on { onBackupFinished(any()) }.thenCallRealMethod()
+ }
+
+ assertThat(fileStorage.toBackupRestoreEntity().backup(context, outputStream))
+ .isEqualTo(EntityBackupResult.UPDATE)
+
+ verify(fileStorage).prepareBackup(file)
+ verify(fileStorage).onBackupFinished(file)
+
+ val decodedData =
+ fileStorage
+ .wrapRestoreInputStream(codec, ByteArrayInputStream(outputStream.toByteArray()))
+ .readBytes()
+ assertThat(decodedData).isEqualTo(data)
+ }
+
+ for (codec in allCodecs()) {
+ // test small data to ensure correctness
+ for (size in 0 until 100) test(codec, size)
+ repeat(10) { test(codec, random.nextInt(100, MAX_DATA_SIZE)) }
+ }
+ }
+
+ @Test
+ fun toBackupRestoreEntity_restore() {
+ val restoreContext = RestoreContext("storage")
+ val inputStream =
+ object : InputStream() {
+ override fun read() = throw IllegalStateException()
+
+ override fun read(b: ByteArray, off: Int, len: Int) = throw IllegalStateException()
+ }
+ FileStorage("storage", "path").toBackupRestoreEntity().restore(restoreContext, inputStream)
+ }
+
+ private open class FileStorage(
+ override val name: String,
+ filePath: String,
+ private val codec: BackupCodec? = null,
+ private val restoreEnabled: Boolean? = null,
+ private val restoreException: Exception? = null,
+ ) : BackupRestoreFileStorage(getApplicationContext(), filePath) {
+
+ override fun defaultCodec() = codec ?: super.defaultCodec()
+
+ override fun enableRestore() = restoreEnabled ?: super.enableRestore()
+
+ override fun onRestoreFinished(file: File) {
+ super.onRestoreFinished(file)
+ if (restoreException != null) throw restoreException
+ }
+ }
+}
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreFileStorageTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreFileStorageTest.kt
new file mode 100644
index 0000000..422273d
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreFileStorageTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.app.Application
+import android.os.Build
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests of [BackupRestoreFileStorage]. */
+@RunWith(AndroidJUnit4::class)
+class BackupRestoreFileStorageTest {
+ private val application: Application = getApplicationContext()
+
+ @Test
+ fun dataDirCompat() {
+ val expected =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ application.dataDir
+ } else {
+ File(application.applicationInfo.dataDir)
+ }
+ assertThat(application.dataDirCompat).isEqualTo(expected)
+ }
+
+ @Test
+ fun backupFile() {
+ assertThat(FileStorage("path").backupFile.toString())
+ .startsWith(application.dataDirCompat.toString())
+ }
+
+ @Test
+ fun restoreFile() {
+ FileStorage("path").apply { assertThat(restoreFile).isEqualTo(backupFile) }
+ }
+
+ @Test
+ fun checkFilePaths() {
+ FileStorage("path").checkFilePaths()
+ }
+
+ @Test
+ fun checkFilePaths_emptyFilePath() {
+ assertFailsWith(IllegalArgumentException::class) { FileStorage("").checkFilePaths() }
+ }
+
+ @Test
+ fun checkFilePaths_absoluteFilePath() {
+ assertFailsWith(IllegalArgumentException::class) {
+ FileStorage("${File.separatorChar}file").checkFilePaths()
+ }
+ }
+
+ @Test
+ fun checkFilePaths_backupFile() {
+ assertFailsWith(IllegalArgumentException::class) {
+ FileStorage("path", fileForBackup = File("path")).checkFilePaths()
+ }
+ }
+
+ @Test
+ fun checkFilePaths_restoreFile() {
+ assertFailsWith(IllegalArgumentException::class) {
+ FileStorage("path", fileForRestore = File("path")).checkFilePaths()
+ }
+ }
+
+ @Test
+ fun createBackupRestoreEntities() {
+ assertThat(FileStorage("path").createBackupRestoreEntities()).isEmpty()
+ }
+
+ private class FileStorage(
+ filePath: String,
+ val fileForBackup: File? = null,
+ val fileForRestore: File? = null,
+ ) : BackupRestoreFileStorage(getApplicationContext(), filePath) {
+ override val name = "storage"
+
+ override val backupFile: File
+ get() = fileForBackup ?: super.backupFile
+
+ override val restoreFile: File
+ get() = fileForRestore ?: super.restoreFile
+ }
+}
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt
new file mode 100644
index 0000000..d8f5028
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.app.Application
+import android.app.backup.BackupAgentHelper
+import android.app.backup.BackupHelper
+import android.app.backup.BackupManager
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import kotlin.test.assertFailsWith
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+import org.robolectric.Shadows
+import org.robolectric.shadows.ShadowBackupManager
+
+/** Tests of [BackupRestoreStorageManager]. */
+@RunWith(AndroidJUnit4::class)
+class BackupRestoreStorageManagerTest {
+ private val application: Application = getApplicationContext()
+ private val manager = BackupRestoreStorageManager.getInstance(application)
+ private val fileStorage = FileStorage("fileStorage")
+ private val keyedStorage = KeyedStorage("keyedStorage")
+
+ private val storage1 = mock<ObservableBackupRestoreStorage> { on { name } doReturn "1" }
+ private val storage2 = mock<ObservableBackupRestoreStorage> { on { name } doReturn "1" }
+
+ @After
+ fun tearDown() {
+ manager.removeAll()
+ ShadowBackupManager.reset()
+ }
+
+ @Test
+ fun getInstance() {
+ assertThat(BackupRestoreStorageManager.getInstance(application)).isSameInstanceAs(manager)
+ }
+
+ @Test
+ fun addBackupAgentHelpers() {
+ val fs = FileStorage("fs")
+ manager.add(keyedStorage, fileStorage, storage1, fs)
+ val backupAgentHelper = DummyBackupAgentHelper()
+ manager.addBackupAgentHelpers(backupAgentHelper)
+ backupAgentHelper.backupHelpers.apply {
+ assertThat(size).isEqualTo(3)
+ assertThat(remove(keyedStorage.name)).isSameInstanceAs(keyedStorage)
+ assertThat(remove(storage1.name)).isSameInstanceAs(storage1)
+ val fileArchiver = entries.first().value as BackupRestoreFileArchiver
+ assertThat(fileArchiver.fileStorages.toSet()).containsExactly(fs, fileStorage)
+ }
+ }
+
+ @Test
+ fun addBackupAgentHelpers_withoutFileStorage() {
+ manager.add(keyedStorage, storage1)
+ val backupAgentHelper = DummyBackupAgentHelper()
+ manager.addBackupAgentHelpers(backupAgentHelper)
+ backupAgentHelper.backupHelpers.apply {
+ assertThat(size).isEqualTo(3)
+ assertThat(remove(keyedStorage.name)).isSameInstanceAs(keyedStorage)
+ assertThat(remove(storage1.name)).isSameInstanceAs(storage1)
+ val fileArchiver = entries.first().value as BackupRestoreFileArchiver
+ assertThat(fileArchiver.fileStorages).isEmpty()
+ }
+ }
+
+ @Test
+ fun add() {
+ manager.add(keyedStorage, fileStorage, storage1)
+ assertThat(manager.storageWrappers).apply {
+ hasSize(3)
+ containsKey(keyedStorage.name)
+ containsKey(fileStorage.name)
+ containsKey(storage1.name)
+ }
+ }
+
+ @Test
+ fun add_identicalName() {
+ manager.add(storage1)
+ assertFailsWith(IllegalStateException::class) { manager.add(storage1) }
+ assertFailsWith(IllegalStateException::class) { manager.add(storage2) }
+ }
+
+ @Test
+ fun add_nonObservable() {
+ assertFailsWith(IllegalArgumentException::class) {
+ manager.add(mock<BackupRestoreStorage>())
+ }
+ }
+
+ @Test
+ fun removeAll() {
+ add()
+ manager.removeAll()
+ assertThat(manager.storageWrappers).isEmpty()
+ }
+
+ @Test
+ fun remove() {
+ manager.add(keyedStorage, fileStorage)
+ assertThat(manager.remove(storage1.name)).isNull()
+ assertThat(manager.remove(keyedStorage.name)).isSameInstanceAs(keyedStorage)
+ assertThat(manager.remove(fileStorage.name)).isSameInstanceAs(fileStorage)
+ }
+
+ @Test
+ fun get() {
+ manager.add(keyedStorage, fileStorage)
+ assertThat(manager.get(storage1.name)).isNull()
+ assertThat(manager.get(keyedStorage.name)).isSameInstanceAs(keyedStorage)
+ assertThat(manager.get(fileStorage.name)).isSameInstanceAs(fileStorage)
+ }
+
+ @Test
+ fun getOrThrow() {
+ manager.add(keyedStorage, fileStorage)
+ assertFailsWith(NullPointerException::class) { manager.getOrThrow(storage1.name) }
+ assertThat(manager.getOrThrow(keyedStorage.name)).isSameInstanceAs(keyedStorage)
+ assertThat(manager.getOrThrow(fileStorage.name)).isSameInstanceAs(fileStorage)
+ }
+
+ @Test
+ fun notifyRestoreFinished() {
+ manager.add(keyedStorage, fileStorage)
+ val keyedObserver = mock<KeyedObserver<String>>()
+ val anyKeyObserver = mock<KeyedObserver<String?>>()
+ val observer = mock<Observer>()
+ val executor = directExecutor()
+ keyedStorage.addObserver("key", keyedObserver, executor)
+ keyedStorage.addObserver(anyKeyObserver, executor)
+ fileStorage.addObserver(observer, executor)
+
+ manager.onRestoreFinished()
+
+ verify(keyedObserver).onKeyChanged("key", ChangeReason.RESTORE)
+ verify(anyKeyObserver).onKeyChanged(null, ChangeReason.RESTORE)
+ verify(observer).onChanged(ChangeReason.RESTORE)
+ if (isRobolectric()) {
+ Shadows.shadowOf(BackupManager(application)).apply {
+ assertThat(isDataChanged).isFalse()
+ assertThat(dataChangedCount).isEqualTo(0)
+ }
+ }
+ }
+
+ @Test
+ fun notifyBackupManager() {
+ manager.add(keyedStorage, fileStorage)
+ val keyedObserver = mock<KeyedObserver<String>>()
+ val anyKeyObserver = mock<KeyedObserver<String?>>()
+ val observer = mock<Observer>()
+ val executor = directExecutor()
+ keyedStorage.addObserver("key", keyedObserver, executor)
+ keyedStorage.addObserver(anyKeyObserver, executor)
+ fileStorage.addObserver(observer, executor)
+
+ val backupManager =
+ if (isRobolectric()) Shadows.shadowOf(BackupManager(application)) else null
+ backupManager?.apply {
+ assertThat(isDataChanged).isFalse()
+ assertThat(dataChangedCount).isEqualTo(0)
+ }
+
+ fileStorage.notifyChange(ChangeReason.UPDATE)
+ verify(observer).onChanged(ChangeReason.UPDATE)
+ verify(keyedObserver, never()).onKeyChanged(any(), any())
+ verify(anyKeyObserver, never()).onKeyChanged(any(), any())
+ reset(observer)
+ backupManager?.apply {
+ assertThat(isDataChanged).isTrue()
+ assertThat(dataChangedCount).isEqualTo(1)
+ }
+
+ keyedStorage.notifyChange("key", ChangeReason.DELETE)
+ verify(observer, never()).onChanged(any())
+ verify(keyedObserver).onKeyChanged("key", ChangeReason.DELETE)
+ verify(anyKeyObserver).onKeyChanged("key", ChangeReason.DELETE)
+ backupManager?.apply {
+ assertThat(isDataChanged).isTrue()
+ assertThat(dataChangedCount).isEqualTo(2)
+ }
+ reset(keyedObserver)
+
+ // backup manager is not notified for restore event
+ fileStorage.notifyChange(ChangeReason.RESTORE)
+ keyedStorage.notifyChange("key", ChangeReason.RESTORE)
+ verify(observer).onChanged(ChangeReason.RESTORE)
+ verify(keyedObserver).onKeyChanged("key", ChangeReason.RESTORE)
+ verify(anyKeyObserver).onKeyChanged("key", ChangeReason.RESTORE)
+ backupManager?.apply {
+ assertThat(isDataChanged).isTrue()
+ assertThat(dataChangedCount).isEqualTo(2)
+ }
+ }
+
+ private class KeyedStorage(override val name: String) :
+ BackupRestoreStorage(), KeyedObservable<String> by KeyedDataObservable() {
+
+ override fun createBackupRestoreEntities(): List<BackupRestoreEntity> = listOf()
+ }
+
+ private class FileStorage(override val name: String) :
+ BackupRestoreFileStorage(getApplicationContext(), "file"), Observable by DataObservable()
+
+ private class DummyBackupAgentHelper : BackupAgentHelper() {
+ val backupHelpers = mutableMapOf<String, BackupHelper>()
+
+ override fun addHelper(keyPrefix: String, helper: BackupHelper) {
+ backupHelpers[keyPrefix] = helper
+ }
+ }
+}
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt
new file mode 100644
index 0000000..99998ff
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.app.backup.BackupAgentHelper
+import android.app.backup.BackupDataInput
+import android.app.backup.BackupDataInputStream
+import android.app.backup.BackupDataOutput
+import android.os.ParcelFileDescriptor
+import android.os.ParcelFileDescriptor.MODE_APPEND
+import android.os.ParcelFileDescriptor.MODE_READ_ONLY
+import android.os.ParcelFileDescriptor.MODE_WRITE_ONLY
+import androidx.collection.MutableScatterMap
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import java.io.DataOutputStream
+import java.io.File
+import java.io.FileDescriptor
+import java.io.FileOutputStream
+import java.io.InputStream
+import java.io.OutputStream
+import kotlin.random.Random
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.doThrow
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+
+/** Tests of [BackupRestoreStorage]. */
+@RunWith(AndroidJUnit4::class)
+class BackupRestoreStorageTest {
+ @get:Rule val temporaryFolder = TemporaryFolder()
+
+ private val entity1 = Entity("key1", "value1".toByteArray())
+ private val entity1NoOpCodec = Entity("key1", "value1".toByteArray(), BackupNoOpCodec())
+ private val entity2 = Entity("key2", "value2".toByteArray(), BackupZipCodec.BEST_SPEED)
+
+ @Test
+ fun performBackup_disabled() {
+ val storage = spy(TestStorage().apply { enabled = false })
+ val unused = performBackup { data, newState -> storage.performBackup(null, data, newState) }
+ verify(storage, never()).createBackupRestoreEntities()
+ assertThat(storage.entities).isNull()
+ assertThat(storage.entityStates.size).isEqualTo(0)
+ }
+
+ @Test
+ fun performBackup_enabled() {
+ val storage = spy(TestStorage())
+ val unused = performBackup { data, newState -> storage.performBackup(null, data, newState) }
+ verify(storage).createBackupRestoreEntities()
+ assertThat(storage.entities).isNull()
+ assertThat(storage.entityStates.size).isEqualTo(0)
+ }
+
+ @Test
+ fun performBackup_entityBackupWithException() {
+ val entity =
+ mock<BackupRestoreEntity> {
+ on { key } doReturn ""
+ on { backup(any(), any()) } doThrow IllegalStateException()
+ }
+ val storage = TestStorage(entity, entity1)
+
+ val (_, stateFile) =
+ performBackup { data, newState -> storage.performBackup(null, data, newState) }
+
+ assertThat(storage.readEntityStates(stateFile)).apply {
+ hasSize(1)
+ containsKey(entity1.key)
+ }
+ }
+
+ @Test
+ fun performBackup_update_unchanged() {
+ performBackupTest({}) { entityStates, newEntityStates ->
+ assertThat(entityStates).isEqualTo(newEntityStates)
+ }
+ }
+
+ @Test
+ fun performBackup_intact() {
+ performBackupTest({ entity1.backupResult = EntityBackupResult.INTACT }) {
+ entityStates,
+ newEntityStates ->
+ assertThat(entityStates).isEqualTo(newEntityStates)
+ }
+ }
+
+ @Test
+ fun performBackup_delete() {
+ performBackupTest({ entity1.backupResult = EntityBackupResult.DELETE }) { _, newEntityStates
+ ->
+ assertThat(newEntityStates.size).isEqualTo(1)
+ assertThat(newEntityStates).containsKey(entity2.key)
+ }
+ }
+
+ private fun performBackupTest(
+ update: () -> Unit,
+ verification: (Map<String, Long>, Map<String, Long>) -> Unit,
+ ) {
+ val storage = TestStorage(entity1, entity2)
+ val (_, stateFile) =
+ performBackup { data, newState -> storage.performBackup(null, data, newState) }
+
+ val entityStates = storage.readEntityStates(stateFile)
+ assertThat(entityStates).apply {
+ hasSize(2)
+ containsKey(entity1.key)
+ containsKey(entity2.key)
+ }
+
+ update.invoke()
+ val (_, newStateFile) =
+ performBackup { data, newState ->
+ stateFile.toParcelFileDescriptor(MODE_READ_ONLY).use {
+ storage.performBackup(it, data, newState)
+ }
+ }
+ verification.invoke(entityStates, storage.readEntityStates(newStateFile))
+ }
+
+ @Test
+ fun restoreEntity_disabled() {
+ val storage = spy(TestStorage().apply { enabled = false })
+ temporaryFolder.newFile().toParcelFileDescriptor(MODE_READ_ONLY).use {
+ storage.restoreEntity(it.toBackupDataInputStream())
+ }
+ verify(storage, never()).createBackupRestoreEntities()
+ assertThat(storage.entities).isNull()
+ assertThat(storage.entityStates.size).isEqualTo(0)
+ }
+
+ @Test
+ fun restoreEntity_entityNotFound() {
+ val storage = TestStorage()
+ temporaryFolder.newFile().toParcelFileDescriptor(MODE_READ_ONLY).use {
+ val backupDataInputStream = it.toBackupDataInputStream()
+ backupDataInputStream.setKey("")
+ storage.restoreEntity(backupDataInputStream)
+ }
+ }
+
+ @Test
+ fun restoreEntity_exception() {
+ val storage = TestStorage(entity1)
+ temporaryFolder.newFile().toParcelFileDescriptor(MODE_READ_ONLY).use {
+ val backupDataInputStream = it.toBackupDataInputStream()
+ backupDataInputStream.setKey(entity1.key)
+ storage.restoreEntity(backupDataInputStream)
+ }
+ }
+
+ @Test
+ fun restoreEntity_codecChanged() {
+ assertThat(entity1.codec()).isNotEqualTo(entity1NoOpCodec.codec())
+ backupAndRestore(entity1) { _, data ->
+ TestStorage(entity1NoOpCodec).apply { restoreEntity(data) }
+ }
+ assertThat(entity1.data).isEqualTo(entity1NoOpCodec.restoredData)
+ }
+
+ @Test
+ fun restoreEntity() {
+ val random = Random.Default
+ fun test(codec: BackupCodec, size: Int) {
+ val entity = Entity("key", random.nextBytes(size), codec)
+ backupAndRestore(entity)
+ entity.verifyRestoredData()
+ }
+ for (codec in allCodecs()) {
+ // test small data to ensure correctness
+ for (size in 0 until 100) test(codec, size)
+ repeat(10) { test(codec, random.nextInt(100, MAX_DATA_SIZE)) }
+ }
+ }
+
+ @Test
+ fun readEntityStates_eof_exception() {
+ val storage = TestStorage()
+ val entityStates = MutableScatterMap<String, Long>()
+ entityStates.put("", 0) // add an item to verify that exiting elements are clear
+ temporaryFolder.newFile().toParcelFileDescriptor(MODE_READ_ONLY).use {
+ storage.readEntityStates(it, entityStates)
+ }
+ assertThat(entityStates.size).isEqualTo(0)
+ }
+
+ @Test
+ fun readEntityStates_other_exception() {
+ val storage = TestStorage()
+ val entityStates = MutableScatterMap<String, Long>()
+ entityStates.put("", 0) // add an item to verify that exiting elements are clear
+ temporaryFolder.newFile().toParcelFileDescriptor(MODE_READ_ONLY).apply {
+ close() // cause exception when read state file
+ storage.readEntityStates(this, entityStates)
+ }
+ assertThat(entityStates.size).isEqualTo(0)
+ }
+
+ @Test
+ fun readEntityStates_unknownVersion() {
+ val storage = TestStorage()
+ val stateFile = temporaryFolder.newFile()
+ stateFile.toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use {
+ DataOutputStream(FileOutputStream(it.fileDescriptor))
+ .writeByte(BackupRestoreStorage.STATE_VERSION + 1)
+ }
+ val entityStates = MutableScatterMap<String, Long>()
+ entityStates.put("", 0) // add an item to verify that exiting elements are clear
+ stateFile.toParcelFileDescriptor(MODE_READ_ONLY).use {
+ storage.readEntityStates(it, entityStates)
+ }
+ assertThat(entityStates.size).isEqualTo(0)
+ }
+
+ @Test
+ fun writeNewStateDescription() {
+ val storage = spy(TestStorage())
+ // use read only mode to trigger exception when write state file
+ temporaryFolder.newFile().toParcelFileDescriptor(MODE_READ_ONLY).use {
+ storage.writeNewStateDescription(it)
+ }
+ verify(storage).onRestoreFinished()
+ }
+
+ @Test
+ fun backupAndRestore() {
+ val storage = spy(TestStorage(entity1, entity2))
+ val backupAgentHelper = BackupAgentHelper()
+ backupAgentHelper.addHelper(storage.name, storage)
+
+ // backup
+ val (dataFile, stateFile) =
+ performBackup { data, newState -> backupAgentHelper.onBackup(null, data, newState) }
+ storage.verifyFieldsArePurged()
+
+ // verify state
+ val entityStates = MutableScatterMap<String, Long>()
+ entityStates[""] = 1
+ storage.readEntityStates(null, entityStates)
+ assertThat(entityStates.size).isEqualTo(0)
+ stateFile.toParcelFileDescriptor(MODE_READ_ONLY).use {
+ storage.readEntityStates(it, entityStates)
+ }
+ assertThat(entityStates.asMap()).apply {
+ hasSize(2)
+ containsKey(entity1.key)
+ containsKey(entity2.key)
+ }
+ reset(storage)
+
+ // restore
+ val newStateFile = temporaryFolder.newFile()
+ dataFile.toParcelFileDescriptor(MODE_READ_ONLY).use { dataPfd ->
+ newStateFile.toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use {
+ backupAgentHelper.onRestore(dataPfd.toBackupDataInput(), 0, it)
+ }
+ }
+ verify(storage).onRestoreFinished()
+ storage.verifyFieldsArePurged()
+
+ // ShadowBackupDataOutput does not write data to file, so restore is bypassed
+ if (!isRobolectric()) {
+ entity1.verifyRestoredData()
+ entity2.verifyRestoredData()
+ assertThat(entityStates.asMap()).isEqualTo(storage.readEntityStates(newStateFile))
+ }
+ }
+
+ private fun backupAndRestore(
+ entity: BackupRestoreEntity,
+ restoreEntity: (TestStorage, BackupDataInputStream) -> TestStorage = { storage, data ->
+ storage.restoreEntity(data)
+ storage
+ },
+ ) {
+ val storage = TestStorage(entity)
+ val entityKey = argumentCaptor<String>()
+ val entitySize = argumentCaptor<Int>()
+ val entityData = argumentCaptor<ByteArray>()
+ val data = mock<BackupDataOutput>()
+
+ val stateFile = temporaryFolder.newFile()
+ stateFile.toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use {
+ storage.performBackup(null, data, it)
+ }
+ val entityStates = MutableScatterMap<String, Long>()
+ stateFile.toParcelFileDescriptor(MODE_READ_ONLY).use {
+ storage.readEntityStates(it, entityStates)
+ }
+ assertThat(entityStates.size).isEqualTo(1)
+
+ verify(data).writeEntityHeader(entityKey.capture(), entitySize.capture())
+ verify(data).writeEntityData(entityData.capture(), entitySize.capture())
+ assertThat(entityKey.allValues).isEqualTo(listOf(entity.key))
+ assertThat(entityData.allValues).hasSize(1)
+ val payload = entityData.firstValue
+ assertThat(entitySize.allValues).isEqualTo(listOf(payload.size, payload.size))
+
+ val dataFile = temporaryFolder.newFile()
+ dataFile.toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use {
+ FileOutputStream(it.fileDescriptor).write(payload)
+ }
+
+ newBackupDataInputStream(entity.key, payload).apply {
+ restoreEntity.invoke(storage, this).also {
+ assertThat(it.entityStates).isEqualTo(entityStates)
+ }
+ }
+ }
+
+ fun performBackup(backup: (BackupDataOutput, ParcelFileDescriptor) -> Unit): Pair<File, File> {
+ val dataFile = temporaryFolder.newFile()
+ val stateFile = temporaryFolder.newFile()
+ dataFile.toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use { dataPfd ->
+ stateFile.toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use {
+ backup.invoke(dataPfd.toBackupDataOutput(), it)
+ }
+ }
+ return dataFile to stateFile
+ }
+
+ private fun BackupRestoreStorage.verifyFieldsArePurged() {
+ assertThat(entities).isNull()
+ assertThat(entityStates.size).isEqualTo(0)
+ assertThat(entityStates.capacity).isEqualTo(0)
+ }
+
+ private fun BackupRestoreStorage.readEntityStates(stateFile: File): Map<String, Long> {
+ val entityStates = MutableScatterMap<String, Long>()
+ stateFile.toParcelFileDescriptor(MODE_READ_ONLY).use { readEntityStates(it, entityStates) }
+ return entityStates.asMap()
+ }
+
+ private fun File.toParcelFileDescriptor(mode: Int) = ParcelFileDescriptor.open(this, mode)
+
+ private fun ParcelFileDescriptor.toBackupDataOutput() = fileDescriptor.toBackupDataOutput()
+
+ private fun ParcelFileDescriptor.toBackupDataInputStream(): BackupDataInputStream =
+ BackupDataInputStream::class.java.newInstance(toBackupDataInput())
+
+ private fun ParcelFileDescriptor.toBackupDataInput() = fileDescriptor.toBackupDataInput()
+
+ private fun FileDescriptor.toBackupDataOutput(): BackupDataOutput =
+ BackupDataOutput::class.java.newInstance(this)
+
+ private fun FileDescriptor.toBackupDataInput(): BackupDataInput =
+ BackupDataInput::class.java.newInstance(this)
+}
+
+private open class TestStorage(vararg val backupRestoreEntities: BackupRestoreEntity) :
+ ObservableBackupRestoreStorage() {
+ var enabled: Boolean? = null
+
+ override val name
+ get() = "TestBackup"
+
+ override fun createBackupRestoreEntities() = backupRestoreEntities.toList()
+
+ override fun enableBackup(backupContext: BackupContext) =
+ enabled ?: super.enableBackup(backupContext)
+
+ override fun enableRestore() = enabled ?: super.enableRestore()
+}
+
+private class Entity(
+ override val key: String,
+ val data: ByteArray,
+ private val codec: BackupCodec? = null,
+) : BackupRestoreEntity {
+ var restoredData: ByteArray? = null
+ var backupResult = EntityBackupResult.UPDATE
+
+ override fun codec() = codec ?: super.codec()
+
+ override fun backup(
+ backupContext: BackupContext,
+ outputStream: OutputStream,
+ ): EntityBackupResult {
+ outputStream.write(data)
+ return backupResult
+ }
+
+ override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+ restoredData = inputStream.readBytes()
+ inputStream.close()
+ }
+
+ fun verifyRestoredData() = assertThat(restoredData).isEqualTo(data)
+}
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt
index b52586c..8638b2f 100644
--- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt
@@ -16,76 +16,58 @@
package com.android.settingslib.datastore
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import com.google.common.util.concurrent.MoreExecutors
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicInteger
import org.junit.Assert
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
-import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.reset
import org.mockito.kotlin.verify
-import org.robolectric.RobolectricTestRunner
-@RunWith(RobolectricTestRunner::class)
+@RunWith(AndroidJUnit4::class)
class KeyedObserverTest {
- @get:Rule
- val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ private val observer1 = mock<KeyedObserver<Any?>>()
+ private val observer2 = mock<KeyedObserver<Any?>>()
+ private val keyedObserver1 = mock<KeyedObserver<Any>>()
+ private val keyedObserver2 = mock<KeyedObserver<Any>>()
- @Mock
- private lateinit var observer1: KeyedObserver<Any?>
+ private val key1 = Object()
+ private val key2 = Object()
- @Mock
- private lateinit var observer2: KeyedObserver<Any?>
-
- @Mock
- private lateinit var keyedObserver1: KeyedObserver<Any>
-
- @Mock
- private lateinit var keyedObserver2: KeyedObserver<Any>
-
- @Mock
- private lateinit var key1: Any
-
- @Mock
- private lateinit var key2: Any
-
- @Mock
- private lateinit var executor: Executor
-
+ private val executor1: Executor = MoreExecutors.directExecutor()
+ private val executor2: Executor = MoreExecutors.newDirectExecutorService()
private val keyedObservable = KeyedDataObservable<Any>()
@Test
fun addObserver_sameExecutor() {
- keyedObservable.addObserver(observer1, executor)
- keyedObservable.addObserver(observer1, executor)
+ keyedObservable.addObserver(observer1, executor1)
+ keyedObservable.addObserver(observer1, executor1)
}
@Test
fun addObserver_keyedObserver_sameExecutor() {
- keyedObservable.addObserver(key1, keyedObserver1, executor)
- keyedObservable.addObserver(key1, keyedObserver1, executor)
+ keyedObservable.addObserver(key1, keyedObserver1, executor1)
+ keyedObservable.addObserver(key1, keyedObserver1, executor1)
}
@Test
fun addObserver_differentExecutor() {
- keyedObservable.addObserver(observer1, executor)
+ keyedObservable.addObserver(observer1, executor1)
Assert.assertThrows(IllegalStateException::class.java) {
- keyedObservable.addObserver(observer1, directExecutor())
+ keyedObservable.addObserver(observer1, executor2)
}
}
@Test
fun addObserver_keyedObserver_differentExecutor() {
- keyedObservable.addObserver(key1, keyedObserver1, executor)
+ keyedObservable.addObserver(key1, keyedObserver1, executor1)
Assert.assertThrows(IllegalStateException::class.java) {
- keyedObservable.addObserver(key1, keyedObserver1, directExecutor())
+ keyedObservable.addObserver(key1, keyedObserver1, executor2)
}
}
@@ -93,7 +75,7 @@
fun addObserver_weaklyReferenced() {
val counter = AtomicInteger()
var observer: KeyedObserver<Any?>? = KeyedObserver { _, _ -> counter.incrementAndGet() }
- keyedObservable.addObserver(observer!!, directExecutor())
+ keyedObservable.addObserver(observer!!, executor1)
keyedObservable.notifyChange(ChangeReason.UPDATE)
assertThat(counter.get()).isEqualTo(1)
@@ -111,7 +93,7 @@
fun addObserver_keyedObserver_weaklyReferenced() {
val counter = AtomicInteger()
var keyObserver: KeyedObserver<Any>? = KeyedObserver { _, _ -> counter.incrementAndGet() }
- keyedObservable.addObserver(key1, keyObserver!!, directExecutor())
+ keyedObservable.addObserver(key1, keyObserver!!, executor1)
keyedObservable.notifyChange(key1, ChangeReason.UPDATE)
assertThat(counter.get()).isEqualTo(1)
@@ -127,45 +109,43 @@
@Test
fun addObserver_notifyObservers_removeObserver() {
- keyedObservable.addObserver(observer1, directExecutor())
- keyedObservable.addObserver(observer2, executor)
+ keyedObservable.addObserver(observer1, executor1)
+ keyedObservable.addObserver(observer2, executor2)
keyedObservable.notifyChange(ChangeReason.UPDATE)
verify(observer1).onKeyChanged(null, ChangeReason.UPDATE)
- verify(observer2, never()).onKeyChanged(any(), any())
- verify(executor).execute(any())
+ verify(observer2).onKeyChanged(null, ChangeReason.UPDATE)
- reset(observer1, executor)
+ reset(observer1, observer2)
keyedObservable.removeObserver(observer2)
keyedObservable.notifyChange(ChangeReason.DELETE)
verify(observer1).onKeyChanged(null, ChangeReason.DELETE)
- verify(executor, never()).execute(any())
+ verify(observer2, never()).onKeyChanged(null, ChangeReason.DELETE)
}
@Test
fun addObserver_keyedObserver_notifyObservers_removeObserver() {
- keyedObservable.addObserver(key1, keyedObserver1, directExecutor())
- keyedObservable.addObserver(key2, keyedObserver2, executor)
+ keyedObservable.addObserver(key1, keyedObserver1, executor1)
+ keyedObservable.addObserver(key2, keyedObserver2, executor2)
keyedObservable.notifyChange(key1, ChangeReason.UPDATE)
verify(keyedObserver1).onKeyChanged(key1, ChangeReason.UPDATE)
- verify(keyedObserver2, never()).onKeyChanged(any(), any())
- verify(executor, never()).execute(any())
+ verify(keyedObserver2, never()).onKeyChanged(key2, ChangeReason.UPDATE)
- reset(keyedObserver1, executor)
- keyedObservable.removeObserver(key2, keyedObserver2)
+ reset(keyedObserver1, keyedObserver2)
+ keyedObservable.removeObserver(key1, keyedObserver1)
keyedObservable.notifyChange(key1, ChangeReason.DELETE)
- verify(keyedObserver1).onKeyChanged(key1, ChangeReason.DELETE)
- verify(executor, never()).execute(any())
+ verify(keyedObserver1, never()).onKeyChanged(key1, ChangeReason.DELETE)
+ verify(keyedObserver2, never()).onKeyChanged(key2, ChangeReason.DELETE)
}
@Test
fun notifyChange_addMoreTypeObservers_checkOnKeyChanged() {
- keyedObservable.addObserver(observer1, directExecutor())
- keyedObservable.addObserver(key1, keyedObserver1, directExecutor())
- keyedObservable.addObserver(key2, keyedObserver2, directExecutor())
+ keyedObservable.addObserver(observer1, executor1)
+ keyedObservable.addObserver(key1, keyedObserver1, executor1)
+ keyedObservable.addObserver(key2, keyedObserver2, executor1)
keyedObservable.notifyChange(ChangeReason.UPDATE)
verify(observer1).onKeyChanged(null, ChangeReason.UPDATE)
@@ -191,10 +171,10 @@
fun notifyChange_addObserverWithinCallback() {
// ConcurrentModificationException is raised if it is not implemented correctly
val observer: KeyedObserver<Any?> = KeyedObserver { _, _ ->
- keyedObservable.addObserver(observer1, executor)
+ keyedObservable.addObserver(observer1, executor1)
}
- keyedObservable.addObserver(observer, directExecutor())
+ keyedObservable.addObserver(observer, executor1)
keyedObservable.notifyChange(ChangeReason.UPDATE)
keyedObservable.removeObserver(observer)
@@ -204,12 +184,12 @@
fun notifyChange_KeyedObserver_addObserverWithinCallback() {
// ConcurrentModificationException is raised if it is not implemented correctly
val keyObserver: KeyedObserver<Any?> = KeyedObserver { _, _ ->
- keyedObservable.addObserver(key1, keyedObserver1, executor)
+ keyedObservable.addObserver(key1, keyedObserver1, executor1)
}
- keyedObservable.addObserver(key1, keyObserver, directExecutor())
+ keyedObservable.addObserver(key1, keyObserver, executor1)
keyedObservable.notifyChange(key1, ChangeReason.UPDATE)
keyedObservable.removeObserver(key1, keyObserver)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
index f065829..173c2b1 100644
--- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
@@ -22,40 +22,33 @@
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicInteger
import org.junit.Assert.assertThrows
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
-import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.reset
import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class ObserverTest {
- @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ private val observer1 = mock<Observer>()
+ private val observer2 = mock<Observer>()
- @Mock private lateinit var observer1: Observer
-
- @Mock private lateinit var observer2: Observer
-
- @Mock private lateinit var executor: Executor
-
+ private val executor1: Executor = MoreExecutors.directExecutor()
+ private val executor2: Executor = MoreExecutors.newDirectExecutorService()
private val observable = DataObservable()
@Test
fun addObserver_sameExecutor() {
- observable.addObserver(observer1, executor)
- observable.addObserver(observer1, executor)
+ observable.addObserver(observer1, executor1)
+ observable.addObserver(observer1, executor1)
}
@Test
fun addObserver_differentExecutor() {
- observable.addObserver(observer1, executor)
+ observable.addObserver(observer1, executor1)
assertThrows(IllegalStateException::class.java) {
- observable.addObserver(observer1, MoreExecutors.directExecutor())
+ observable.addObserver(observer1, executor2)
}
}
@@ -63,7 +56,7 @@
fun addObserver_weaklyReferenced() {
val counter = AtomicInteger()
var observer: Observer? = Observer { counter.incrementAndGet() }
- observable.addObserver(observer!!, MoreExecutors.directExecutor())
+ observable.addObserver(observer!!, executor1)
observable.notifyChange(ChangeReason.UPDATE)
assertThat(counter.get()).isEqualTo(1)
@@ -79,31 +72,27 @@
@Test
fun addObserver_notifyObservers_removeObserver() {
- observable.addObserver(observer1, MoreExecutors.directExecutor())
- observable.addObserver(observer2, executor)
+ observable.addObserver(observer1, executor1)
+ observable.addObserver(observer2, executor2)
observable.notifyChange(ChangeReason.DELETE)
verify(observer1).onChanged(ChangeReason.DELETE)
- verify(observer2, never()).onChanged(any())
- verify(executor).execute(any())
+ verify(observer2).onChanged(ChangeReason.DELETE)
- reset(observer1, executor)
+ reset(observer1, observer2)
observable.removeObserver(observer2)
observable.notifyChange(ChangeReason.UPDATE)
verify(observer1).onChanged(ChangeReason.UPDATE)
- verify(executor, never()).execute(any())
+ verify(observer2, never()).onChanged(ChangeReason.UPDATE)
}
@Test
fun notifyChange_addObserverWithinCallback() {
// ConcurrentModificationException is raised if it is not implemented correctly
- val observer = Observer { observable.addObserver(observer1, executor) }
- observable.addObserver(
- observer,
- MoreExecutors.directExecutor()
- )
+ val observer = Observer { observable.addObserver(observer1, executor1) }
+ observable.addObserver(observer, executor1)
observable.notifyChange(ChangeReason.UPDATE)
observable.removeObserver(observer)
}
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/SharedPreferencesStorageTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/SharedPreferencesStorageTest.kt
new file mode 100644
index 0000000..fec7d75
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/SharedPreferencesStorageTest.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.app.Application
+import android.content.Context
+import android.content.SharedPreferences
+import android.os.Build
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.util.concurrent.Executor
+import kotlin.random.Random
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+
+/** Tests of [SharedPreferencesStorage]. */
+@RunWith(AndroidJUnit4::class)
+class SharedPreferencesStorageTest {
+ private val random = Random.Default
+ private val application: Application = ApplicationProvider.getApplicationContext()
+ private val map =
+ mapOf(
+ "boolean" to true,
+ "float" to random.nextFloat(),
+ "int" to random.nextInt(),
+ "long" to random.nextLong(),
+ "string" to "string",
+ "set" to setOf("string"),
+ )
+
+ @After
+ fun tearDown() {
+ application.getSharedPreferences(NAME, MODE).edit().clear().applySync()
+ }
+
+ @Test
+ fun constructors() {
+ val storage1 = SharedPreferencesStorage(application, NAME, MODE)
+ val storage2 =
+ SharedPreferencesStorage(
+ application,
+ NAME,
+ application.getSharedPreferences(NAME, MODE),
+ )
+ assertThat(storage1.sharedPreferences).isSameInstanceAs(storage2.sharedPreferences)
+ }
+
+ @Test
+ fun observer() {
+ val observer = mock<KeyedObserver<Any?>>()
+ val keyedObserver = mock<KeyedObserver<Any>>()
+ val storage = SharedPreferencesStorage(application, NAME, MODE)
+ val executor: Executor = MoreExecutors.directExecutor()
+ storage.addObserver(observer, executor)
+ storage.addObserver("key", keyedObserver, executor)
+
+ storage.sharedPreferences.edit().putString("key", "string").applySync()
+ verify(observer).onKeyChanged("key", ChangeReason.UPDATE)
+ verify(keyedObserver).onKeyChanged("key", ChangeReason.UPDATE)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ storage.sharedPreferences.edit().clear().applySync()
+ verify(observer).onKeyChanged(null, ChangeReason.DELETE)
+ verify(keyedObserver).onKeyChanged("key", ChangeReason.DELETE)
+ }
+ }
+
+ @Test
+ fun prepareBackup_commitFailed() {
+ val editor = mock<SharedPreferences.Editor> { on { commit() } doReturn false }
+ val storage =
+ spy(SharedPreferencesStorage(application, NAME, MODE)) {
+ onGeneric { mergeSharedPreferences(any(), any(), any()) } doReturn editor
+ }
+ storage.prepareBackup(File(""))
+ }
+
+ @Test
+ fun backupAndRestore() {
+ fun test(codec: BackupCodec) {
+ val storage = SharedPreferencesStorage(application, NAME, MODE, codec)
+ storage.mergeSharedPreferences(storage.sharedPreferences, map, "op").commit()
+ assertThat(storage.sharedPreferences.all).isEqualTo(map)
+
+ val outputStream = ByteArrayOutputStream()
+ assertThat(storage.toBackupRestoreEntity().backup(BackupContext(mock()), outputStream))
+ .isEqualTo(EntityBackupResult.UPDATE)
+ val payload = outputStream.toByteArray()
+
+ storage.sharedPreferences.edit().clear().commit()
+ assertThat(storage.sharedPreferences.all).isEmpty()
+
+ BackupRestoreFileArchiver(application, listOf(storage), "archiver")
+ .restoreEntity(newBackupDataInputStream(storage.storageFilePath, payload))
+ assertThat(storage.sharedPreferences.all).isEqualTo(map)
+ }
+
+ for (codec in allCodecs()) test(codec)
+ }
+
+ @Test
+ fun mergeSharedPreferences_filter() {
+ val storage =
+ SharedPreferencesStorage(application, NAME, MODE) { key, value ->
+ key == "float" || value is String
+ }
+ storage.mergeSharedPreferences(storage.sharedPreferences, map, "op").apply()
+ assertThat(storage.sharedPreferences.all)
+ .containsExactly("float", map["float"], "string", map["string"])
+ }
+
+ @Test
+ fun mergeSharedPreferences_invalidSet() {
+ val storage = SharedPreferencesStorage(application, NAME, MODE, verbose = true)
+ storage
+ .mergeSharedPreferences(
+ storage.sharedPreferences,
+ mapOf<String, Any>("set" to setOf(Any())),
+ "op"
+ )
+ .apply()
+ assertThat(storage.sharedPreferences.all).isEmpty()
+ }
+
+ @Test
+ fun mergeSharedPreferences_unknownType() {
+ val storage = SharedPreferencesStorage(application, NAME, MODE)
+ storage
+ .mergeSharedPreferences(storage.sharedPreferences, map + ("key" to Any()), "op")
+ .apply()
+ assertThat(storage.sharedPreferences.all).isEqualTo(map)
+ }
+
+ @Test
+ fun mergeSharedPreferences() {
+ val storage = SharedPreferencesStorage(application, NAME, MODE, verbose = true)
+ storage.mergeSharedPreferences(storage.sharedPreferences, map, "op").apply()
+ assertThat(storage.sharedPreferences.all).isEqualTo(map)
+ }
+
+ private fun SharedPreferences.Editor.applySync() {
+ apply()
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ }
+
+ companion object {
+ private const val NAME = "pref"
+ private const val MODE = Context.MODE_PRIVATE
+ }
+}
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/TestUtils.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/TestUtils.kt
new file mode 100644
index 0000000..823d222
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/TestUtils.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.app.backup.BackupDataInput
+import android.app.backup.BackupDataInputStream
+import android.os.Build
+import java.io.ByteArrayInputStream
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.mock
+
+internal const val MAX_DATA_SIZE = 1 shl 12
+
+internal fun allCodecs() =
+ arrayOf<BackupCodec>(
+ BackupNoOpCodec(),
+ ) + zipCodecs()
+
+internal fun zipCodecs() =
+ arrayOf<BackupCodec>(
+ BackupZipCodec.DEFAULT_COMPRESSION,
+ BackupZipCodec.BEST_COMPRESSION,
+ BackupZipCodec.BEST_SPEED,
+ )
+
+internal fun <T : Any> Class<T>.newInstance(arg: Any, type: Class<*> = arg.javaClass): T =
+ getDeclaredConstructor(type).apply { isAccessible = true }.newInstance(arg)
+
+internal fun newBackupDataInputStream(
+ key: String,
+ data: ByteArray,
+ e: Exception? = null,
+): BackupDataInputStream {
+ // ShadowBackupDataOutput does not write data to file, so mock for reading data
+ val inputStream = ByteArrayInputStream(data)
+ val backupDataInput =
+ mock<BackupDataInput> {
+ on { readEntityData(any(), any(), any()) } doAnswer
+ {
+ if (e != null) throw e
+ val buf = it.arguments[0] as ByteArray
+ val offset = it.arguments[1] as Int
+ val size = it.arguments[2] as Int
+ inputStream.read(buf, offset, size)
+ }
+ }
+ return BackupDataInputStream::class
+ .java
+ .newInstance(backupDataInput, BackupDataInput::class.java)
+ .apply {
+ setKey(key)
+ setDataSize(data.size)
+ }
+}
+
+internal fun BackupDataInputStream.setKey(value: Any) {
+ val field = javaClass.getDeclaredField("key")
+ field.isAccessible = true
+ field.set(this, value)
+}
+
+internal fun BackupDataInputStream.setDataSize(dataSize: Int) {
+ val field = javaClass.getDeclaredField("dataSize")
+ field.isAccessible = true
+ field.setInt(this, dataSize)
+}
+
+internal fun isRobolectric() = Build.FINGERPRINT.contains("robolectric")
diff --git a/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml b/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml
deleted file mode 100644
index c7fbb5f..0000000
--- a/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2024 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="@color/settingslib_materialColorOnSurfaceVariant"
- android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
-</vector>
diff --git a/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml
deleted file mode 100644
index a2b9648..0000000
--- a/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2024 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeight"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:background="?android:attr/selectableItemBackground"
- android:orientation="vertical"
- android:clipToPadding="false">
-
- <LinearLayout
- android:id="@+id/icon_frame"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:minWidth="56dp"
- android:gravity="start|top"
- android:orientation="horizontal"
- android:paddingEnd="12dp"
- android:paddingTop="16dp"
- android:paddingBottom="4dp">
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:paddingTop="16dp"
- android:paddingBottom="8dp"
- android:textColor="@color/settingslib_materialColorOnSurfaceVariant"
- android:hyphenationFrequency="normalFast"
- android:lineBreakWordStyle="phrase"
- android:ellipsize="marquee" />
-
- <com.android.settingslib.widget.LinkTextView
- android:id="@+id/settingslib_learn_more"
- android:text="@string/settingslib_learn_more_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:paddingBottom="8dp"
- android:clickable="true"
- android:visibility="gone" />
- </LinearLayout>
-
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/Graph/Android.bp b/packages/SettingsLib/Graph/Android.bp
new file mode 100644
index 0000000..e2ed1e4
--- /dev/null
+++ b/packages/SettingsLib/Graph/Android.bp
@@ -0,0 +1,21 @@
+package {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "SettingsLibGraph-srcs",
+ srcs: ["src/**/*"],
+}
+
+android_library {
+ name: "SettingsLibGraph",
+ defaults: [
+ "SettingsLintDefaults",
+ ],
+ srcs: [":SettingsLibGraph-srcs"],
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.preference_preference",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SettingsLib/Graph/AndroidManifest.xml b/packages/SettingsLib/Graph/AndroidManifest.xml
new file mode 100644
index 0000000..93acb35
--- /dev/null
+++ b/packages/SettingsLib/Graph/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.graph">
+
+ <uses-sdk android:minSdkVersion="21" />
+</manifest>
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt
new file mode 100644
index 0000000..9231f40
--- /dev/null
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt
@@ -0,0 +1,70 @@
+package com.android.settingslib.graph
+
+import androidx.annotation.StringRes
+import androidx.annotation.XmlRes
+import androidx.preference.Preference
+import androidx.preference.PreferenceManager
+import androidx.preference.PreferenceScreen
+
+/** Manager to create and initialize preference screen. */
+class PreferenceScreenManager(private val preferenceManager: PreferenceManager) {
+ private val context = preferenceManager.context
+ // the map will preserve order
+ private val updaters = mutableMapOf<String, PreferenceUpdater>()
+ private val screenUpdaters = mutableListOf<PreferenceScreenUpdater>()
+
+ /** Creates an empty [PreferenceScreen]. */
+ fun createPreferenceScreen(): PreferenceScreen =
+ preferenceManager.createPreferenceScreen(context)
+
+ /** Creates [PreferenceScreen] from resource. */
+ fun createPreferenceScreen(@XmlRes xmlRes: Int): PreferenceScreen =
+ preferenceManager.inflateFromResource(context, xmlRes, null)
+
+ /** Adds updater for given preference. */
+ fun addPreferenceUpdater(@StringRes key: Int, updater: PreferenceUpdater) =
+ addPreferenceUpdater(context.getString(key), updater)
+
+ /** Adds updater for given preference. */
+ fun addPreferenceUpdater(
+ key: String,
+ updater: PreferenceUpdater,
+ ): PreferenceScreenManager {
+ updaters.put(key, updater)?.let { if (it != updater) throw IllegalArgumentException() }
+ return this
+ }
+
+ /** Adds updater for preference screen. */
+ fun addPreferenceScreenUpdater(updater: PreferenceScreenUpdater): PreferenceScreenManager {
+ screenUpdaters.add(updater)
+ return this
+ }
+
+ /** Adds a list of updaters for preference screen. */
+ fun addPreferenceScreenUpdater(
+ vararg updaters: PreferenceScreenUpdater,
+ ): PreferenceScreenManager {
+ screenUpdaters.addAll(updaters)
+ return this
+ }
+
+ /** Updates preference screen with registered updaters. */
+ fun updatePreferenceScreen(preferenceScreen: PreferenceScreen) {
+ for ((key, updater) in updaters) {
+ preferenceScreen.findPreference<Preference>(key)?.let { updater.updatePreference(it) }
+ }
+ for (updater in screenUpdaters) {
+ updater.updatePreferenceScreen(preferenceScreen)
+ }
+ }
+}
+
+/** Updater of [Preference]. */
+interface PreferenceUpdater {
+ fun updatePreference(preference: Preference)
+}
+
+/** Updater of [PreferenceScreen]. */
+interface PreferenceScreenUpdater {
+ fun updatePreferenceScreen(preferenceScreen: PreferenceScreen)
+}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt
new file mode 100644
index 0000000..9e4c1f6
--- /dev/null
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt
@@ -0,0 +1,26 @@
+package com.android.settingslib.graph
+
+import android.content.Context
+import androidx.preference.PreferenceScreen
+
+/**
+ * Interface to provide [PreferenceScreen].
+ *
+ * It is expected to be implemented by Activity/Fragment and the implementation needs to use
+ * [Context] APIs (e.g. `getContext()`, `getActivity()`) with caution: preference screen creation
+ * could happen in background service, where the Activity/Fragment lifecycle callbacks (`onCreate`,
+ * `onDestroy`, etc.) are not invoked.
+ */
+interface PreferenceScreenProvider {
+
+ /**
+ * Creates [PreferenceScreen].
+ *
+ * Preference screen creation could happen in background service. The implementation MUST use
+ * given [context] instead of APIs like `getContext()`, `getActivity()`, etc.
+ */
+ fun createPreferenceScreen(
+ context: Context,
+ preferenceScreenManager: PreferenceScreenManager,
+ ): PreferenceScreen?
+}
diff --git a/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml b/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml
new file mode 100644
index 0000000..ea15a67
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:color="@color/settingslib_materialColorOnPrimaryContainer"
+ android:alpha="?android:attr/disabledAlpha" />
+ <item android:color="@color/settingslib_materialColorOnPrimaryContainer"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml b/packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml
new file mode 100644
index 0000000..ea15a67
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:color="@color/settingslib_materialColorOnPrimaryContainer"
+ android:alpha="?android:attr/disabledAlpha" />
+ <item android:color="@color/settingslib_materialColorOnPrimaryContainer"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml
new file mode 100644
index 0000000..5192a9a
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/settingslib_materialColorSecondaryFixed" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ProfileSelector/res/color-v35/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color-v35/settingslib_tabs_indicator_color.xml
new file mode 100644
index 0000000..4b16832
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/color-v35/settingslib_tabs_indicator_color.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/settingslib_materialColorPrimaryFixed" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml b/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml
index 81ddf29..1429e3b 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+<!--Deprecated. After sdk 35 don't use it.-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/system_accent2_500" android:lStar="51" />
</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml b/packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml
new file mode 100644
index 0000000..eedc364
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Disabled status of thumb -->
+ <item android:state_enabled="false"
+ android:color="@color/settingslib_materialColorOutline"
+ android:alpha="?android:attr/disabledAlpha" />
+ <!-- Toggle off status of thumb -->
+ <item android:state_checked="false"
+ android:color="@color/settingslib_materialColorOutline" />
+ <!-- Enabled or toggle on status of thumb -->
+ <item android:color="@color/settingslib_track_on_color" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
index 037b80a..b46181e 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
@@ -13,6 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+<!--Deprecated. After sdk 35, don't use it, using materialColorOnSurfaceInverse in light theme -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/system_neutral1_500" android:lStar="98" />
</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml
index 762bb31..f0bcf0a 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+<!--Deprecated. After sdk 35 don't use it.-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/system_neutral2_500" android:lStar="45" />
</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
new file mode 100644
index 0000000..4ced9f2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+ <item android:state_selected="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+ <item android:state_activated="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+ <item android:color="@color/settingslib_materialColorSurfaceContainerHighest"/> <!-- not selected -->
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml
new file mode 100644
index 0000000..eedc364
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Disabled status of thumb -->
+ <item android:state_enabled="false"
+ android:color="@color/settingslib_materialColorOutline"
+ android:alpha="?android:attr/disabledAlpha" />
+ <!-- Toggle off status of thumb -->
+ <item android:state_checked="false"
+ android:color="@color/settingslib_materialColorOutline" />
+ <!-- Enabled or toggle on status of thumb -->
+ <item android:color="@color/settingslib_track_on_color" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml
new file mode 100644
index 0000000..230eb7d
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="?android:attr/disabledAlpha"
+ android:color="@color/settingslib_materialColorOnSurface"/>
+ <item android:color="@color/settingslib_materialColorOnSurface"/>
+</selector>
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml
new file mode 100644
index 0000000..5bd2a29
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="?android:attr/disabledAlpha"
+ android:color="@color/settingslib_materialColorOnSurfaceVariant"/>
+ <item android:color="@color/settingslib_materialColorOnSurfaceVariant"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml
new file mode 100644
index 0000000..3cb3435
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item
+ android:id="@android:id/background">
+ <shape>
+ <corners android:radius="8dp" />
+ <solid android:color="@color/settingslib_materialColorSurfaceVariant" />
+ </shape>
+ </item>
+
+ <item
+ android:id="@android:id/progress">
+ <scale android:scaleWidth="100%" android:useIntrinsicSizeAsMinimum="true">
+ <shape>
+ <corners android:radius="8dp" />
+ <solid android:color="?android:attr/textColorPrimary" />
+ <size android:width="8dp"/>
+ </shape>
+ </scale>
+ </item>
+</layer-list>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
new file mode 100644
index 0000000..285ab73
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="1dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_preference_bg_color" />
+ <corners
+ android:radius="?android:attr/dialogCornerRadius" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
new file mode 100644
index 0000000..e417307
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="1dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_preference_bg_color" />
+ <corners
+ android:topLeftRadius="0dp"
+ android:bottomLeftRadius="?android:attr/dialogCornerRadius"
+ android:topRightRadius="0dp"
+ android:bottomRightRadius="?android:attr/dialogCornerRadius" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
new file mode 100644
index 0000000..e964657
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="1dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_preference_bg_color" />
+ <corners
+ android:radius="1dp" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
new file mode 100644
index 0000000..a9d69c2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="1dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_preference_bg_color" />
+ <corners
+ android:topLeftRadius="?android:attr/dialogCornerRadius"
+ android:bottomLeftRadius="0dp"
+ android:topRightRadius="?android:attr/dialogCornerRadius"
+ android:bottomRightRadius="0dp" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
index 5411591..0a36a4f 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
@@ -29,17 +29,20 @@
<color name="settingslib_track_off_color">@android:color/system_neutral1_700</color>
<!-- Dialog accent color -->
+ <!--Deprecated. After sdk 35 don't use it, using materialColorPrimary-->
<color name="settingslib_dialog_accent">@android:color/system_accent1_100</color>
<!-- Dialog background color. -->
<color name="settingslib_dialog_background">@color/settingslib_surface_dark</color>
<!-- Dialog error color. -->
<color name="settingslib_dialog_colorError">#f28b82</color> <!-- Red 300 -->
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_colorSurfaceVariant">@android:color/system_neutral1_700</color>
<color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_700</color>
<!-- copy from accent_primary_variant_dark_device_default-->
+ <!-- TODO: deprecate it after moving into partner code-->
<color name="settingslib_accent_primary_variant">@android:color/system_accent1_300</color>
<color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_50</color>
@@ -48,7 +51,9 @@
<color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_100</color>
+ <!--Deprecated. After sdk 35, don't use it, using materialColorOnSurfaceInverse in dark theme -->
<color name="settingslib_surface_dark">@android:color/system_neutral1_800</color>
+ <!--Deprecated. After sdk 35, don't use it-->
<color name="settingslib_colorSurface">@color/settingslib_surface_dark</color>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
index beed90e..8cfe54f 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
@@ -38,7 +38,8 @@
<color name="settingslib_track_off_color">@android:color/system_surface_container_highest_dark
</color>
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurface-->
<color name="settingslib_text_color_primary_device_default">@android:color/system_on_surface_dark</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant-->
<color name="settingslib_text_color_secondary_device_default">@android:color/system_on_surface_variant_dark</color>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
index 229d9e3..7c76ea1 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
@@ -16,6 +16,34 @@
-->
<resources>
+ <!-- Material next state on color-->
+ <color name="settingslib_state_on_color">@color/settingslib_materialColorPrimaryContainer</color>
+
+ <!-- Material next state off color-->
+ <color name="settingslib_state_off_color">@color/settingslib_materialColorPrimaryContainer</color>
+
+ <!-- Material next thumb disable color-->
+ <color name="settingslib_thumb_disabled_color">@color/settingslib_materialColorOutline</color>
+
+ <!-- Material next thumb off color-->
+ <color name="settingslib_thumb_on_color">@color/settingslib_materialColorOnPrimary</color>
+
+ <!-- Material next thumb off color-->
+ <color name="settingslib_thumb_off_color">@color/settingslib_materialColorOutline</color>
+
+ <!-- Material next track on color-->
+ <color name="settingslib_track_on_color">@color/settingslib_materialColorPrimary</color>
+
+ <!-- Material next track off color-->
+ <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
+
+ <!-- Dialog background color. -->
+ <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color>
+
+ <color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color>
+
+ <color name="settingslib_text_color_preference_category_title">@color/settingslib_materialColorPrimary</color>
+
<color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_dark</color>
<color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_on_secondary_container_dark</color>
<color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_on_tertiary_container_dark</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index fe47e85..7706e0e 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -35,49 +35,57 @@
<color name="settingslib_track_off_color">@color/settingslib_switch_track_off</color>
<!-- Dialog accent color -->
+ <!--Deprecated. After sdk 35 don't use it, using materialColorPrimary-->
<color name="settingslib_dialog_accent">@android:color/system_accent1_600</color>
<!-- Dialog background color -->
<color name="settingslib_dialog_background">@color/settingslib_surface_light</color>
<!-- Dialog error color. -->
<color name="settingslib_dialog_colorError">#d93025</color> <!-- Red 600 -->
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_colorSurfaceVariant">@android:color/system_neutral2_100</color>
<color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_100</color>
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_accent_device_default_dark">@android:color/system_accent1_100</color>
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_accent_device_default_light">@android:color/system_accent1_600</color>
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_primary_dark_device_default_settings">@android:color/system_neutral1_900</color>
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_primary_device_default_settings_light">@android:color/system_neutral1_50</color>
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_accent_primary_device_default">@android:color/system_accent1_100</color>
<!-- copy from accent_primary_variant_light_device_default-->
+ <!-- TODO: deprecate it after moving into partner code-->
<color name="settingslib_accent_primary_variant">@android:color/system_accent1_600</color>
-
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_accent_secondary_device_default">@android:color/system_accent2_100</color>
-
+ <!--Deprecated. After sdk 35 don't use it.using materialColorOnSurfaceInverse in dark theme-->
<color name="settingslib_background_device_default_dark">@android:color/system_neutral1_900</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceInverse in light theme-->
<color name="settingslib_background_device_default_light">@android:color/system_neutral1_50</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurface-->
<color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_900</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant-->
<color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_700</color>
<color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_600</color>
<color name="settingslib_ripple_color">?android:attr/colorControlHighlight</color>
- <color name="settingslib_material_grey_900">#ff212121</color>
-
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_colorAccentPrimary">@color/settingslib_accent_primary_device_default</color>
-
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_colorAccentSecondary">@color/settingslib_accent_secondary_device_default</color>
+ <!--Deprecated. After sdk 35, don't use it-->
<color name="settingslib_colorSurface">@color/settingslib_surface_light</color>
<color name="settingslib_spinner_title_color">@android:color/system_neutral1_900</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
index e4befc29..a9534c3 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
@@ -17,6 +17,7 @@
<resources>
<style name="PreferenceTheme.SettingsLib" parent="@style/PreferenceThemeOverlay">
<item name="preferenceCategoryTitleTextAppearance">@style/TextAppearance.CategoryTitle.SettingsLib</item>
+ <item name="preferenceCategoryTitleTextColor">@color/settingslib_text_color_preference_category_title</item>
<item name="preferenceScreenStyle">@style/SettingsPreferenceScreen.SettingsLib</item>
<item name="preferenceCategoryStyle">@style/SettingsCategoryPreference.SettingsLib</item>
<item name="preferenceStyle">@style/SettingsPreference.SettingsLib</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml
index 24e3c46..fb637fb 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml
@@ -16,9 +16,11 @@
-->
<resources>
- <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v31" >
+ <style name="Theme.SettingsBase_v33" parent="Theme.SettingsBase_v31" >
<item name="android:spinnerStyle">@style/Spinner.SettingsLib</item>
<item name="android:spinnerItemStyle">@style/SpinnerItem.SettingsLib</item>
<item name="android:spinnerDropDownItemStyle">@style/SpinnerDropDownItem.SettingsLib</item>
</style>
+
+ <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v33" />
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
index 3709b5d..185ac3e 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
@@ -39,8 +39,8 @@
<!-- Material next track outline color-->
<color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurface-->
<color name="settingslib_text_color_primary_device_default">@android:color/system_on_surface_light</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant-->
<color name="settingslib_text_color_secondary_device_default">@android:color/system_on_surface_variant_light</color>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
index 2691344..2a6499a 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
@@ -16,12 +16,42 @@
-->
<resources>
+ <!-- Material next state on color-->
+ <color name="settingslib_state_on_color">@color/settingslib_materialColorPrimaryContainer</color>
+
+ <!-- Material next state off color-->
+ <color name="settingslib_state_off_color">@color/settingslib_materialColorPrimaryContainer</color>
+
+ <!-- Material next thumb disable color-->
+ <color name="settingslib_thumb_disabled_color">@color/settingslib_materialColorOutline</color>
+
+ <!-- Material next thumb off color-->
+ <color name="settingslib_thumb_on_color">@color/settingslib_materialColorOnPrimary</color>
+
+ <!-- Material next thumb off color-->
+ <color name="settingslib_thumb_off_color">@color/settingslib_materialColorOutline</color>
+
+ <!-- Material next track on color-->
+ <color name="settingslib_track_on_color">@color/settingslib_materialColorPrimary</color>
+
+ <!-- Material next track off color-->
+ <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
+
+ <!-- Dialog background color. -->
+ <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color>
+
+ <!-- Material next track outline color-->
+ <color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color>
+
+ <color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color>
+
+ <color name="settingslib_text_color_preference_category_title">@color/settingslib_materialColorPrimary</color>
+
<!-- The text color of spinner title -->
<color name="settingslib_spinner_title_color">@color/settingslib_materialColorOnPrimaryContainer</color>
<!-- The text color of dropdown item title -->
<color name="settingslib_spinner_dropdown_color">@color/settingslib_materialColorOnPrimaryContainer</color>
-
<color name="settingslib_materialColorOnSecondaryFixedVariant">@android:color/system_on_secondary_fixed_variant</color>
<color name="settingslib_materialColorOnTertiaryFixedVariant">@android:color/system_on_tertiary_fixed_variant</color>
<color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_light</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
index 01dfd7d..cdd5c25 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
@@ -1,31 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
-->
<resources>
- <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v31" >
- <item name="android:spinnerStyle">@style/Spinner.SettingsLib</item>
- <item name="android:spinnerItemStyle">@style/SpinnerItem.SettingsLib</item>
- <item name="android:spinnerDropDownItemStyle">@style/SpinnerDropDownItem.SettingsLib</item>
-
+ <style name="Theme.SettingsBase_v35" parent="Theme.SettingsBase_v33" >
<item name="android:colorAccent">@color/settingslib_materialColorPrimary</item>
- <!-- component module background -->
<item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainer</item>
<item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item>
<item name="android:textColorSecondary">@color/settingslib_materialColorOnSurfaceVariant</item>
<item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item>
</style>
+
+ <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v35" />
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
index bdc6a68..2284436 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
@@ -40,12 +40,13 @@
singleLine: Boolean = true,
enabled: Boolean = true,
shape: Shape = OutlinedTextFieldDefaults.shape,
+ modifier: Modifier = Modifier
+ .fillMaxWidth()
+ .padding(SettingsDimension.textFieldPadding),
onTextChange: (String) -> Unit
) {
OutlinedTextField(
- modifier = Modifier
- .fillMaxWidth()
- .padding(SettingsDimension.textFieldPadding),
+ modifier = modifier,
value = value,
onValueChange = onTextChange,
label = {
diff --git a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
index 712f6f0..ea3dbd9 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
+++ b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
@@ -20,6 +20,7 @@
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
import android.text.style.AbsoluteSizeSpan;
import android.util.AttributeSet;
import android.view.View;
@@ -174,6 +175,7 @@
bottomSummary.setVisibility(View.GONE);
} else {
bottomSummary.setVisibility(View.VISIBLE);
+ bottomSummary.setMovementMethod(LinkMovementMethod.getInstance());
bottomSummary.setText(mBottomSummary);
}
diff --git a/packages/SettingsLib/aconfig/OWNERS b/packages/SettingsLib/aconfig/OWNERS
new file mode 100644
index 0000000..ba02d20
--- /dev/null
+++ b/packages/SettingsLib/aconfig/OWNERS
@@ -0,0 +1,2 @@
+# go/android-fwk-media-solutions for info on areas of ownership.
+per-file settingslib_media_flag_declarations.aconfig = file:platform/frameworks/av:/media/janitors/media_solutions_OWNERS
diff --git a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
index 4d70aec..7aae1a6 100644
--- a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
@@ -21,3 +21,13 @@
description: "Enable Output Switcher when no media is playing."
bug: "284227163"
}
+
+flag {
+ name: "remove_unnecessary_route_scanning"
+ namespace: "media_solutions"
+ description: "Avoid active scan requests on UI components that only display route status information."
+ bug: "332515672"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index ef77a56..e4be79b 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> oor tot vol"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laaiproses word geoptimeer"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laai tans"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Laai"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Laai tans vinnig"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Gelaai"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Volgelaai"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Laai wag tans"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Beheer deur administrateur"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Beheer deur Beperkte Instellings"</string>
<string name="disabled" msgid="8017887509554714950">"Gedeaktiveer"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Foon, een staaf."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Foon, twee stawe."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Foon, drie stawe."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Foonsein is vol."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Geen data nie."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Data, een staaf."</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index b590287..368698c 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - እስኪሞላ ድረስ <xliff:g id="TIME">%2$s</xliff:g> ይቀራል"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ኃይል መሙላት እንዲተባ ተደርጓል"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - ኃይል በመሙላት ላይ"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"ያልታወቀ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ኃይል በመሙላት ላይ"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ኃይል በፍጥነት በመሙላት ላይ"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"ባትሪ ሞልቷል"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"ሙሉ ለሙሉ ኃይል ተሞልቷል"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ኃይል መሙላት በይቆይ ላይ"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"በአስተዳዳሪ ቁጥጥር የተደረገበት"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"በተገደበ ቅንብር ቁጥጥር የሚደረግበት"</string>
<string name="disabled" msgid="8017887509554714950">"ቦዝኗል"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"የስልክ አንድ አሞሌ"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"የስልክ ሁለት አሞሌ"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"የስልክ ሦስት አሞሌ"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"የስልክ አመልካች ሙሉ ነው።"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"ምንም ውሂብ የለም።"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"የውሂብ አንድ አሞሌ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 353df68..41b51a5 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - يتبقّى <xliff:g id="TIME">%2$s</xliff:g> حتى اكتمال شحن البطارية."</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - تم تحسين الشحن"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g>: جارٍ الشحن"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"غير معروف"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"جارٍ الشحن"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"جارٍ الشحن سريعًا"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"مشحونة"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"البطارية مشحونة بالكامل."</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"الشحن معلَّق"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"إعدادات يتحكم فيها المشرف"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"يتحكّم فيه إعداد محظور"</string>
<string name="disabled" msgid="8017887509554714950">"غير مفعّل"</string>
@@ -657,7 +669,7 @@
<string name="user_image_photo_selector" msgid="433658323306627093">"اختيار صورة"</string>
<string name="failed_attempts_now_wiping_device" msgid="4016329172216428897">"لقد استنفدت عدد المحاولات غير الصحيحة وسيتم حذف بيانات هذا الجهاز."</string>
<string name="failed_attempts_now_wiping_user" msgid="469060411789668050">"لقد استنفدت عدد المحاولات غير الصحيحة وسيتم حذف حساب هذا المستخدم."</string>
- <string name="failed_attempts_now_wiping_profile" msgid="7626589520888963129">"لقد استنفدت عدد المحاولات غير الصحيحة وسيتم حذف الملف الشخصي للعمل وبياناته."</string>
+ <string name="failed_attempts_now_wiping_profile" msgid="7626589520888963129">"لقد استنفدت عدد المحاولات غير الصحيحة وسيتم حذف ملف العمل وبياناته."</string>
<string name="failed_attempts_now_wiping_dialog_dismiss" msgid="2749889771223578925">"إغلاق"</string>
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"الإعداد التلقائي للجهاز"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"غير مفعّل"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"إشارة الهاتف تتكون من شريط واحد."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"إشارة الهاتف تتكون من شريطين."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"إشارة الهاتف تتكون من ثلاثة أشرطة."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"إشارة الهاتف كاملة."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"لا تتوفر بيانات."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"إشارة البيانات تتكون من شريط واحد."</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index a9ad715..b5ea37f 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"সম্পূৰ্ণ হ’বলৈ <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> বাকী আছে"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - চাৰ্জিং অপ্টিমাইজ কৰা হৈছে"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ চাৰ্জ হৈ আছে"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"অজ্ঞাত"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"চাৰ্জ কৰি থকা হৈছে"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"দ্ৰুততাৰে চাৰ্জ হৈছে"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"চাৰ্জ হ’ল"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"সম্পূৰ্ণ চাৰ্জ হৈছে"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"চাৰ্জিং স্থগিত ৰখা হৈছে"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"এডমিনৰ দ্বাৰা নিয়ন্ত্ৰিত"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"প্ৰতিবন্ধিত ছেটিঙৰ দ্বাৰা নিয়ন্ত্ৰিত"</string>
<string name="disabled" msgid="8017887509554714950">"নিষ্ক্ৰিয়"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"ফ\'ন ছিগনেলৰ এডাল দণ্ড।"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"ফ\'ন ছিগনেলৰ দুডাল দণ্ড।"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"ফ\'নৰ ছিগনেলৰ তিনিডাল দণ্ড আছে।"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"ফ\'নৰ ছিগনেল পূৰা আছে৷"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"কোনো ডেটা নাই।"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"ডেটা ছিগনেলৰ এডাল দণ্ড।"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 10ac5fc..9324252 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - tam şarj edilənədək <xliff:g id="TIME">%2$s</xliff:g> qalıb"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj optimallaşdırılıb"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj edilir"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Naməlum"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Enerji doldurma"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Sürətlə doldurulur"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Şarj edilib"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Tam şarj edilib"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Şarj gözlədilir"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Admin tərəfindən nəzarət olunur"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Məhdudlaşdırılmış Ayar ilə nəzarət edilir"</string>
<string name="disabled" msgid="8017887509554714950">"Deaktiv"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Şəbəkə bir xətdir."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Şəbəkə iki xətdir."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Şəbəkə üç xətdir."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Tam şəbəkə."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Məlumat yoxdur."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Data bir xətdir."</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 86229e8..02e5f69 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do kraja punjenja"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je optimizovano"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Punjenje"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Puni se"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo se puni"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Napunjeno"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Napunjeno do kraja"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Punjenje je na čekanju"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Kontroliše administrator"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrolišu ograničena podešavanja"</string>
<string name="disabled" msgid="8017887509554714950">"Onemogućeno"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Signal telefona ima jednu crtu."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Signal telefona od dve crte."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Signal telefona od tri crte."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Signal telefona je pun."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Nema podataka."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Signal za podatke ima jednu crtu."</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 680aaa2..2658891 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – да поўнай зарадкі засталося: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Зарадка аптымізавана"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – зараджаецца"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Невядома"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Зарадка"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Хуткая зарадка"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Зараджаны"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Акумулятар поўнасцю зараджаны"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Зарадка прыпынена"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Кантралюецца адміністратарам"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Пад кіраваннем Абмежаванага наладжвання"</string>
<string name="disabled" msgid="8017887509554714950">"Адключанае"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Адна планка на тэлефоне."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"2 планкі тэлефона."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"3 планкі тэлефона."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Поўны сігнал тэлефона."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Няма дадзеных."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Адна планка дадзеных."</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 1cfe768..85eaccd 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Оставащо време до пълно зареждане: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Зареждането е оптимизирано"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарежда се"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Зарежда се"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Зарежда се бързо"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Заредена"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Напълно заредено"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Зареждането е поставено на пауза"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Контролира се от администратор"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Управлява се чрез ограничена настройка"</string>
<string name="disabled" msgid="8017887509554714950">"Деактивирано"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Телефонът е с една чертичка."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Телефонът е с две чертички."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Телефонът е с три чертички."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Сигналът за телефона е пълен."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Няма данни."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Данните са с една чертичка."</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 6e5135a..d4485b8 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>-এ ব্যাটারি পুরো চার্জ হয়ে যাবে"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - চার্জিং অপ্টিমাইজ করা হয়েছে"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - চার্জ করা হচ্ছে"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"অজানা"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"চার্জ হচ্ছে"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"দ্রুত চার্জ হচ্ছে"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"চার্জ হয়েছে"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"সম্পূর্ণ চার্জ আছে"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"চার্জিং হোল্ডে আছে"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"প্রশাসকের দ্বারা নিয়ন্ত্রিত"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"এটি বিধিনিষেধ সেটিং থেকে নিয়ন্ত্রণ করা হয়"</string>
<string name="disabled" msgid="8017887509554714950">"অক্ষম হয়েছে"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"এক দন্ড ফোনের সংকেত রয়েছে৷"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"দুই দন্ড ফোনের সংকেত রয়েছে৷"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"তিন দন্ড ফোনের সংকেত রয়েছে৷"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"ফোনের সংকেত পূর্ণ রয়েছে৷"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"কোনো ডেটা নেই৷"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"এক দন্ড ডেটার সংকেত৷"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index d991bd6..4f9c1f8 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do potpune napunjenosti"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje je optimizirano"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo punjenje"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Napunjeno"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Potpuno napunjeno"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Punjenje je na čekanju"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Pod kontrolom administratora"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrolira ograničena postavka"</string>
<string name="disabled" msgid="8017887509554714950">"Onemogućeno"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefonski signal na jednoj crtici."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefonski signal na dvije crtice."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefonski signal na tri crtice."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Telefonski signal pun."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Nema podataka."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Prijenos podataka na jednoj crtici."</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 7388715..8a6c26c 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> per completar la càrrega"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g>: càrrega optimitzada"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g>: s\'està carregant"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Desconegut"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"S\'està carregant"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Càrrega ràpida"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Carregada"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Totalment carregada"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Càrrega en espera"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlat per l\'administrador"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlat per l\'opció de configuració restringida"</string>
<string name="disabled" msgid="8017887509554714950">"Desactivat"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Senyal de telèfon: una barra"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Senyal de telèfon: dues barres."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Senyal de telèfon: tres barres."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Senyal de telèfon: complet."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Senyal de dades: no n\'hi ha"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Senyal de dades: una barra."</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index e09d303..dc6d2d0 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabití"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – optimalizované nabíjení"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Nabíjení"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Neznámé"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíjí se"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Rychlé nabíjení"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Nabito"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Plně nabito"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Nabíjení pozastaveno"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Spravováno administrátorem"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Spravováno omezeným nastavením"</string>
<string name="disabled" msgid="8017887509554714950">"Deaktivováno"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Jedna čárka signálu telefonní sítě."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Dvě čárky signálu telefonní sítě."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Tři čárky signálu telefonní sítě."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Plný signál telefonní sítě."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Žádné datové připojení."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Jedna čárka signálu datové sítě."</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 09902b8..b4fee14 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – fuldt opladet om <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – opladning er optimeret"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – oplades"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Ukendt"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Oplader"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Oplader hurtigt"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Opladet"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fuldt opladet"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Opladningen er blevet sat på pause"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Kontrolleret af administratoren"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Styres af en begrænset indstilling"</string>
<string name="disabled" msgid="8017887509554714950">"Deaktiveret"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefon en bjælke."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefon to bjælker."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefon tre bjælker."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Telefonsignal fuldt."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Ingen data."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Data en bjælke."</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index d542d0f..91d108d 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – voll in <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Laden wird optimiert"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Wird geladen"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Unbekannt"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Wird aufgeladen"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Schnelles Aufladen"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Aufgeladen"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Vollständig geladen"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Ladevorgang angehalten"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Durch den Administrator verwaltet"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Gesteuert durch eingeschränkte Einstellung"</string>
<string name="disabled" msgid="8017887509554714950">"Deaktiviert"</string>
@@ -582,7 +594,7 @@
<string name="tv_media_transfer_default" msgid="5403053145185843843">"Standardeinstellung: Fernseher"</string>
<string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-Ausgang"</string>
<string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interne Lautsprecher"</string>
- <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Verbindung kann nicht hergestellt werden. Schalte das Gerät aus & und wieder ein."</string>
+ <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Verbindung kann nicht hergestellt werden. Schalte das Gerät aus und wieder ein."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Netzbetriebenes Audiogerät"</string>
<string name="help_label" msgid="3528360748637781274">"Hilfe und Feedback"</string>
<string name="storage_category" msgid="2287342585424631813">"Speicher"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefonsignal - ein Balken"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefonsignal - zwei Balken"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefonsignal - drei Balken"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Volle Telefonsignalstärke"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Keine Daten"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Datensignal - ein Balken"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index af21647..e74e5c3 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - Απομένουν <xliff:g id="TIME">%2$s</xliff:g> για πλήρη φόρτιση"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Η φόρτιση βελτιστοποιήθηκε"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ Φόρτιση"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Άγνωστο"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Φόρτιση"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ταχεία φόρτιση"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Φορτισμένη"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Πλήρως φορτισμένο"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Η φόρτιση τέθηκε σε αναμονή"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Ελέγχονται από το διαχειριστή"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Ελέγχεται από τη Ρύθμιση με περιορισμό"</string>
<string name="disabled" msgid="8017887509554714950">"Απενεργοποιημένο"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Μία γραμμή τηλεφώνου."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Δύο γραμμές τηλεφώνου."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Τρεις γραμμές μπαταρίας."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Πλήρες σήμα τηλεφώνου."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Δεν υπάρχουν δεδομένα."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Μία γραμμή δεδομένων."</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 93c17bd..4b4154a 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging optimised"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – charging"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully charged"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Charging on hold"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlled by admin"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by restricted setting"</string>
<string name="disabled" msgid="8017887509554714950">"Disabled"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Phone one bar."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Phone two bars."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Phone three bars."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Phone signal full."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"No data."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Data one bar."</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index e5d2e29..6a799a8 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -487,6 +487,10 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging optimized"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging"</string>
+ <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATUS">%2$s</xliff:g> - Full by <xliff:g id="TIME">%3$s</xliff:g>"</string>
+ <string name="power_charging_duration_v2" msgid="2938998284074003248">"<xliff:g id="LEVEL">%1$s</xliff:g> - Fully charged by <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_remaining_charging_duration_only_v2" msgid="5358176435722950193">"Fully charged by <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_fast_charging_duration_only_v2" msgid="6270950195810579563">"Full by <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
@@ -498,6 +502,8 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully Charged"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Charging on hold"</string>
+ <string name="battery_info_status_charging_v2" msgid="6118522107222245505">"Charging"</string>
+ <string name="battery_info_status_charging_fast_v2" msgid="1825439848151256589">"Fast charging"</string>
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlled by admin"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by Restricted Setting"</string>
<string name="disabled" msgid="8017887509554714950">"Disabled"</string>
@@ -684,6 +690,7 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Phone one bar."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Phone two bars."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Phone three bars."</string>
+ <string name="accessibility_phone_four_bars" msgid="4477202400261338403">"Phone four bars."</string>
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Phone signal full."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"No data."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Data one bar."</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 93c17bd..4b4154a 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging optimised"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – charging"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully charged"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Charging on hold"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlled by admin"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by restricted setting"</string>
<string name="disabled" msgid="8017887509554714950">"Disabled"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Phone one bar."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Phone two bars."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Phone three bars."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Phone signal full."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"No data."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Data one bar."</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 93c17bd..4b4154a 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging optimised"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – charging"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully charged"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Charging on hold"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlled by admin"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by restricted setting"</string>
<string name="disabled" msgid="8017887509554714950">"Disabled"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Phone one bar."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Phone two bars."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Phone three bars."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Phone signal full."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"No data."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Data one bar."</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 5e14648..7638057 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -487,6 +487,10 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging optimized"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging"</string>
+ <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATUS">%2$s</xliff:g> - Full by <xliff:g id="TIME">%3$s</xliff:g>"</string>
+ <string name="power_charging_duration_v2" msgid="2938998284074003248">"<xliff:g id="LEVEL">%1$s</xliff:g> - Fully charged by <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_remaining_charging_duration_only_v2" msgid="5358176435722950193">"Fully charged by <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_fast_charging_duration_only_v2" msgid="6270950195810579563">"Full by <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charging rapidly"</string>
@@ -498,6 +502,8 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully Charged"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Charging on hold"</string>
+ <string name="battery_info_status_charging_v2" msgid="6118522107222245505">"Charging"</string>
+ <string name="battery_info_status_charging_fast_v2" msgid="1825439848151256589">"Fast charging"</string>
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlled by admin"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by Restricted Setting"</string>
<string name="disabled" msgid="8017887509554714950">"Disabled"</string>
@@ -684,6 +690,7 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Phone one bar."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Phone two bars."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Phone three bars."</string>
+ <string name="accessibility_phone_four_bars" msgid="4477202400261338403">"Phone four bars."</string>
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Phone signal full."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"No data."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Data one bar."</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 682f732..bad913c 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> para completar"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carga optimizada"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Cargando"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Cargando rápidamente"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Cargada"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Carga completa"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Se detuvo la carga"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlada por el administrador"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Función controlada por configuración restringida"</string>
<string name="disabled" msgid="8017887509554714950">"Inhabilitada"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Una barra de teléfono"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Dos barras de teléfono"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Tres barras de teléfono"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Señal de teléfono completa"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"No hay datos."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Una barra de datos"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index f3a23f0..b4627d2 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> hasta la carga completa"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carga optimizada"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ Cargar"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carga rápida"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Cargada"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Carga completa"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Carga pausada"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlada por el administrador"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlado por ajustes restringidos"</string>
<string name="disabled" msgid="8017887509554714950">"Inhabilitada"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Una barra de cobertura"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Dos barras de cobertura"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Tres barras de cobertura"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Cobertura al máximo"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Sin datos"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Una barra de datos"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 8ad05d8..57ce62a 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – täislaadimiseks kulub <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – laadimine on optimeeritud"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – laadimine"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Tundmatu"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Laadimine"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Kiirlaadimine"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Laetud"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Täielikult laetud"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Laadimine on ootele pandud"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Juhib administraator"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Haldavad piiranguga seaded"</string>
<string name="disabled" msgid="8017887509554714950">"Keelatud"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefonisignaal: üks pulk."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefonisignaal: kaks pulka."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefonisignaal: kolm pulka."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Telefonisignaal on tugev."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Andmed puuduvad."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Andmesignaal: üks pulk."</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index f43d617..781457d 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> guztiz kargatu arte"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Kargatzeko modu optimizatua"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ Kargatzen"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Ezezaguna"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Kargatzen"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Bizkor kargatzen"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Kargatuta"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Erabat kargatuta"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Kargatze-prozesua zain dago"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Administratzaileak kontrolatzen du"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Ezarpen mugatuak kontrolatzen du"</string>
<string name="disabled" msgid="8017887509554714950">"Desgaituta"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefono-seinaleak barra bat du."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefono-seinaleak bi barra ditu."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefono-seinaleak hiru barra ditu."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Telefono-seinale osoa."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Ez dago daturik."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Datu-seinaleak barra bat du."</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index c404f54..f9bab28 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> تا شارژ کامل باقی مانده است"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - شارژ بهینه شده است"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - درحال شارژ"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"ناشناس"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"در حال شارژ شدن"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"درحال شارژ شدن سریع"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"شارژ کامل شد"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"کاملاً شارژ شده است"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"شارژ موقتاً متوقف شده است"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"توسط سرپرست سیستم کنترل میشود"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"با تنظیم «حالت محدود» کنترل میشود"</string>
<string name="disabled" msgid="8017887509554714950">"غیر فعال شد"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"یک نوار برای تلفن."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"دو نوار برای تلفن."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"سه نوار برای تلفن."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"قدرت امواج تلفن همراه کامل است."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"دادهای وجود ندارد."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"یک نوار برای داده."</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 6c52bf2..7377d0d 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> kunnes täynnä"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Lataus optimoitu"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladataan"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Tuntematon"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Ladataan"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Nopea lataus"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Ladattu"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Täyteen ladattu"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Lataus on pidossa"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Järjestelmänvalvoja hallinnoi tätä asetusta."</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Rajoitettujen asetusten mukaisesti"</string>
<string name="disabled" msgid="8017887509554714950">"Pois päältä"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Puhelinverkkosignaali - yksi palkki."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Puhelinverkkosignaali - kaksi palkkia."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Puhelinverkkosignaali - kolme palkkia."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Vahva puhelinverkkosignaali."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Ei datasignaalia."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Datasignaali - yksi palkki."</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 3b2a185..bfd7d9c 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la recharge complète)"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Recharge optimisée"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Recharge en cours…"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Charge en cours…"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Recharge rapide"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Chargée"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Complètement rechargée"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Recharge en pause"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Contrôlé par l\'administrateur"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Contrôlé par les paramètres restreints"</string>
<string name="disabled" msgid="8017887509554714950">"Désactivée"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Signal : faible"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Signal : moyen"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Signal : bon"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Signal excellent"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Aucun signal"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Signal faible"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 5be267d..846cc99 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - chargée à 100 %% dans <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Recharge optimisée"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - En charge"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Batterie en charge"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Charge rapide"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Chargée"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Complètement chargée"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Recharge en pause"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Contrôlé par l\'administrateur"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Contrôlé par les paramètres restreints"</string>
<string name="disabled" msgid="8017887509554714950">"Désactivée"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Signal : faible"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Signal : moyen"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Signal : bon"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Signal excellent"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Aucun signal"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Signal faible"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 1dc9ec4..ffd4e6a 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> para completar a carga)"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> (carga optimizada)"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> (cargando)"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Descoñecido"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Cargando rapidamente"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Cargada"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Carga completa"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Carga en pausa"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Opción controlada polo administrador"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Baixo o control de opcións restrinxidas"</string>
<string name="disabled" msgid="8017887509554714950">"Desactivada"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Unha barra de cobertura"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Dúas barras de cobertura"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Tres barras de cobertura"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Cobertura ao máximo"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Sen datos"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Unha barra de sinal de datos"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 9127301..c1746b3 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - પૂર્ણ ચાર્જ થવામાં <xliff:g id="TIME">%2$s</xliff:g> બાકી છે"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જિંગ ઑપ્ટિમાઇઝ કરવામાં આવ્યું છે"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જિંગ"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"અજાણ્યું"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ચાર્જ થઈ રહ્યું છે"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ઝડપથી ચાર્જ થાય છે"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"ચાર્જ થયું"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"સંપૂર્ણપણે ચાર્જ છે"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ચાર્જિંગ હોલ્ડ પર રાખવામાં આવ્યું"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"વ્યવસ્થાપક દ્વારા નિયંત્રિત"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"પ્રતિબંધિત સેટિંગ દ્વારા નિયંત્રિત"</string>
<string name="disabled" msgid="8017887509554714950">"અક્ષમ કર્યો"</string>
@@ -542,7 +554,7 @@
<string name="okay" msgid="949938843324579502">"ઓકે"</string>
<string name="done" msgid="381184316122520313">"થઈ ગયું"</string>
<string name="alarms_and_reminders_label" msgid="6918395649731424294">"અલાર્મ અને રિમાઇન્ડર"</string>
- <string name="alarms_and_reminders_switch_title" msgid="4939393911531826222">"અલાર્મ અને રિમાન્ડરના સેટિંગની મંજૂરી આપો"</string>
+ <string name="alarms_and_reminders_switch_title" msgid="4939393911531826222">"અલાર્મ અને રિમાઇન્ડરના સેટિંગની મંજૂરી આપો"</string>
<string name="alarms_and_reminders_title" msgid="8819933264635406032">"અલાર્મ અને રિમાઇન્ડર"</string>
<string name="alarms_and_reminders_footer_title" msgid="6302587438389079695">"આ ઍપને અલાર્મ સેટ કરવા અને સમય પ્રતિ સંવેદનશીલ ક્રિયાઓ શેડ્યૂલ કરવા માટે મંજૂરી આપો. આ ઍપને બૅકગ્રાઉન્ડમાં ચાલવા દે છે, જેને કારણે બૅટરીનો વધુ વપરાશ થઈ શકે છે.\n\nજો આ પરવાનગી બંધ હોય, તો આ ઍપ દ્વારા શેડ્યૂલ કરવામાં આવેલા વર્તમાન અલાર્મ અને સમય આધારિત ઇવેન્ટ કામ કરશે નહીં."</string>
<string name="keywords_alarms_and_reminders" msgid="6633360095891110611">"શેડ્યૂલ, અલાર્મ, રિમાઇન્ડર, ઘડિયાળ"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"ફોન એક બાર."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"ફોન બે બાર."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"ફોન ત્રણ બાર."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"પૂર્ણ ફોન સિગ્નલ."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"કોઈ ડેટા નથી."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"ડેટા એક બાર."</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 3f956e36..99dcbb0 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -192,7 +192,7 @@
<string name="launch_defaults_none" msgid="8049374306261262709">"कोई डिफ़ॉल्ट सेट नहीं है"</string>
<string name="tts_settings" msgid="8130616705989351312">"लेख से बोली सेटिंग"</string>
<string name="tts_settings_title" msgid="7602210956640483039">"लिखे गए शब्दों को सुनने की सुविधा"</string>
- <string name="tts_default_rate_title" msgid="3964187817364304022">"बोलने की दर"</string>
+ <string name="tts_default_rate_title" msgid="3964187817364304022">"बोलने की स्पीड"</string>
<string name="tts_default_rate_summary" msgid="3781937042151716987">"बोलने की गति तय करें"</string>
<string name="tts_default_pitch_title" msgid="6988592215554485479">"पिच"</string>
<string name="tts_default_pitch_summary" msgid="9132719475281551884">"कृत्रिम बोली के लहजे को प्रभावित करता है"</string>
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> में बैटरी पूरी चार्ज हो जाएगी"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंग को ऑप्टिमाइज़ किया गया"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्ज हो रही है"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हो रही है"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"तेज़ चार्ज हो रही है"</string>
@@ -498,12 +506,16 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"बैटरी चार्ज हो गई"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"बैटरी पूरी चार्ज है"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"फ़ोन को चार्ज होने से रोक दिया गया है"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"इसका नियंत्रण एडमिन के पास है"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"इसे पाबंदी मोड वाली सेटिंग से कंट्रोल किया जाता है"</string>
<string name="disabled" msgid="8017887509554714950">"बंद किया गया"</string>
<string name="external_source_trusted" msgid="1146522036773132905">"अनुमति है"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"अनुमति नहीं है"</string>
- <string name="install_other_apps" msgid="3232595082023199454">"अज्ञात ऐप्लिकेशन इंस्टॉल करने की अनुमति देना"</string>
+ <string name="install_other_apps" msgid="3232595082023199454">"अनजान ऐप्लिकेशन इंस्टॉल करने की अनुमति"</string>
<string name="home" msgid="973834627243661438">"सेटिंग का होम पेज"</string>
<string-array name="battery_labels">
<item msgid="7878690469765357158">"0%"</item>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"फ़ोन एक बार."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"फ़ोन दो बार."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"फोन तीन बार."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"फ़ोन सिग्नल पूरा."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"कोई डेटा नहीं."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"डेटा एक बार."</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index fcac2c8..a1dcf83 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -33,7 +33,7 @@
<string name="wifi_security_short_none_owe" msgid="8827409046261759703">"Ništa/OWE"</string>
<string name="wifi_security_short_owe" msgid="5073524307942025369">"OWE"</string>
<string name="wifi_security_short_eap_suiteb" msgid="4174071135081556115">"Suite-B-192"</string>
- <string name="wifi_security_none" msgid="7392696451280611452">"Nema"</string>
+ <string name="wifi_security_none" msgid="7392696451280611452">"Ništa"</string>
<string name="wifi_security_wep" msgid="1413627788581122366">"WEP"</string>
<string name="wifi_security_wpa" msgid="1072450904799930636">"WPA-Personal"</string>
<string name="wifi_security_wpa2" msgid="4038267581230425543">"WPA2-Personal"</string>
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do napunjenosti"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje se optimizira"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – punjenje"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Brzo punjenje"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Napunjeno"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Posve puna"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Punjenje na čekanju"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Kontrolira administrator"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrolira ograničena postavka"</string>
<string name="disabled" msgid="8017887509554714950">"Onemogućeno"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefonski signal jedan stupac."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefonski signal dva stupca."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefonski signal tri stupca."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Telefonski signal pun."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Nema podataka."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Podatkovni signal jedan stupac."</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 5cf5796..7133353 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> a teljes töltöttségig"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Optimalizált töltés"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Töltés…"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Ismeretlen"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Töltés"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Gyorstöltés"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Feltöltve"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Teljesen feltöltve"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"A töltés szünetel"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Rendszergazda által irányítva"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Korlátozott beállítás vezérli"</string>
<string name="disabled" msgid="8017887509554714950">"Letiltva"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefon egy sáv."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefon két sáv."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefon három sáv."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Telefonjel megtelt."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Nincsenek adatok."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Adat egy sáv."</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 327c048..160cb79 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> մինչև լրիվ լիցքավորումը"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Լիցքավորումն օպտիմալացված է"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> — Լիցքավորում"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Անհայտ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Լիցքավորում"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Արագ լիցքավորում"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Լիցքավորված է"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Լրիվ լիցքավորված է"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Լրցքավորումը դադարեցված է"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Վերահսկվում է ադմինիստրատորի կողմից"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Կառավարվում է սահմանափակ ռեժիմի կարգավորումներով"</string>
<string name="disabled" msgid="8017887509554714950">"Կասեցված է"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Հեռախոսի մեկ գիծ:"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Հեռախոսի երկու գիծ:"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Հեռախոսի երեք գիծ:"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Հեռախոսի ազդանշանը լիքն է:"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Տվյալներ չկան:"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Տվյալների մեկ գիծ:"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 1677985..7ee46cf 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi sampai penuh"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengisian daya dioptimalkan"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Mengisi daya"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Mengisi daya"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mengisi daya cepat"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Terisi"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Baterai Terisi Penuh"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Pengisian daya dihentikan sementara"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Dikontrol oleh admin"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Dikontrol oleh Setelan Terbatas"</string>
<string name="disabled" msgid="8017887509554714950">"Dinonaktifkan"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Ponsel satu batang."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Ponsel dua batang."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Ponsel tiga batang."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Sinyal ponsel penuh."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Tidak ada data yang diterima."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Data satu batang."</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index f4afdb5..51d1803 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> fram að fullri hleðslu"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Hleðsla fínstillt"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ Í hleðslu"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Óþekkt"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Í hleðslu"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hröð hleðsla"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Fullhlaðin"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Full hleðsla"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Hleðsla í bið"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Stjórnað af kerfisstjóra"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Stýrt af takmarkaði stillingu"</string>
<string name="disabled" msgid="8017887509554714950">"Óvirkt"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Styrkur símasambands er eitt strik."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Styrkur símasambands er tvö strik."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Styrkur símasambands er þrjú strik."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Fullur styrkur símasambands."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Engin gögn."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Sendistyrkur gagnatengingar er eitt strik."</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 8b11325..e8c5cf5 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> alla ricarica completa"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ricarica ottimizzata"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ In carica"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Sconosciuta"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"In carica"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ricarica veloce"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Carica"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Batteria completamente carica"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Ricarica in sospeso"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Gestita dall\'amministratore"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Gestita tramite impostazioni con restrizioni"</string>
<string name="disabled" msgid="8017887509554714950">"Disattivato"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefono: una barra."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefono: due barre."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefono: tre barre."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Massimo segnale telefonico."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Nessun dato."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Dati: una barra."</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 7fd38b3..72a025c 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -122,8 +122,8 @@
<string name="bluetooth_profile_opp" msgid="6692618568149493430">"העברת קבצים"</string>
<string name="bluetooth_profile_hid" msgid="2969922922664315866">"התקן קלט"</string>
<string name="bluetooth_profile_pan" msgid="1006235139308318188">"גישה לאינטרנט"</string>
- <string name="bluetooth_profile_pbap" msgid="2103406516858653017">"אישור גישה אל אנשי קשר והיסטוריית שיחות"</string>
- <string name="bluetooth_profile_pbap_summary" msgid="402819589201138227">"המידע ישמש להודעות על שיחות ועוד"</string>
+ <string name="bluetooth_profile_pbap" msgid="2103406516858653017">"אישור גישה אל אנשי הקשר והיסטוריית השיחות"</string>
+ <string name="bluetooth_profile_pbap_summary" msgid="402819589201138227">"המידע הזה ישמש למשל כדי להשמיע התראות על שיחות נכנסות"</string>
<string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"שיתוף חיבור לאינטרנט"</string>
<string name="bluetooth_profile_map" msgid="8907204701162107271">"הודעות טקסט"</string>
<string name="bluetooth_profile_sap" msgid="8304170950447934386">"גישה ל-SIM"</string>
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – הזמן הנותר לטעינה מלאה: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – הטעינה עברה אופטימיזציה"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – בטעינה"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"לא ידוע"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"בטעינה"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"הסוללה נטענת מהר"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"הסוללה טעונה"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"טעונה במלואה"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"הטעינה הושהתה"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"נמצא בשליטת מנהל מערכת"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"בשליטה של הגדרה מוגבלת"</string>
<string name="disabled" msgid="8017887509554714950">"מושבת"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"פס אחד של טלפון."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"שני פסים של טלפון."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"שלושה פסים של טלפון."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"אות הטלפון מלא."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"אין נתונים."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"פס אחד של נתונים."</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index b8aae29..80858ab 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - 完了まであと <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電が最適化されています"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電中"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"急速充電中"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"充電が完了しました"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"充電完了"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"充電を一時停止しています"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"管理者により管理されています"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"制限付き設定によって管理されています"</string>
<string name="disabled" msgid="8017887509554714950">"無効"</string>
@@ -655,9 +667,9 @@
<string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"終了時にアクティビティを保存、削除できます"</string>
<string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"アクティビティは、リセットして今すぐ削除するか、終了時に保存または削除できます"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"写真を選択"</string>
- <string name="failed_attempts_now_wiping_device" msgid="4016329172216428897">"間違えた回数が上限を超えました。このデバイスのデータが削除されます。"</string>
- <string name="failed_attempts_now_wiping_user" msgid="469060411789668050">"間違えた回数が上限を超えました。このユーザーが削除されます。"</string>
- <string name="failed_attempts_now_wiping_profile" msgid="7626589520888963129">"間違えた回数が上限を超えました。この仕事用プロファイルと関連データが削除されます。"</string>
+ <string name="failed_attempts_now_wiping_device" msgid="4016329172216428897">"試行回数が上限に達しました。このデバイスのデータが削除されます。"</string>
+ <string name="failed_attempts_now_wiping_user" msgid="469060411789668050">"試行回数が上限に達しました。このユーザーが削除されます。"</string>
+ <string name="failed_attempts_now_wiping_profile" msgid="7626589520888963129">"試行回数が上限に達しました。この仕事用プロファイルと関連データが削除されます。"</string>
<string name="failed_attempts_now_wiping_dialog_dismiss" msgid="2749889771223578925">"閉じる"</string>
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"デバイスのデフォルト"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"無効"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"電波状態:レベル1"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"電波状態:レベル2"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"電波状態:レベル3"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"電波状態:フル"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"データ信号:なし"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"データ信号:レベル1"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 35ebabd..c422b73 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> — სრულ დატენვამდე დარჩენილია <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - დატენვა ოპტიმიზირებულია"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – იტენება"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"უცნობი"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"იტენება"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"სწრაფად იტენება"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"დატენილია"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"ბოლომდე დატენილი"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"დატენვა შეჩერებულია"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"იმართება ადმინისტრატორის მიერ"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"კონტროლდება შეზღუდული რეჟიმის პარამეტრით"</string>
<string name="disabled" msgid="8017887509554714950">"გამორთული"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"ტელეფონის სიგნალი ერთ ზოლზეა."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"ტელეფონის სიგნალი ორ ზოლზეა."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"ტელეფონის სიგნალი სამ ზოლზეა."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"ტელეფონის სიგნალი სრულია."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"მონაცემები არ არის."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"თარიღი ზოლზე."</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index e79e3c5..bac01d9 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: толық зарядталуға <xliff:g id="TIME">%2$s</xliff:g> қалды"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядтау оңтайландырылды"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Зарядталып жатыр"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Белгісіз"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Зарядталуда"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Жылдам зарядтау"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Зарядталды"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Толық зарядталды."</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Зарядтау кідіртілді."</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Әкімші басқарады"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Шектелген параметрлер арқылы басқарылады."</string>
<string name="disabled" msgid="8017887509554714950">"Өшірілген"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Телефон бір баған."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Телефон екі баған."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Телефон үш баған."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Телефон сигналы толық."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Дерекқор жоқ."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Дерекқор бір баған."</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index e016eb1..0d41022 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - នៅសល់ <xliff:g id="TIME">%2$s</xliff:g> ទៀតទើបពេញ"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - បានបង្កើនប្រសិទ្ធភាពនៃការសាក"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - កំពុងសាកថ្ម"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"មិនស្គាល់"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"កំពុងសាកថ្ម"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"កំពុងសាកថ្មយ៉ាងឆាប់រហ័ស"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"បានសាកថ្មពេញ"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"បានសាកថ្មពេញ"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"កំពុងផ្អាកការសាកថ្ម"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"គ្រប់គ្រងដោយអ្នកគ្រប់គ្រង"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"គ្រប់គ្រងដោយការកំណត់ដែលបានរឹតបន្តឹង"</string>
<string name="disabled" msgid="8017887509554714950">"បិទ"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"សេវាទូរស័ព្ទមួយកាំ។"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"សេវាទូរស័ព្ទពីរកាំ។"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"សេវាទូរស័ព្ទបីកាំ។"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"សេវាទូរស័ព្ទពេញ។"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"គ្មានទិន្នន័យ។"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"ទិន្នន័យមួយកាំ។"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 2ef70ec..f28712f 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -151,7 +151,7 @@
<string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ಇನ್ಪುಟ್ಗಾಗಿ ಬಳಸು"</string>
<string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ಶ್ರವಣ ಸಾಧನಗಳಿಗಾಗಿ ಬಳಸಿ"</string>
<string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO ಗೆ ಬಳಸಿ"</string>
- <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"ಜೋಡಿಸಿ"</string>
+ <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"ಪೇರ್ ಮಾಡಿ"</string>
<string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ಜೋಡಿ ಮಾಡು"</string>
<string name="bluetooth_pairing_decline" msgid="6483118841204885890">"ರದ್ದುಮಾಡಿ"</string>
<string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"ಸಂಪರ್ಕಗೊಳಿಸಿದಾಗ, ಜೋಡಿಸುವಿಕೆಯು ನಿಮ್ಮ ಸಂಪರ್ಕಗಳು ಮತ್ತು ಕರೆ ಇತಿಹಾಸಕ್ಕೆ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸುತ್ತದೆ."</string>
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ತಿ ಚಾರ್ಜ್ ಆಗುತ್ತದೆ"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜಿಂಗ್ ಅನ್ನು ಆಪ್ಟಿಮೈಸ್ ಮಾಡಲಾಗಿದೆ"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜಿಂಗ್ ಆಗುತ್ತಿದೆ"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"ಅಪರಿಚಿತ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ವೇಗದ ಚಾರ್ಜಿಂಗ್"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"ಚಾರ್ಜ್ ಆಗಿದೆ"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"ಪೂರ್ಣವಾಗಿ ಚಾರ್ಜ್ ಆಗಿದೆ"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ಚಾರ್ಜಿಂಗ್ ಅನ್ನು ಹೋಲ್ಡ್ ಮಾಡಲಾಗಿದೆ"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"ನಿರ್ವಾಹಕರ ಮೂಲಕ ನಿಯಂತ್ರಿಸಲಾಗಿದೆ"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ನಿರ್ಬಂಧಿಸಲಾದ ಸೆಟ್ಟಿಂಗ್ ಮೂಲಕ ನಿಯಂತ್ರಿಸಲಾಗುತ್ತದೆ"</string>
<string name="disabled" msgid="8017887509554714950">"ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"ಪೋನ್ ಒಂದು ಪಟ್ಟಿ."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"ಫೋನ್ ಎರಡು ಪಟ್ಟಿಗಳು."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"ಫೋನ್ ಮೂರು ಪಟ್ಟಿಗಳು."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"ಫೋನ್ ಸಂಕೇತ ಪೂರ್ತಿ ಇದೆ."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"ಯಾವುದೇ ಡೇಟಾ ಇಲ್ಲ."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"ಡೇಟಾ ಒಂದು ಪಟ್ಟಿ."</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 881da43..e0331f5 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> 후 충전 완료"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - 충전 최적화됨"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ 충전 중"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"알 수 없음"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"충전 중"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"고속 충전 중"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"충전됨"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"완전히 충전됨"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"충전 일시중지"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"관리자가 제어"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"제한된 설정으로 제어됨"</string>
<string name="disabled" msgid="8017887509554714950">"사용 안함"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"휴대전화 신호 막대가 하나입니다."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"휴대전화 신호 막대가 두 개입니다."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"휴대전화 신호 막대가 세 개입니다."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"휴대전화의 신호가 강합니다."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"데이터가 없습니다."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"데이터 신호 막대가 하나입니다."</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 1863ed5..cbc8be1 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -281,7 +281,7 @@
<string name="oem_unlock_enable" msgid="5334869171871566731">"OEM бөгөттөн чыгаруу"</string>
<string name="oem_unlock_enable_summary" msgid="5857388174390953829">"Кайра жүктөгүчтү бөгөттөн чыгарууга уруксат берүү"</string>
<string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"OEM бөгөттөн чыгарууга уруксатпы?"</string>
- <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"ЭСКЕРТҮҮ: Бул жөндөө күйгүзүлүп турганда түзмөктү коргоо өзгөчөлүктөрү иштебейт."</string>
+ <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"ЭСКЕРТҮҮ: Бул параметр күйгүзүлүп турганда түзмөктү коргоо өзгөчөлүктөрү иштебейт."</string>
<string name="mock_location_app" msgid="6269380172542248304">"Жалган жайгашкан жерлерди көрсөткөн колдонмону тандоо"</string>
<string name="mock_location_app_not_set" msgid="6972032787262831155">"Жалган жайгашкан жерлерди көрсөткөн колдонмо жок"</string>
<string name="mock_location_app_set" msgid="4706722469342913843">"Жалган жайгашкан жерлерди көрсөткөн колдонмо: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> кийин толук кубатталат"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> — Кубаттоо жакшыртылды"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Кубатталууда"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Белгисиз"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Кубатталууда"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ыкчам кубатталууда"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Кубатталды"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Толук кубатталды"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Кубаттоо күтүү режиминде"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Администратор тарабынан көзөмөлдөнөт"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Чектелген параметр аркылуу көзөмөлдөнөт"</string>
<string name="disabled" msgid="8017887509554714950">"Өчүрүлгөн"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Телефон сигналы бир таякча."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Телефон сигналы эки таякча."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Телефон сигналы үч таякча."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Телефон сигналы толук."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Сигнал жок."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Мобилдик интернеттин сигналы бир таякча."</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index d764123..2366c13 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"ຍັງເຫຼືອອີກ <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ຈຶ່ງຈະສາກເຕັມ"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ການສາກຖືກປັບໃຫ້ເໝາະສົມແລ້ວ"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - ກຳລັງສາກໄຟ"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"ບໍ່ຮູ້ຈັກ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ກຳລັງສາກໄຟ"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ກຳລັງສາກໄຟດ່ວນ"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"ສາກເຕັມແລ້ວ"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"ສາກເຕັມແລ້ວ"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ຢຸດການສາກຊົ່ວຄາວ"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"ຄວບຄຸມໂດຍຜູ້ເບິ່ງແຍງ"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ຄວບຄຸມໂດຍການຕັ້ງຄ່າທີ່ຈຳກັດໄວ້"</string>
<string name="disabled" msgid="8017887509554714950">"ປິດການນຳໃຊ້"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"ສັນຍານນຶ່ງຂີດ."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"ສັນຍານສອງຂີດ."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"ສັນຍານສາມຂີດ."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"ສັນຍານເຕັມ."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"ບໍ່ມີຂໍ້ມູນ."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"ຂໍ້ມູນນຶ່ງຂີດ."</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index b388d964..479c850 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – liko <xliff:g id="TIME">%2$s</xliff:g>, kol bus visiškai įkrauta"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – įkrovimas optimizuotas"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – įkraunama"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Nežinomas"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Kraunasi..."</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Greitai įkraunama"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Įkrauta"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Visiškai įkrautas"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Įkrovimas pristabdytas"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Valdo administratorius"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Valdoma pagal apribotą nustatymą"</string>
<string name="disabled" msgid="8017887509554714950">"Neleidžiama"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Viena telefono juosta."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Dvi telefono juostos."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Trys telefono juostos."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Telefono signalas stiprus."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Duomenų nėra."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Viena duomenų juosta."</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index b28a2e9..f4fc898 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> — <xliff:g id="TIME">%2$s</xliff:g> līdz pilnai uzlādei"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> — uzlāde optimizēta"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> — notiek uzlāde"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Nezināms"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Uzlāde"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Notiek ātrā uzlāde"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Uzlādēts"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Pilnībā uzlādēts"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Uzlāde apturēta"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Kontrolē administrators"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrolē ierobežots iestatījums"</string>
<string name="disabled" msgid="8017887509554714950">"Atspējots"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Tālrunis: viena josla."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Tālrunis: divas joslas."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Tālrunis: trīs joslas."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Pilna piekļuve tālruņa signālam"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Nav datu."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Dati: viena josla"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 3b53006..f5ff438 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до полна батерија"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – полнењето е оптимизирано"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – се полни"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Се полни"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Брзо полнење"</string>
@@ -498,12 +506,16 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Полна"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Целосно полна"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Полнењето е паузирано"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Контролирано од администраторот"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Контролирано со ограничени поставки"</string>
<string name="disabled" msgid="8017887509554714950">"Оневозможено"</string>
<string name="external_source_trusted" msgid="1146522036773132905">"Со дозвола"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Без дозвола"</string>
- <string name="install_other_apps" msgid="3232595082023199454">"Непознати апликации"</string>
+ <string name="install_other_apps" msgid="3232595082023199454">"Инсталирање непознати апликации"</string>
<string name="home" msgid="973834627243661438">"Почетна страница за поставки"</string>
<string-array name="battery_labels">
<item msgid="7878690469765357158">"0 %"</item>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Телефон една цртичка.."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Телефон две цртички."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Телефон три цртички."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Сигналот за телефон е исполнет."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Нема податоци."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Податоци една цртичка."</string>
@@ -698,7 +712,7 @@
<string name="keyboard_layout_default_label" msgid="1997292217218546957">"Стандардно"</string>
<string name="turn_screen_on_title" msgid="3266937298097573424">"Вклучување на екранот"</string>
<string name="allow_turn_screen_on" msgid="6194845766392742639">"Дозволи вклучување на екранот"</string>
- <string name="allow_turn_screen_on_description" msgid="43834403291575164">"Дозволува одредена апликација да го вклучува екранот. Ако е дозволено, апликацијата може да го вклучува екранот во секое време без ваша намера."</string>
+ <string name="allow_turn_screen_on_description" msgid="43834403291575164">"Дозволете одредена апликација да го вклучува екранот. Ако е дозволено, апликацијата ќе може да го вклучува екранот во секое време без ваша намера."</string>
<string name="bt_le_audio_broadcast_dialog_title" msgid="5392738488989777074">"Да се прекине емитувањето на <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Ако емитувате на <xliff:g id="SWITCHAPP">%1$s</xliff:g> или го промените излезот, тековното емитување ќе запре"</string>
<string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Емитување на <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 06e7a23..40cf60e 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - പൂർണ്ണമാകാൻ <xliff:g id="TIME">%2$s</xliff:g> ശേഷിക്കുന്നു"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ചാർജിംഗ് ഒപ്റ്റിമൈസ് ചെയ്തു"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ ചാർജ് ചെയ്യുന്നു"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"അജ്ഞാതം"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ചാർജ് ചെയ്യുന്നു"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"അതിവേഗ ചാർജിംഗ്"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"ചാർജായി"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"പൂർണ്ണമായി ചാർജ് ചെയ്തു"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ചാർജിംഗ് ഹോൾഡിലാണ്"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"അഡ്മിൻ നിയന്ത്രിക്കുന്നത്"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"നിയന്ത്രിത ക്രമീകരണം ഉപയോഗിച്ച് നിയന്ത്രിക്കുന്നത്"</string>
<string name="disabled" msgid="8017887509554714950">"പ്രവർത്തനരഹിതമാക്കി"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"ഫോണിൽ ഒരു ബാർ."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"ഫോണിൽ രണ്ട് ബാർ."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"ഫോണിൽ മൂന്ന് ബാർ."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"ഫോൺ സിഗ്നൽ പൂർണ്ണമാണ്."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"ഡാറ്റാ സിഗ്നൽ ഒന്നുമില്ല."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"ഡാറ്റ ഒരു ബാർ."</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 0a23494..f4b82ea 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - дүүрэх хүртэл <xliff:g id="TIME">%2$s</xliff:g> үлдсэн"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Цэнэглэх явцыг оновчилсон"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Цэнэглэж байна"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Тодорхойгүй"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Цэнэглэж байна"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Хурдан цэнэглэж байна"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Цэнэглэсэн"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Бүрэн цэнэглэсэн"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Цэнэглэхийг хүлээлгэд оруулсан"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Админ удирдсан"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Хязгаарлагдсан тохиргоогоор хянадаг"</string>
<string name="disabled" msgid="8017887509554714950">"Идэвхгүйжүүлсэн"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Утас нэг баганатай."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Утас хоёр баганатай."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Утас гурван баганатай."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Утасны дохио дүүрэн."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Дата байхгүй."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Дата нэг баганатай."</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index b40ba9e..7ea7579 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - पूर्ण चार्ज होण्यासाठी <xliff:g id="TIME">%2$s</xliff:g> शिल्लक आहे"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्जिंग ऑप्टिमाइझ केले"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ चार्ज होत आहे"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज होत आहे"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"वेगाने चार्ज होत आहे"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"चार्ज झाली"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"पूर्ण चार्ज झाली"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"चार्जिंग थांबवले आहे"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"प्रशासकाने नियंत्रित केलेले"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"प्रतिबंधित केलेल्या सेटिंग द्वारे नियंत्रित"</string>
<string name="disabled" msgid="8017887509554714950">"अक्षम"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"फोन एक बार."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"फोन दोन बार."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"फोन तीन बार."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"फोन सिग्नल पूर्ण."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"कोणताही डेटा नाही."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"डेटा एक बार."</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 2858ab2..a64aff3 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi sebelum penuh"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Pengecasan dioptimumkan"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Mengecas"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Mengecas"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mengecas pantas"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Sudah dicas"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Dicas Penuh"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Pengecasan ditunda"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Dikawal oleh pentadbir"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Dikawal oleh Tetapan Terhad"</string>
<string name="disabled" msgid="8017887509554714950">"Dilumpuhkan"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefon satu bar."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefon dua bar."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefon tiga bar."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Isyarat telefon penuh."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Tiada data."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Data satu bar."</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 3898f8d..a94c359 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားပြည့်ရန် <xliff:g id="TIME">%2$s</xliff:g> လိုသည်"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားသွင်းခြင်းကို အကောင်းဆုံးပြင်ဆင်ထားသည်"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားသွင်းနေသည်"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"မသိ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"အားသွင်းနေပါသည်"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"အမြန် အားသွင်းနေသည်"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"အားသွင်းပြီးပါပြီ"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"အားအပြည့်သွင်းထားသည်"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"အားသွင်းခြင်းကို ခဏရပ်ထားသည်"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"စီမံခန့်ခွဲသူမှ ထိန်းချုပ်ပါသည်"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ကန့်သတ်ဆက်တင်ဖြင့် ထိန်းချုပ်ထားသည်"</string>
<string name="disabled" msgid="8017887509554714950">"ပိတ်ထားပြီး"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"ဖုန်းလိုင်းတစ်ဘား။"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"ဖုန်းလိုင်းနှစ်ဘား။"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"ဖုန်းလိုင်းသုံးဘား။"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"ဖုန်းလိုင်းအပြည့်။"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"ဒေတာမရှိပါ။"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"ဒေတာတစ်ဘား။"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 7edaba7..3b73bc3 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Fulladet om <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladingen er optimalisert"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – lader"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Ukjent"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Lader"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Lader raskt"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Ladet"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fulladet"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Ladingen er satt på vent"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Kontrollert av administratoren"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrollert av en begrenset innstilling"</string>
<string name="disabled" msgid="8017887509554714950">"Slått av"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefon – én stolpe."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefon – to stolper."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefon – tre stolper."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Telefonsignal er fullt."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Ingen data."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Data – én stolpe"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 559278a..c6a8706 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - पूरा चार्ज हुन <xliff:g id="TIME">%2$s</xliff:g> लाग्ने छ"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्ज गर्ने प्रक्रिया अप्टिमाइज गरिएको छ"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - चार्ज गरिँदै छ"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हुँदै छ"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"द्रुत गतिमा चार्ज गरिँदै छ"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"चार्ज भयो"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"पूर्ण रूपमा चार्ज भएको छ"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"चार्जिङ होल्ड गरिएको छ"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"प्रशासकद्वारा नियन्त्रित"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"प्रतिबन्धित सेटिङले नियन्त्रण गरेको"</string>
<string name="disabled" msgid="8017887509554714950">"असक्षम पारियो"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"फोन एउटा पट्टि।"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"फोन दुई पट्टि।"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"फोन तिन पट्टिहरू।"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"फोन सङ्केत भरिएको।"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"डेटा छैन।"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"डेटाको एउटा पट्टि।"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 7007677..fb7e4c2 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - vol over <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Opladen geoptimaliseerd"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ Opladen"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Opladen"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Snel opladen"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Opgeladen"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Volledig opgeladen"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Opladen in de wacht"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Ingesteld door beheerder"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Beheerd door beperkte instelling"</string>
<string name="disabled" msgid="8017887509554714950">"Uitgezet"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefoon: één streepje."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefoon: twee streepjes."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefoon: drie streepjes."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Telefoonsignaal is op volle sterkte."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Geen gegevens."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Gegevens: één streepje."</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 360810a..180d5cd 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -96,7 +96,7 @@
<string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"ସଂଯୁକ୍ତ ହେଲା (ଫୋନ୍ କିମ୍ବା ମେଡିଆ ନୁହେଁ), ବ୍ୟାଟେରୀ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string>
<string name="bluetooth_active_battery_level" msgid="3450745316700494425">"ସକ୍ରିୟ, <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ବ୍ୟାଟେରୀ"</string>
<string name="bluetooth_active_battery_level_untethered" msgid="2706188607604205362">"ସକ୍ରିୟ, L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> ବ୍ୟାଟେରୀ, R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> ବ୍ୟାଟେରୀ"</string>
- <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ବ୍ୟାଟେରୀ"</string>
+ <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ବେଟେରୀ"</string>
<string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"ବେଟେରୀ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
<string name="bluetooth_battery_level_untethered" msgid="4002282355111504349">"L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> ବ୍ୟାଟେରୀ, R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> ବ୍ୟାଟେରୀ"</string>
<string name="bluetooth_battery_level_untethered_left" msgid="2952823007648782646">"ବାମ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - ପୂର୍ଣ୍ଣ ହେବାକୁ ଆଉ <xliff:g id="TIME">%2$s</xliff:g> ବାକି ଅଛି"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ଚାର୍ଜିଂକୁ ଅପ୍ଟିମାଇଜ କରାଯାଇଛି"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ ଚାର୍ଜିଂ"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"ଅଜ୍ଞାତ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ଚାର୍ଜ ହେଉଛି"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ଶୀଘ୍ର ଚାର୍ଜ ହେଉଛି"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"ଚାର୍ଜ ହୋଇଯାଇଛି"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"ସମ୍ପୂର୍ଣ୍ଣ ଭାବରେ ଚାର୍ଜ ହୋଇଛି"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ଚାର୍ଜିଂ ହୋଲ୍ଡରେ ଅଛି"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"ଆଡ୍ମିନ୍ ଦ୍ୱାରା ନିୟନ୍ତ୍ରିତ"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ପ୍ରତିବନ୍ଧିତ ସେଟିଂ ଦ୍ୱାରା ନିୟନ୍ତ୍ରଣ କରାଯାଇଛି"</string>
<string name="disabled" msgid="8017887509554714950">"ଅକ୍ଷମ ହୋଇଛି"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"ଫୋନର ଗୋଟିଏ ବାର ଅଛି।"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"ଫୋନର ଦୁଇଟି ବାର୍ ଅଛି।"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"ଫୋନ୍ରେ ତିନୋଟି ବାର୍ ଅଛି।"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"ଫୋନ୍ ସିଗ୍ନାଲ୍ ପୂର୍ଣ୍ଣ ଅଛି।"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"କୌଣସି ଡାଟା ନାହିଁ।"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"ଡାଟାର ଗୋଟିଏ ବାର ଅଛି।"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index cbfef1e..d9be9bb 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਬੈਟਰੀ ਪੂਰੀ ਚਾਰਜ ਹੋਣ ਵਿੱਚ <xliff:g id="TIME">%2$s</xliff:g> ਬਾਕੀ"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਚਾਰਜਿੰਗ ਨੂੰ ਸੁਯੋਗ ਬਣਾਇਆ ਗਿਆ"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"ਅਗਿਆਤ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ਤੇਜ਼ ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"ਬੈਟਰੀ ਚਾਰਜ ਹੋ ਗਈ"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"ਪੂਰੀ ਚਾਰਜ ਹੋ ਗਈ ਹੈ"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ਚਾਰਜਿੰਗ ਨੂੰ ਰੋਕਿਆ ਗਿਆ ਹੈ"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਕੰਟਰੋਲ ਕੀਤੀ ਗਈ"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ਪ੍ਰਤਿਬੰਧਿਤ ਸੈਟਿੰਗ ਰਾਹੀਂ ਕੰਟਰੋਲ ਕੀਤੀ ਜਾਂਦੀ ਹੈ"</string>
<string name="disabled" msgid="8017887509554714950">"ਅਯੋਗ ਬਣਾਇਆ"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"ਫ਼ੋਨ ਇੱਕ ਬਾਰ।"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"ਫ਼ੋਨ ਦੋ ਬਾਰਸ।"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"ਫ਼ੋਨ ਤਿੰਨ ਬਾਰਸ।"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"ਫ਼ੋਨ ਸਿਗਨਲ ਪੂਰਾ।"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"ਕੋਈ ਡਾਟਾ ਨਹੀਂ।"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">" ਡਾਟਾ ਇੱਕ ਬਾਰ।"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 22015b3..2eccb04 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -487,6 +487,10 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do pełnego naładowania"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – ładowanie zoptymalizowane"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – ładowanie"</string>
+ <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATUS">%2$s</xliff:g> – Bateria będzie pełna do <xliff:g id="TIME">%3$s</xliff:g>"</string>
+ <string name="power_charging_duration_v2" msgid="2938998284074003248">"<xliff:g id="LEVEL">%1$s</xliff:g> – Pełne naładowanie do <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_remaining_charging_duration_only_v2" msgid="5358176435722950193">"Pełne naładowanie do <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_fast_charging_duration_only_v2" msgid="6270950195810579563">"Bateria będzie pełna do <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="battery_info_status_unknown" msgid="268625384868401114">"Nieznane"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Ładowanie"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Szybkie ładowanie"</string>
@@ -498,6 +502,8 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Naładowana"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Bateria w pełni naładowana"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Ładowanie wstrzymane"</string>
+ <string name="battery_info_status_charging_v2" msgid="6118522107222245505">"Ładowanie"</string>
+ <string name="battery_info_status_charging_fast_v2" msgid="1825439848151256589">"Szybkie ładowanie"</string>
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Kontrolowane przez administratora"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Obowiązują ustawienia z ograniczonym dostępem"</string>
<string name="disabled" msgid="8017887509554714950">"Wyłączone"</string>
@@ -684,6 +690,7 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefon: jeden pasek."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefon: dwa paski."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefon: trzy paski."</string>
+ <string name="accessibility_phone_four_bars" msgid="4477202400261338403">"Cztery słupki w telefonie."</string>
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Telefon: pełna moc sygnału."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Brak danych."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Dane: jeden pasek."</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index d1feae4..f37764a 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a conclusão"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carregamento otimizado"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> (carregando)"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregando rápido"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Carregada"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Carga completa"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Carregamento suspenso"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlada pelo admin"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlada pelas configurações restritas"</string>
<string name="disabled" msgid="8017887509554714950">"Desativado"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Uma barra de sinal do telefone."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Duas barras de sinal do telefone."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Três barras de sinal do telefone."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Sinal do telefone cheio."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Nenhum dado."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Uma barra de sinal de dados."</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 444ce66..850b6f3 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> até à carga máxima"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g>: carregamento otimizado"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – A carregar"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"A carregar"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregamento rápido"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Carregada"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Totalmente carregada"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Carregamento em espera"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlado pelo gestor"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlado por uma definição restrita"</string>
<string name="disabled" msgid="8017887509554714950">"Desativada"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Uma barra de telefone."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Duas barras de telefone."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Três barras de telefone."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Sinal de telefone completo."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Sem dados."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Uma barra de dados."</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index d1feae4..f37764a 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a conclusão"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Carregamento otimizado"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> (carregando)"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Carregando rápido"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Carregada"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Carga completa"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Carregamento suspenso"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlada pelo admin"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlada pelas configurações restritas"</string>
<string name="disabled" msgid="8017887509554714950">"Desativado"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Uma barra de sinal do telefone."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Duas barras de sinal do telefone."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Três barras de sinal do telefone."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Sinal do telefone cheio."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Nenhum dado."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Uma barra de sinal de dados."</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 06e61ca..e7101eb 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> până la finalizare"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Încărcare optimizată"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Se încarcă"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Necunoscut"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Se încarcă"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Se încarcă rapid"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Încărcată"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Complet încărcată"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Încărcare întreruptă"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlată de administrator"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlată de setarea restricționată"</string>
<string name="disabled" msgid="8017887509554714950">"Dezactivată"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Semnal pentru telefon: o bară."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Semnal pentru telefon: două bare."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Semnal pentru telefon: trei bare."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Semnal pentru telefon: complet."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Nu există semnal pentru date."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Semnal pentru date: o bară."</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 8bb5b19..c77becf 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до полной зарядки"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядка оптимизирована"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – заряжается"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Идет зарядка"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Быстрая зарядка"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Батарея заряжена"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Батарея заряжена"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Зарядка приостановлена"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Контролируется администратором"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Контролируется настройками с ограниченным доступом"</string>
<string name="disabled" msgid="8017887509554714950">"Отключено"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Сигнал телефонной сети: одно деление."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Сигнал телефонной сети: два деления."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Сигнал телефонной сети: три деления."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Надежный телефонный сигнал."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Сигнал передачи данных отсутствует."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Сигнал передачи данных: одно деление."</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 074028c..2f72f01 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - සම්පූර්ණ වීමට <xliff:g id="TIME">%2$s</xliff:g>ක් ඉතිරියි"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණය ප්රශස්ත කර ඇත"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණය වේ"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"නොදනී"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ආරෝපණය වෙමින්"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"ශීඝ්ර ආරෝපණය"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"අරෝපිතයි"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"සම්පූර්ණයෙන් ආරෝපණ වී ඇත"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ආරෝපණය රදවාගෙන ඇත"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"පරිපාලක විසින් පාලනය කරන ලදී"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"සීමා කළ සැකසීම මගින් පාලනය වේ"</string>
<string name="disabled" msgid="8017887509554714950">"අබල කර ඇත"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"දුරකථනය තීරු එකයි."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"දුරකථනය තීරු දෙකයි."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"දුරකථනය තීරු තුනයි."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"දුරකථනයේ සංඥාව පිරී ඇත."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"දත්ත නැත."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"දත්ත තීරු එකයි."</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index dab6e97..9b143b7 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabitia"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Nabíjanie je optimalizované"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – nabíja sa"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Neznáme"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíja sa"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Rýchle nabíjanie"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Nabité"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Úplne nabitá"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Nabíjanie je pozastavené"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Ovládané správcom"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Ovládané obmedzeným nastavením"</string>
<string name="disabled" msgid="8017887509554714950">"Deaktivované"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Jeden stĺpec signálu telefónnej siete."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Dve čiarky signálu telefónnej siete."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Tri čiarky signálu telefónnej siete."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Plný signál telefónnej siete."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Žiadna dátová sieť."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Jedna čiarka signálu dátovej siete."</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index b842203..6e3f21b 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – še <xliff:g id="TIME">%2$s</xliff:g> do napolnjenosti"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – polnjenje je optimizirano"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – polnjenje"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Neznano"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Polnjenje"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hitro polnjenje"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Napolnjeno"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Popolnoma napolnjena"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Polnjenje je na čakanju"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Nadzira skrbnik"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Pod nadzorom omejene nastavitve"</string>
<string name="disabled" msgid="8017887509554714950">"Onemogočeno"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefon z eno črtico."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefon z dvema črticama."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefon s tremi črticami."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Signal telefona je poln."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Ni podatkov."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Podatkovni signal z eno črtico."</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 54ea2e7..82b3cb2 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> derisa të mbushet"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Karikimi u optimizua"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Po karikohet"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"I panjohur"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Po karikohet"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Karikim i shpejtë"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Karikuar"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Karikuar plotësisht"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Karikimi në pritje"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Kontrolluar nga administratori"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kontrollohet nga \"Cilësimet e kufizuara\""</string>
<string name="disabled" msgid="8017887509554714950">"Çaktivizuar"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefoni ka edhe një vijë."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefoni ka dy vija."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefoni ka tre vija."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Sinjali i telefonit është i plotë."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Nuk ka të dhëna."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Sinjali është vetëm një vijë."</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 3df0824..0437a14 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до краја пуњења"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – пуњење је оптимизовано"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Пуњење"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Пуни се"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Брзо се пуни"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Напуњено"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Напуњено до краја"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Пуњење је на чекању"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Контролише администратор"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Контролишу ограничена подешавања"</string>
<string name="disabled" msgid="8017887509554714950">"Онемогућено"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Сигнал телефона има једну црту."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Сигнал телефона од две црте."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Сигнал телефона од три црте."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Сигнал телефона је пун."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Нема података."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Сигнал за податке има једну црту."</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 2a57893..5e6db36 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> kvar tills fulladdat"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Laddningen har optimerats"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – laddas"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Okänd"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Laddar"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Laddas snabbt"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Laddat"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fulladdad"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Laddningen har pausats"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Strys av administratören"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Styrs av spärrad inställning"</string>
<string name="disabled" msgid="8017887509554714950">"Inaktiverad"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefon: en stapel."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefon: två staplar."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefon: tre staplar."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Telefonsignalen är full."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Inga data."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Data: en stapel."</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index ab518d0..f8307e2 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> zimesalia ijae chaji"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Hali ya kuchaji imeboreshwa"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Inachaji"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Haijulikani"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Inachaji"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Inachaji kwa kasi"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Imechajiwa"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Imejaa Chaji"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Imesitisha kuchaji"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Imedhibitiwa na msimamizi"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Imedhibitiwa na Mpangilio wenye Mipaka"</string>
<string name="disabled" msgid="8017887509554714950">"Imezimwa"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Mwambaa mmoja wa simu."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Miambaa miwili ya simu"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Miambaa mitatu ya simu."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Ishara ya simu imejaa."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Hakuna data."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Upapi mmoja wa habari"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 1462b73..c6896a2 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - முழுவதும் சார்ஜாக <xliff:g id="TIME">%2$s</xliff:g> ஆகும்"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - சார்ஜிங் மேம்படுத்தப்பட்டது"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ சார்ஜாகிறது"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"அறியப்படாத"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"சார்ஜ் ஆகிறது"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"வேகமாக சார்ஜாகிறது"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"சார்ஜாகிவிட்டது"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"முழுவதும் சார்ஜாகிவிட்டது"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"சார்ஜிங் இடைநிறுத்தப்பட்டுள்ளது"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"நிர்வாகி கட்டுப்படுத்துகிறார்"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"வரையறுக்கப்பட்ட அமைப்பால் கட்டுப்படுத்தப்படுகிறது"</string>
<string name="disabled" msgid="8017887509554714950">"முடக்கப்பட்டது"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"சிக்னல் ஒரு கோட்டில் உள்ளது."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"சிக்னல் இரண்டு கோட்டில் உள்ளது."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"சிக்னல் மூன்று கோட்டில் உள்ளது."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"சிக்னல் முழுமையாக உள்ளது."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"டேட்டா சிக்னல் இல்லை."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"தரவு சிக்னல் ஒரு கோட்டில் உள்ளது."</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 718442c..c61cf2e 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జింగ్ ఆప్టిమైజ్ చేయబడింది"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జ్ అవుతోంది"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"తెలియదు"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ఛార్జ్ అవుతోంది"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"వేగవంతమైన ఛార్జింగ్"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"ఛార్జ్ చేయబడింది"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"పూర్తి ఛార్జ్ అయింది"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"ఛార్జింగ్ హోల్డ్లో ఉంది"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"నిర్వాహకుని ద్వారా నియంత్రించబడింది"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"పరిమితం చేసిన సెట్టింగ్ ద్వారా నియంత్రించబడుతుంది"</string>
<string name="disabled" msgid="8017887509554714950">"డిజేబుల్ చేయబడింది"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"ఫోన్ ఒక బారు."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"ఫోన్ రెండు బార్లు."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"ఫోన్ మూడు బార్లు."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"ఫోన్ సిగ్నల్ పూర్తిగా ఉంది."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"డేటా లేదు."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"డేటా ఒక బారు."</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 6cbed5a..74087e5 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - อีก <xliff:g id="TIME">%2$s</xliff:g> จึงจะเต็ม"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - ปรับการชาร์จให้เหมาะสมแล้ว"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ กำลังชาร์จ"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"ไม่ทราบ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"กำลังชาร์จ"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"กำลังชาร์จอย่างเร็ว"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"ชาร์จแล้ว"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"ชาร์จเต็มแล้ว"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"หยุดการชาร์จชั่วคราว"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"ผู้ดูแลระบบเป็นผู้ควบคุม"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"ควบคุมโดยการตั้งค่าที่จำกัด"</string>
<string name="disabled" msgid="8017887509554714950">"ปิดอยู่"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"สัญญาณโทรศัพท์หนึ่งขีด"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"สัญญาณโทรศัพท์สองขีด"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"สัญญาณโทรศัพท์สามขีด"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"สัญญาณโทรศัพท์เต็ม"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"ไม่มีข้อมูล"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"สัญญาณข้อมูลหนึ่งขีด"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index db07e7a..aaa2bd0 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> na lang bago mapuno"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Naka-optimize ang pag-charge"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Nagcha-charge"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Hindi Kilala"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Nagcha-charge"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Mabilis na charge"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Puno ang Baterya"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Naka-hold ang pag-charge"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Pinapamahalaan ng admin"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kinokontrol ng Pinaghihigpitang Setting"</string>
<string name="disabled" msgid="8017887509554714950">"Naka-disable"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telepono na isang bar."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telepono na dalawang bar."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telepono na tatlong bar."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Puno ang signal ng telepono."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Walang data."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Data na isang bar."</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 4a6e903..bf990c7 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - Tamamen şarj olmasına <xliff:g id="TIME">%2$s</xliff:g> kaldı"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj işlemi optimize edildi"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ Şarj ediliyor"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Bilinmiyor"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Şarj oluyor"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hızlı şarj oluyor"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Şarj oldu"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Pilin Şarjı Tam"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Şarj işlemi beklemede"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Yönetici tarafından denetleniyor"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kısıtlanmış ayar tarafından kontrol ediliyor"</string>
<string name="disabled" msgid="8017887509554714950">"Devre dışı"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefon sinyali bir çubuk."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefon sinyali iki çubuk."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefon sinyali üç çubuk."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Telefon sinyali tam."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Veri yok."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Veri sinyali bir çubuk."</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index ec1f7f5..d945b5f 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до повного заряду"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – заряджання оптимізовано"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – заряджається"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Невідомо"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Заряджається"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Швидке заряджання"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Заряджено"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Повністю заряджено"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Заряджання призупинено"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Керується адміністратором"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Керується налаштуваннями з обмеженнями"</string>
<string name="disabled" msgid="8017887509554714950">"Вимкнено"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Одна смужка сигналу телефону."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Дві смужки сигналу телефону."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Три смужки сигналу телефону."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Максимальний сигнал телефону."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Немає сигналу даних."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Одна смужка сигналу даних."</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index db8c427..ab97d02 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"مکمل چارج ہونے میں <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> باقی ہے"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - چارجنگ کو بہتر بنایا گیا"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - چارج ہو رہی ہے"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"نامعلوم"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"چارج ہو رہا ہے"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"تیزی سے چارج ہو رہا ہے"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"چارج ہو گئی"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"مکمل طور پر چارج ہو گئی"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"چارجنگ ہولڈ پر ہے"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"کنٹرول کردہ بذریعہ منتظم"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"محدود کردہ ترتیب کے زیر انتظام ہے"</string>
<string name="disabled" msgid="8017887509554714950">"غیر فعال"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"فون کا ایک بار۔"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"فون کے دو بارز۔"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"فون کے تین بارز۔"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"فون سگنل پورا ہے۔"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"کوئی ڈیٹا نہیں ہے۔"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"ڈیٹا کا ایک بار۔"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 2355fc9..0bfa951 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Toʻlishiga <xliff:g id="TIME">%2$s</xliff:g> qoldi"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Quvvatlash optimallashtirildi"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - Quvvatlanmoqda"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Noma’lum"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Quvvat olmoqda"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Tezkor quvvat olmoqda"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Quvvat oldi"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Toʻliq quvvatlandi"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Quvvatlash toʻxtatildi"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Administrator tomonidan boshqariladi"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Cheklangan sozlama tomonidan boshqariladi"</string>
<string name="disabled" msgid="8017887509554714950">"Oʻchiq"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Telefon bitta panelda."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Telefon ikkita panelda."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Telefon uchta panelda."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Telefon signali to‘liq."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Ma’lumotlar yo‘q."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Ma’lumotlar bitta panelda."</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 2427bea..d0062d45 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> nữa là pin đầy"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> – Quá trình sạc được tối ưu hoá"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – Đang sạc"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Không xác định"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Đang sạc"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Đang sạc nhanh"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Đã sạc"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Đã sạc đầy"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Đang tạm ngưng sạc"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Do quản trị viên kiểm soát"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Do chế độ Cài đặt hạn chế kiểm soát"</string>
<string name="disabled" msgid="8017887509554714950">"Đã tắt"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Tín hiệu điện thoại một vạch."</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Tín hiệu điện thoại hai vạch."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Tín hiệu điện thoại ba vạch."</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Tín hiệu điện thoại đầy đủ."</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Không có dữ liệu."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Tín hiệu dữ liệu một vạch."</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index c3e9e20..a291ede 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -313,7 +313,7 @@
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="2040810756832027227">"正在流式传输:<xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="select_private_dns_configuration_title" msgid="7887550926056143018">"专用 DNS"</string>
<string name="select_private_dns_configuration_dialog_title" msgid="3731422918335951912">"选择专用 DNS 模式"</string>
- <string name="private_dns_mode_off" msgid="7065962499349997041">"已关闭"</string>
+ <string name="private_dns_mode_off" msgid="7065962499349997041">"关闭"</string>
<string name="private_dns_mode_opportunistic" msgid="1947864819060442354">"自动"</string>
<string name="private_dns_mode_provider" msgid="3619040641762557028">"专用 DNS 提供商主机名"</string>
<string name="private_dns_mode_provider_hostname_hint" msgid="6564868953748514595">"输入 DNS 提供商的主机名"</string>
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - 还需<xliff:g id="TIME">%2$s</xliff:g>充满"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充电方式已优化"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - 正在充电"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"正在充电"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"正在快速充电"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"已充满电"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"已充满电"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"充电已暂停"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"由管理员控制"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"由受限设置控制"</string>
<string name="disabled" msgid="8017887509554714950">"已停用"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"手机信号强度为一格。"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"手机信号强度为两格。"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"手机信号强度为三格。"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"手机信号满格。"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"没有数据网络信号。"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"数据信号强度为一格。"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index cc1dc11..2054136 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充滿電"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - 已優化充電"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ 充電中"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"快速充電中"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"已充滿電"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"充電完成"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"目前暫停充電"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"已由管理員停用"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"由「受限設定」控制"</string>
<string name="disabled" msgid="8017887509554714950">"已停用"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"電話訊號強度為一格。"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"電話訊號強度為兩格。"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"電話訊號強度為三格。"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"電話訊號滿格。"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"沒有數據網絡。"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"數據網絡訊號強度為一格。"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 26b7907..9996577 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充飽"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電效能已最佳化"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電中"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"快速充電中"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"充電完成"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"充電完成"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"目前暫停充電"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"已由管理員停用"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"由受限制的設定控管"</string>
<string name="disabled" msgid="8017887509554714950">"已停用"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"電話訊號強度一格。"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"電話訊號強度兩格。"</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"電話訊號強度三格。"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"電話訊號滿格。"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"沒有數據網路。"</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"數據網路訊號強度一格。"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index d42202d..3cac220 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -487,6 +487,14 @@
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> okusele kuze kugcwale"</string>
<string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Ukushaja kuthuthukisiwe"</string>
<string name="power_charging_future_paused" msgid="1809543660923642799">"Iku-<xliff:g id="LEVEL">%1$s</xliff:g> ‑ Iyashaja"</string>
+ <!-- no translation found for power_fast_charging_duration_v2 (3797735998640359490) -->
+ <skip />
+ <!-- no translation found for power_charging_duration_v2 (2938998284074003248) -->
+ <skip />
+ <!-- no translation found for power_remaining_charging_duration_only_v2 (5358176435722950193) -->
+ <skip />
+ <!-- no translation found for power_remaining_fast_charging_duration_only_v2 (6270950195810579563) -->
+ <skip />
<string name="battery_info_status_unknown" msgid="268625384868401114">"Akwaziwa"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Iyashaja"</string>
<string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Ishaja ngokushesha"</string>
@@ -498,6 +506,10 @@
<string name="battery_info_status_full" msgid="1339002294876531312">"Kushajiwe"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Ishaje Ngokuphelele"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Ukushaja kumisiwe"</string>
+ <!-- no translation found for battery_info_status_charging_v2 (6118522107222245505) -->
+ <skip />
+ <!-- no translation found for battery_info_status_charging_fast_v2 (1825439848151256589) -->
+ <skip />
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Kulawulwa umqondisi"</string>
<string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Kulawulwe Isethingi Elikhawulelwe"</string>
<string name="disabled" msgid="8017887509554714950">"Akusebenzi"</string>
@@ -684,6 +696,8 @@
<string name="accessibility_phone_one_bar" msgid="5719721147018970063">"Ibha eyodwa yefoni"</string>
<string name="accessibility_phone_two_bars" msgid="2531458337458953263">"Amabha amabilil efoni."</string>
<string name="accessibility_phone_three_bars" msgid="1523967995996696619">"Amabha amathathu efoni"</string>
+ <!-- no translation found for accessibility_phone_four_bars (4477202400261338403) -->
+ <skip />
<string name="accessibility_phone_signal_full" msgid="4302338883816077134">"Isiginali yefoni igcwele"</string>
<string name="accessibility_no_data" msgid="4563181886936931008">"Ayikho idatha."</string>
<string name="accessibility_data_one_bar" msgid="6892888138070752480">"Idatha enye yebha"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
index f73081a..169c330 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
@@ -51,6 +51,7 @@
};
private static final int[] MICROPHONE_OPS = new int[]{
AppOpsManager.OP_RECORD_AUDIO,
+ AppOpsManager.OP_PHONE_CALL_MICROPHONE,
};
private static final int[] CAMERA_OPS = new int[]{
AppOpsManager.OP_CAMERA,
@@ -144,6 +145,11 @@
if (!showSystemApps) {
for (int op : mOps) {
final String permission = AppOpsManager.opToPermission(op);
+ if (permission == null) {
+ // Some ops like OP_PHONE_CALL_MICROPHONE don't have corresponding
+ // permissions. No need to check in this case.
+ continue;
+ }
final int permissionFlags = mPackageManager.getPermissionFlags(permission,
packageName,
user);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index 34c60a1..9faebe2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -381,6 +381,14 @@
});
}
+ /** Gets devices with matched connection states. */
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) {
+ if (mService == null) {
+ return new ArrayList<BluetoothDevice>(0);
+ }
+ return mService.getDevicesMatchingConnectionStates(states);
+ }
+
public boolean isEnabled(BluetoothDevice device) {
if (mService == null || device == null) {
return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
index 8fd4e91..822a6088 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
@@ -18,14 +18,18 @@
import static android.provider.DeviceConfig.NAMESPACE_ACTIVITY_MANAGER;
+import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.os.IDeviceIdleController;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.telecom.DefaultDialerManager;
import android.text.TextUtils;
@@ -121,6 +125,14 @@
return true;
}
+ if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) {
+ // App is subject to DevicePolicyManager.setUserControlDisabledPackages() policy.
+ final int userId = UserHandle.getUserId(uid);
+ if (mAppContext.getPackageManager().isPackageStateProtected(pkg, userId)) {
+ return true;
+ }
+ }
+
return false;
}
@@ -163,27 +175,77 @@
/**
* Add app into power save allow list.
- * @param pkg packageName
+ * @param pkg packageName of the app
*/
+ // TODO: Fix all callers to pass in UID
public void addApp(String pkg) {
+ addApp(pkg, Process.INVALID_UID);
+ }
+
+ /**
+ * Add app into power save allow list.
+ * @param pkg packageName of the app
+ * @param uid uid of the app
+ */
+ public void addApp(String pkg, int uid) {
try {
+ if (android.app.Flags.appRestrictionsApi()) {
+ if (uid == Process.INVALID_UID) {
+ uid = mAppContext.getSystemService(PackageManager.class).getPackageUid(pkg, 0);
+ }
+ final boolean wasInList = isAllowlisted(pkg, uid);
+
+ if (!wasInList) {
+ mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled(
+ pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED,
+ true, ActivityManager.RESTRICTION_REASON_USER,
+ "settings", 0);
+ }
+ }
+
mDeviceIdleService.addPowerSaveWhitelistApp(pkg);
mAllowlistedApps.add(pkg);
} catch (RemoteException e) {
Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Unable to find package", e);
}
}
/**
* Remove package from power save allow list.
- * @param pkg
+ * @param pkg packageName of the app
*/
public void removeApp(String pkg) {
+ removeApp(pkg, Process.INVALID_UID);
+ }
+
+ /**
+ * Remove package from power save allow list.
+ * @param pkg packageName of the app
+ * @param uid uid of the app
+ */
+ public void removeApp(String pkg, int uid) {
try {
+ if (android.app.Flags.appRestrictionsApi()) {
+ if (uid == Process.INVALID_UID) {
+ uid = mAppContext.getSystemService(PackageManager.class).getPackageUid(pkg, 0);
+ }
+ final boolean wasInList = isAllowlisted(pkg, uid);
+ if (wasInList) {
+ mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled(
+ pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED,
+ false, ActivityManager.RESTRICTION_REASON_USER,
+ "settings", 0);
+ }
+ }
+
mDeviceIdleService.removePowerSaveWhitelistApp(pkg);
mAllowlistedApps.remove(pkg);
} catch (RemoteException e) {
Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Unable to find package", e);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
index 6fb0179..e91c0bc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
@@ -168,17 +168,16 @@
/**
* Gets a list of preferences that other apps have injected.
*
- * @param profileId Identifier of the user/profile to obtain the injected settings for or
- * UserHandle.USER_CURRENT for all profiles associated with current user.
+ * @param profiles UserHandles of the users/profiles for which to obtain the injected settings.
*/
public Map<Integer, List<Preference>> getInjectedSettings(Context prefContext,
- final int profileId) {
+ final Set<UserHandle> profiles) {
final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- final List<UserHandle> profiles = um.getUserProfiles();
+ final List<UserHandle> allProfilesForUser = um.getUserProfiles();
final ArrayMap<Integer, List<Preference>> result = new ArrayMap<>();
mSettings.clear();
- for (UserHandle userHandle : profiles) {
- if (profileId == UserHandle.USER_CURRENT || profileId == userHandle.getIdentifier()) {
+ for (UserHandle userHandle : allProfilesForUser) {
+ if (profiles.contains(userHandle)) {
final List<Preference> prefs = new ArrayList<>();
Iterable<InjectedSetting> settings = getSettings(userHandle);
for (InjectedSetting setting : settings) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 9b1e4b7..eae58ad 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -52,6 +52,7 @@
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
import android.os.Build;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -131,6 +132,7 @@
protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
@NonNull protected final Context mContext;
@NonNull protected final String mPackageName;
+ @NonNull protected final UserHandle mUserHandle;
private final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
private MediaDevice mCurrentConnectedDevice;
private final LocalBluetoothManager mBluetoothManager;
@@ -140,16 +142,28 @@
/* package */ InfoMediaManager(
@NonNull Context context,
@NonNull String packageName,
+ @NonNull UserHandle userHandle,
@NonNull LocalBluetoothManager localBluetoothManager) {
mContext = context;
mBluetoothManager = localBluetoothManager;
mPackageName = packageName;
+ mUserHandle = userHandle;
}
- /** Creates an instance of InfoMediaManager. */
+ /**
+ * Creates an instance of InfoMediaManager.
+ *
+ * @param context The {@link Context}.
+ * @param packageName The package name of the app for which to control routing, or null if the
+ * caller is interested in system-level routing only (for example, headsets, built-in
+ * speakers, as opposed to app-specific routing (for example, casting to another device).
+ * @param userHandle The {@link UserHandle} of the user on which the app to control is running,
+ * or null if the caller does not need app-specific routing (see {@code packageName}).
+ */
public static InfoMediaManager createInstance(
Context context,
@Nullable String packageName,
+ @Nullable UserHandle userHandle,
LocalBluetoothManager localBluetoothManager) {
// The caller is only interested in system routes (headsets, built-in speakers, etc), and is
@@ -159,25 +173,28 @@
packageName = context.getPackageName();
}
+ if (userHandle == null) {
+ userHandle = android.os.Process.myUserHandle();
+ }
+
if (Flags.useMediaRouter2ForInfoMediaManager()) {
try {
- return new RouterInfoMediaManager(context, packageName, localBluetoothManager);
+ return new RouterInfoMediaManager(
+ context, packageName, userHandle, localBluetoothManager);
} catch (PackageNotAvailableException ex) {
// TODO: b/293578081 - Propagate this exception to callers for proper handling.
Log.w(TAG, "Returning a no-op InfoMediaManager for package " + packageName);
- return new NoOpInfoMediaManager(context, packageName, localBluetoothManager);
+ return new NoOpInfoMediaManager(
+ context, packageName, userHandle, localBluetoothManager);
}
} else {
- return new ManagerInfoMediaManager(context, packageName, localBluetoothManager);
+ return new ManagerInfoMediaManager(
+ context, packageName, userHandle, localBluetoothManager);
}
}
public void startScan() {
- mMediaDevices.clear();
- registerRouter();
startScanOnRouter();
- updateRouteListingPreference();
- refreshDevices();
}
private void updateRouteListingPreference() {
@@ -191,7 +208,6 @@
public final void stopScan() {
stopScanOnRouter();
- unregisterRouter();
}
protected abstract void stopScanOnRouter();
@@ -278,14 +294,37 @@
return null;
}
- protected final void registerCallback(MediaDeviceCallback callback) {
+ /**
+ * Registers the specified {@code callback} to receive state updates about routing information.
+ *
+ * <p>As long as there is a registered {@link MediaDeviceCallback}, {@link InfoMediaManager}
+ * will receive state updates from the platform.
+ *
+ * <p>Call {@link #unregisterCallback(MediaDeviceCallback)} once you no longer need platform
+ * updates.
+ */
+ public final void registerCallback(@NonNull MediaDeviceCallback callback) {
+ boolean wasEmpty = mCallbacks.isEmpty();
if (!mCallbacks.contains(callback)) {
mCallbacks.add(callback);
+ if (wasEmpty) {
+ mMediaDevices.clear();
+ registerRouter();
+ updateRouteListingPreference();
+ refreshDevices();
+ }
}
}
- protected final void unregisterCallback(MediaDeviceCallback callback) {
- mCallbacks.remove(callback);
+ /**
+ * Unregisters the specified {@code callback}.
+ *
+ * @see #registerCallback(MediaDeviceCallback)
+ */
+ public final void unregisterCallback(@NonNull MediaDeviceCallback callback) {
+ if (mCallbacks.remove(callback) && mCallbacks.isEmpty()) {
+ unregisterRouter();
+ }
}
private void dispatchDeviceListAdded(@NonNull List<MediaDevice> devices) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 63056b6..473c627 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -17,7 +17,6 @@
import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
-import android.app.Notification;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
@@ -106,14 +105,23 @@
* Register to start receiving callbacks for MediaDevice events.
*/
public void registerCallback(DeviceCallback callback) {
- mCallbacks.add(callback);
+ boolean wasEmpty = mCallbacks.isEmpty();
+ if (!mCallbacks.contains(callback)) {
+ mCallbacks.add(callback);
+ if (wasEmpty) {
+ mInfoMediaManager.registerCallback(mMediaDeviceCallback);
+ }
+ }
}
/**
* Unregister to stop receiving callbacks for MediaDevice events
*/
public void unregisterCallback(DeviceCallback callback) {
- mCallbacks.remove(callback);
+ if (mCallbacks.remove(callback) && mCallbacks.isEmpty()) {
+ mInfoMediaManager.unregisterCallback(mMediaDeviceCallback);
+ unRegisterDeviceAttributeChangeCallback();
+ }
}
/**
@@ -125,7 +133,7 @@
*
* It will use {@link BluetoothAdapter#getDefaultAdapter()] for setting the bluetooth adapter.
*/
- public LocalMediaManager(Context context, String packageName, Notification notification) {
+ public LocalMediaManager(Context context, String packageName) {
mContext = context;
mPackageName = packageName;
mLocalBluetoothManager =
@@ -138,7 +146,10 @@
}
mInfoMediaManager =
- InfoMediaManager.createInstance(context, packageName, mLocalBluetoothManager);
+ // TODO: b/321969740 - Take the userHandle as a parameter and pass it through. The
+ // package name is not sufficient to unambiguously identify an app.
+ InfoMediaManager.createInstance(
+ context, packageName, /* userHandle */ null, mLocalBluetoothManager);
}
/**
@@ -225,10 +236,6 @@
* Start scan connected MediaDevice
*/
public void startScan() {
- synchronized (mMediaDevicesLock) {
- mMediaDevices.clear();
- }
- mInfoMediaManager.registerCallback(mMediaDeviceCallback);
mInfoMediaManager.startScan();
}
@@ -278,9 +285,7 @@
* Stop scan MediaDevice
*/
public void stopScan() {
- mInfoMediaManager.unregisterCallback(mMediaDeviceCallback);
mInfoMediaManager.stopScan();
- unRegisterDeviceAttributeChangeCallback();
}
/**
@@ -551,7 +556,6 @@
private MediaDevice getMutingExpectedDevice() {
if (mBluetoothAdapter == null
|| mAudioManager.getMutingExpectedDevice() == null) {
- Log.w(TAG, "BluetoothAdapter is null or muting expected device not exist");
return null;
}
final List<BluetoothDevice> bluetoothDevices =
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index 23063da..d621751 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -21,6 +21,7 @@
import android.media.MediaRouter2Manager;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -53,8 +54,9 @@
/* package */ ManagerInfoMediaManager(
Context context,
@NonNull String packageName,
+ @NonNull UserHandle userHandle,
LocalBluetoothManager localBluetoothManager) {
- super(context, packageName, localBluetoothManager);
+ super(context, packageName, userHandle, localBluetoothManager);
mRouterManager = MediaRouter2Manager.getInstance(context);
}
@@ -87,8 +89,7 @@
@Override
protected void transferToRoute(@NonNull MediaRoute2Info route) {
- // TODO: b/279555229 - provide real user handle of a caller.
- mRouterManager.transfer(mPackageName, route, android.os.Process.myUserHandle());
+ mRouterManager.transfer(mPackageName, route, mUserHandle);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index cf11c6d..d2b018c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -20,6 +20,7 @@
import android.media.MediaRoute2Info;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
+import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -58,8 +59,9 @@
NoOpInfoMediaManager(
Context context,
@NonNull String packageName,
+ @NonNull UserHandle userHandle,
LocalBluetoothManager localBluetoothManager) {
- super(context, packageName, localBluetoothManager);
+ super(context, packageName, userHandle, localBluetoothManager);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 0dceeba..045c60d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -25,7 +25,7 @@
import android.media.RouteDiscoveryPreference;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
-import android.os.Process;
+import android.os.UserHandle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
@@ -70,15 +70,16 @@
/* package */ RouterInfoMediaManager(
Context context,
@NonNull String packageName,
+ @NonNull UserHandle userHandle,
LocalBluetoothManager localBluetoothManager)
throws PackageNotAvailableException {
- super(context, packageName, localBluetoothManager);
+ super(context, packageName, userHandle, localBluetoothManager);
MediaRouter2 router = null;
if (Flags.enableCrossUserRoutingInMediaRouter2()) {
try {
- router = MediaRouter2.getInstance(context, packageName, Process.myUserHandle());
+ router = MediaRouter2.getInstance(context, packageName, userHandle);
} catch (IllegalArgumentException ex) {
// Do nothing
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
index 724dd51..869fb7f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
@@ -17,6 +17,7 @@
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.media.flags.Flags
import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
import com.android.settingslib.volume.shared.model.AudioManagerEvent
import kotlinx.coroutines.CoroutineScope
@@ -69,10 +70,14 @@
}
}
localMediaManager.registerCallback(callback)
- localMediaManager.startScan()
+ if (!Flags.removeUnnecessaryRouteScanning()) {
+ localMediaManager.startScan()
+ }
awaitClose {
- localMediaManager.stopScan()
+ if (!Flags.removeUnnecessaryRouteScanning()) {
+ localMediaManager.stopScan()
+ }
localMediaManager.unregisterCallback(callback)
}
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
index f0185b9..3bd37a2 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
@@ -64,21 +64,23 @@
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_returnsRouterInfoMediaManager() {
InfoMediaManager manager =
- InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null);
+ InfoMediaManager.createInstance(
+ mContext, mContext.getPackageName(), mContext.getUser(), null);
assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
}
@Test
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_withFakePackage_returnsNoOpInfoMediaManager() {
- InfoMediaManager manager = InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null);
+ InfoMediaManager manager =
+ InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null);
assertThat(manager).isInstanceOf(NoOpInfoMediaManager.class);
}
@Test
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_withNullPackage_returnsRouterInfoMediaManager() {
- InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null);
+ InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null, null);
assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
}
@@ -86,7 +88,8 @@
@RequiresFlagsDisabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOff_returnsManagerInfoMediaManager() {
InfoMediaManager manager =
- InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null);
+ InfoMediaManager.createInstance(
+ mContext, mContext.getPackageName(), mContext.getUser(), null);
assertThat(manager).isInstanceOf(ManagerInfoMediaManager.class);
}
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/OWNERS b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/OWNERS
new file mode 100644
index 0000000..384fd9b
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/OWNERS
@@ -0,0 +1,3 @@
+#Android Media Better Together
[email protected]
[email protected]
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
index b656253..0d318c3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
@@ -96,7 +96,7 @@
assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_ONE}, UID)).isTrue();
assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_TWO}, UID)).isFalse();
- mPowerAllowlistBackend.addApp(PACKAGE_TWO);
+ mPowerAllowlistBackend.addApp(PACKAGE_TWO, UID);
verify(mDeviceIdleService, atLeastOnce()).addPowerSaveWhitelistApp(PACKAGE_TWO);
assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isTrue();
@@ -104,7 +104,7 @@
assertThat(mPowerAllowlistBackend.isAllowlisted(
new String[] {PACKAGE_ONE, PACKAGE_TWO}, UID)).isTrue();
- mPowerAllowlistBackend.removeApp(PACKAGE_TWO);
+ mPowerAllowlistBackend.removeApp(PACKAGE_TWO, UID);
verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_TWO);
assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isTrue();
@@ -112,7 +112,7 @@
assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_ONE}, UID)).isTrue();
assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_TWO}, UID)).isFalse();
- mPowerAllowlistBackend.removeApp(PACKAGE_ONE);
+ mPowerAllowlistBackend.removeApp(PACKAGE_ONE, UID);
verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_ONE);
assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isFalse();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index d793867..69faddf 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -35,6 +35,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.verify;
import static org.mockito.Mockito.when;
@@ -144,7 +145,8 @@
doReturn(mMediaSessionManager).when(mContext).getSystemService(
Context.MEDIA_SESSION_SERVICE);
mInfoMediaManager =
- new ManagerInfoMediaManager(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager);
+ new ManagerInfoMediaManager(
+ mContext, TEST_PACKAGE_NAME, mContext.getUser(), mLocalBluetoothManager);
mShadowRouter2Manager = ShadowRouter2Manager.getShadow();
mInfoMediaManager.mRouterManager = MediaRouter2Manager.getInstance(mContext);
}
@@ -750,35 +752,31 @@
@Test
public void onTransferred_getAvailableRoutes_shouldAddMediaDevice() {
- final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
- final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
- routingSessionInfos.add(sessionInfo);
- final List<String> selectedRoutes = new ArrayList<>();
- selectedRoutes.add(TEST_ID);
- when(sessionInfo.getSelectedRoutes()).thenReturn(selectedRoutes);
- mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
+ mInfoMediaManager.mRouterManager = mRouterManager;
+ // Since test is running in Robolectric, return a fake session to avoid NPE.
+ when(mRouterManager.getRoutingSessions(anyString()))
+ .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION));
+ when(mRouterManager.getSelectedRoutes(any()))
+ .thenReturn(List.of(TEST_SELECTED_SYSTEM_ROUTE));
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
mInfoMediaManager.registerCallback(mCallback);
- when(info.getDeduplicationIds()).thenReturn(Set.of());
- when(info.getId()).thenReturn(TEST_ID);
- when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ MediaDevice mediaDevice = mInfoMediaManager.getCurrentConnectedDevice();
+ assertThat(mediaDevice).isNotNull();
+ assertThat(mediaDevice.getId()).isEqualTo(TEST_SYSTEM_ROUTE_ID);
- final List<MediaRoute2Info> routes = new ArrayList<>();
- routes.add(info);
- mShadowRouter2Manager.setTransferableRoutes(routes);
+ when(mRouterManager.getRoutingSessions(anyString()))
+ .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION, TEST_REMOTE_ROUTING_SESSION));
+ when(mRouterManager.getSelectedRoutes(any())).thenReturn(List.of(TEST_REMOTE_ROUTE));
- final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
- assertThat(mediaDevice).isNull();
-
- mInfoMediaManager.mMediaRouterCallback.onTransferred(sessionInfo, sessionInfo);
+ mInfoMediaManager.mMediaRouterCallback.onTransferred(
+ TEST_SYSTEM_ROUTING_SESSION, TEST_REMOTE_ROUTING_SESSION);
final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
- assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
+ assertThat(infoDevice).isNotNull();
+ assertThat(infoDevice.getId()).isEqualTo(TEST_REMOTE_ROUTE.getId());
assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice);
- assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
- verify(mCallback).onConnectedDeviceChanged(TEST_ID);
+ verify(mCallback).onConnectedDeviceChanged(TEST_REMOTE_ROUTE.getId());
}
@Test
@@ -794,7 +792,8 @@
mInfoMediaManager.mMediaRouterCallback.onSessionUpdated(TEST_SYSTEM_ROUTING_SESSION);
- verify(mCallback).onDeviceListAdded(any());
+ // Expecting 1st call after registerCallback() and 2nd call after onSessionUpdated().
+ verify(mCallback, times(2)).onDeviceListAdded(any());
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 693b7d0..ddb5419 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -21,6 +21,8 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -58,6 +60,7 @@
import org.robolectric.shadow.api.Shadow;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@@ -117,6 +120,13 @@
mInfoMediaManager = mock(
InfoMediaManager.class,
withSettings().useConstructor(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager));
+ doReturn(
+ List.of(
+ new RoutingSessionInfo.Builder(TEST_SESSION_ID, TEST_PACKAGE_NAME)
+ .addSelectedRoute(TEST_DEVICE_ID_1)
+ .build()))
+ .when(mInfoMediaManager)
+ .getRoutingSessionsForPackage();
mInfoMediaDevice1 = spy(new InfoMediaDevice(mContext, mRouteInfo1));
mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2);
@@ -124,15 +134,16 @@
new LocalMediaManager(
mContext, mLocalBluetoothManager, mInfoMediaManager, TEST_PACKAGE_NAME);
mLocalMediaManager.mAudioManager = mAudioManager;
+ mLocalMediaManager.registerCallback(mCallback);
+ clearInvocations(mCallback);
}
@Test
- public void startScan_mediaDevicesListShouldBeClear() {
+ public void onDeviceListAdded_shouldClearDeviceList() {
final MediaDevice device = mock(MediaDevice.class);
mLocalMediaManager.mMediaDevices.add(device);
-
assertThat(mLocalMediaManager.mMediaDevices).hasSize(1);
- mLocalMediaManager.startScan();
+ mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(Collections.emptyList());
assertThat(mLocalMediaManager.mMediaDevices).isEmpty();
}
@@ -147,7 +158,6 @@
when(device.getId()).thenReturn(TEST_DEVICE_ID_1);
when(currentDevice.getId()).thenReturn(TEST_CURRENT_DEVICE_ID);
- mLocalMediaManager.registerCallback(mCallback);
assertThat(mLocalMediaManager.connectDevice(device)).isTrue();
verify(mInfoMediaManager).connectToDevice(device);
}
@@ -158,7 +168,6 @@
mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice2);
mLocalMediaManager.mCurrentConnectedDevice = mInfoMediaDevice1;
- mLocalMediaManager.registerCallback(mCallback);
assertThat(mLocalMediaManager.connectDevice(mInfoMediaDevice2)).isTrue();
assertThat(mInfoMediaDevice2.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
@@ -171,7 +180,6 @@
mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice2);
mLocalMediaManager.mCurrentConnectedDevice = mInfoMediaDevice1;
- mLocalMediaManager.registerCallback(mCallback);
assertThat(mLocalMediaManager.connectDevice(mInfoMediaDevice1)).isFalse();
assertThat(mInfoMediaDevice1.getState()).isNotEqualTo(LocalMediaManager.MediaDeviceState
@@ -189,7 +197,6 @@
when(cachedDevice.isConnected()).thenReturn(false);
when(cachedDevice.isBusy()).thenReturn(false);
- mLocalMediaManager.registerCallback(mCallback);
assertThat(mLocalMediaManager.connectDevice(device)).isTrue();
verify(cachedDevice).connect();
@@ -252,7 +259,6 @@
when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
assertThat(mLocalMediaManager.mMediaDevices).isEmpty();
- mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
@@ -274,7 +280,6 @@
when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
assertThat(mLocalMediaManager.mMediaDevices).hasSize(1);
- mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
@@ -292,7 +297,6 @@
mLocalMediaManager.mMediaDevices.add(device2);
assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
- mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.mMediaDeviceCallback
.onDeviceListRemoved(mLocalMediaManager.mMediaDevices);
@@ -302,19 +306,21 @@
@Test
public void onDeviceListRemoved_phoneDeviceNotLastDeviceAfterRemoveDeviceList_removeList() {
- final List<MediaDevice> devices = new ArrayList<>();
final MediaDevice device1 = mock(MediaDevice.class);
final MediaDevice device2 = mock(MediaDevice.class);
final MediaDevice device3 = mock(MediaDevice.class);
- devices.add(device1);
- devices.add(device3);
+
+ mLocalMediaManager.mMediaDevices.clear();
mLocalMediaManager.mMediaDevices.add(device1);
mLocalMediaManager.mMediaDevices.add(device2);
mLocalMediaManager.mMediaDevices.add(device3);
assertThat(mLocalMediaManager.mMediaDevices).hasSize(3);
- mLocalMediaManager.registerCallback(mCallback);
- mLocalMediaManager.mMediaDeviceCallback.onDeviceListRemoved(devices);
+
+ final List<MediaDevice> devicesToRemove = new ArrayList<>();
+ devicesToRemove.add(device1);
+ devicesToRemove.add(device3);
+ mLocalMediaManager.mMediaDeviceCallback.onDeviceListRemoved(devicesToRemove);
assertThat(mLocalMediaManager.mMediaDevices).hasSize(1);
verify(mCallback).onDeviceListUpdate(any());
@@ -332,7 +338,6 @@
when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
- mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_2);
assertThat(mLocalMediaManager.getCurrentConnectedDevice()).isEqualTo(device2);
@@ -352,7 +357,6 @@
when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
- mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_1);
verify(mCallback, never()).onDeviceAttributesChanged();
@@ -366,7 +370,6 @@
assertThat(mInfoMediaDevice1.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
.STATE_DISCONNECTED);
- mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_1);
assertThat(mInfoMediaDevice1.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
@@ -375,7 +378,6 @@
@Test
public void onConnectedDeviceChanged_nullConnectedDevice_noException() {
- mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_2);
}
@@ -392,7 +394,6 @@
when(cachedDevice.isConnected()).thenReturn(false);
when(cachedDevice.isBusy()).thenReturn(false);
- mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.connectDevice(device);
mLocalMediaManager.mDeviceAttributeChangeCallback.onDeviceAttributesChanged();
@@ -410,7 +411,6 @@
.STATE_CONNECTING);
assertThat(mInfoMediaDevice2.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
.STATE_CONNECTED);
- mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.mMediaDeviceCallback.onRequestFailed(REASON_UNKNOWN_ERROR);
assertThat(mInfoMediaDevice1.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
@@ -421,8 +421,6 @@
@Test
public void onDeviceAttributesChanged_shouldBeCalled() {
- mLocalMediaManager.registerCallback(mCallback);
-
mLocalMediaManager.mDeviceAttributeChangeCallback.onDeviceAttributesChanged();
verify(mCallback).onDeviceAttributesChanged();
@@ -475,7 +473,6 @@
when(device1.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE);
assertThat(mLocalMediaManager.mMediaDevices).hasSize(0);
- mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
@@ -497,7 +494,6 @@
when(cachedDevice.isConnected()).thenReturn(false);
when(cachedDevice.isBusy()).thenReturn(false);
- mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.connectDevice(device);
verify(cachedDevice).connect();
@@ -512,8 +508,6 @@
@Test
public void onRequestFailed_shouldDispatchOnRequestFailed() {
- mLocalMediaManager.registerCallback(mCallback);
-
mLocalMediaManager.mMediaDeviceCallback.onRequestFailed(1);
verify(mCallback).onRequestFailed(1);
@@ -532,7 +526,6 @@
mShadowBluetoothAdapter = null;
assertThat(mLocalMediaManager.mMediaDevices).hasSize(1);
- mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
index d630301..908f50d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
@@ -46,6 +46,7 @@
new NoOpInfoMediaManager(
mContext,
/* packageName */ "FAKE_PACKAGE_NAME",
+ mContext.getUser(),
/* localBluetoothManager */ null);
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index a33c160..75c0cec 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -272,6 +272,7 @@
Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
Settings.Secure.AUDIO_DEVICE_INVENTORY,
Settings.Secure.SCREEN_RESOLUTION_MODE,
- Settings.Secure.ACCESSIBILITY_FLOATING_MENU_TARGETS
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_TARGETS,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 1bff592..8faf917 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -430,5 +430,7 @@
VALIDATORS.put(Secure.AUDIO_DEVICE_INVENTORY, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.SCREEN_RESOLUTION_MODE, new InclusiveIntegerRangeValidator(
Secure.RESOLUTION_MODE_UNKNOWN, Secure.RESOLUTION_MODE_FULL));
+ VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
+ new InclusiveIntegerRangeValidator(0, 10));
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index ad3eb92..e77cf2f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -531,13 +531,22 @@
pw.println(" put NAMESPACE KEY VALUE [default]");
pw.println(" Change the contents of KEY to VALUE for the given NAMESPACE.");
pw.println(" {default} to set as the default value.");
+ pw.println(" override NAMESPACE KEY VALUE");
+ pw.println(" Set flag NAMESPACE/KEY to the given VALUE, and ignores "
+ + "server-updates for");
+ pw.println(" this flag. This can still be called even if there is no underlying "
+ + "value set.");
pw.println(" delete NAMESPACE KEY");
pw.println(" Delete the entry for KEY for the given NAMESPACE.");
+ pw.println(" clear_override NAMESPACE KEY");
+ pw.println(" Clear local sticky flag override for KEY in the given NAMESPACE.");
pw.println(" list_namespaces [--public]");
pw.println(" Prints the name of all (or just the public) namespaces.");
pw.println(" list [NAMESPACE]");
pw.println(" Print all keys and values defined, optionally for the given "
+ "NAMESPACE.");
+ pw.println(" list_local_overrides");
+ pw.println(" Print all flags that have been overridden.");
pw.println(" reset RESET_MODE [NAMESPACE]");
pw.println(" Reset all flag values, optionally for a NAMESPACE, according to "
+ "RESET_MODE.");
@@ -547,8 +556,9 @@
+ "flags are reset");
pw.println(" set_sync_disabled_for_tests SYNC_DISABLED_MODE");
pw.println(" Modifies bulk property setting behavior for tests. When in one of the"
- + " disabled modes this ensures that config isn't overwritten.");
- pw.println(" SYNC_DISABLED_MODE is one of:");
+ + " disabled modes");
+ pw.println(" this ensures that config isn't overwritten. SYNC_DISABLED_MODE is "
+ + "one of:");
pw.println(" none: Sync is not disabled. A reboot may be required to restart"
+ " syncing.");
pw.println(" persistent: Sync is disabled, this state will survive a reboot.");
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 46cee6b..fa9b279 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1760,6 +1760,9 @@
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
SecureSettingsProto.Accessibility.DISPLAY_DALTONIZER);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
+ SecureSettingsProto.Accessibility.DISPLAY_DALTONIZER_SATURATION_LEVEL);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
SecureSettingsProto.Accessibility.DISPLAY_INVERSION_ENABLED);
dumpSetting(s, p,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b94e224..46bf494 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -934,6 +934,9 @@
<!-- Permission required for Cts test - CtsSettingsTestCases -->
<uses-permission android:name="android.permission.PREPARE_FACTORY_RESET" />
+ <!-- Permission required for CTS test - FileIntegrityManagerTest -->
+ <uses-permission android:name="android.permission.SETUP_FSVERITY" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 40db52e..5e11e1a 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -117,6 +117,7 @@
"SystemUILogLib",
"SystemUIPluginLib",
"SystemUISharedLib",
+ "SystemUI-shared-utils",
"SystemUI-statsd",
"SettingsLib",
"com_android_systemui_flags_lib",
@@ -263,6 +264,7 @@
"SystemUISharedLib",
"SystemUICustomizationLib",
"SystemUICustomizationTestUtils",
+ "SystemUI-shared-utils",
"SystemUI-statsd",
"SettingsLib",
"com_android_systemui_flags_lib",
@@ -361,6 +363,7 @@
"SystemUICustomizationTestUtils",
"androidx.compose.runtime_runtime",
"kosmos",
+ "androidx.test.rules",
],
libs: [
"android.test.runner",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index bbc9fe4..561c85e 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -367,6 +367,9 @@
<uses-permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE" />
+ <!-- To follow the grammatical gender preference -->
+ <uses-permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" />
+
<!-- Listen to (dis-)connection of external displays and enable / disable them. -->
<uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-cs/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-cs/strings.xml
index e5dd693..15eb928 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-cs/strings.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-cs/strings.xml
@@ -21,7 +21,7 @@
<string name="previous_button_content_description" msgid="840869171117765966">"Zpět na předchozí obrazovku"</string>
<string name="next_button_content_description" msgid="6810058269847364406">"Přejít na další obrazovku"</string>
<string name="accessibility_menu_description" msgid="4458354794093858297">"Nabídka usnadnění přístupu zobrazuje na obrazovce velkou nabídku k ovládání zařízení. Můžete zamknout zařízení, upravit hlasitost a jas, pořídit snímek obrazovky apod."</string>
- <string name="accessibility_menu_summary" msgid="340071398148208130">"Ovládat zařízení pomocí velké nabídky"</string>
+ <string name="accessibility_menu_summary" msgid="340071398148208130">"Ovládání zařízení pomocí velké nabídky"</string>
<string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Nastavení nabídky usnadnění přístupu"</string>
<string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Velká tlačítka"</string>
<string name="accessibility_menu_large_buttons_summary" msgid="236873938502785311">"Zvětšit tlačítka v nabídce přístupnosti"</string>
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c979d05..c2e4b82 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -784,3 +784,23 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "slice_broadcast_relay_in_background"
+ namespace: "systemui"
+ description: "Move handling of slice broadcast relay broadcasts to background threads"
+ bug: "334767208"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "register_battery_controller_receivers_in_corestartable"
+ namespace: "systemui"
+ description: "Decide whether to register the receivers in battery controller impl in the BatteryControllerStartable corestartable."
+ bug: "307517093"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffect.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffect.kt
new file mode 100644
index 0000000..ffa2b46
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffect.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.surfaceeffects.revealeffect
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.RenderEffect
+import androidx.core.graphics.ColorUtils
+import com.android.systemui.surfaceeffects.RenderEffectDrawCallback
+import com.android.systemui.surfaceeffects.utils.MathUtils
+import kotlin.math.max
+import kotlin.math.min
+
+/** Creates a reveal effect with a circular ripple sparkles on top. */
+class RippleRevealEffect(
+ private val config: RippleRevealEffectConfig,
+ private val renderEffectCallback: RenderEffectDrawCallback,
+ private val stateChangedCallback: AnimationStateChangedCallback? = null
+) {
+ private val rippleRevealShader = RippleRevealShader().apply { applyConfig(config) }
+ private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
+
+ fun play() {
+ if (animator.isRunning) {
+ return
+ }
+
+ animator.duration = config.duration.toLong()
+ animator.addUpdateListener { updateListener ->
+ val playTime = updateListener.currentPlayTime.toFloat()
+ rippleRevealShader.setTime(playTime * TIME_SCALE_FACTOR)
+
+ // Compute radius.
+ val progress = updateListener.animatedValue as Float
+ val innerRad = MathUtils.lerp(config.innerRadiusStart, config.innerRadiusEnd, progress)
+ val outerRad = MathUtils.lerp(config.outerRadiusStart, config.outerRadiusEnd, progress)
+ rippleRevealShader.setInnerRadius(innerRad)
+ rippleRevealShader.setOuterRadius(outerRad)
+
+ // Compute alphas.
+ val innerAlphaProgress =
+ MathUtils.constrainedMap(
+ 1f,
+ 0f,
+ config.innerFadeOutStart,
+ config.duration,
+ playTime
+ )
+ val outerAlphaProgress =
+ MathUtils.constrainedMap(
+ 1f,
+ 0f,
+ config.outerFadeOutStart,
+ config.duration,
+ playTime
+ )
+ val innerAlpha = MathUtils.lerp(0f, 255f, innerAlphaProgress)
+ val outerAlpha = MathUtils.lerp(0f, 255f, outerAlphaProgress)
+
+ val innerColor = ColorUtils.setAlphaComponent(config.innerColor, innerAlpha.toInt())
+ val outerColor = ColorUtils.setAlphaComponent(config.outerColor, outerAlpha.toInt())
+ rippleRevealShader.setInnerColor(innerColor)
+ rippleRevealShader.setOuterColor(outerColor)
+
+ // Pass in progresses since those functions take in normalized alpha values.
+ rippleRevealShader.setBackgroundAlpha(max(innerAlphaProgress, outerAlphaProgress))
+ rippleRevealShader.setSparkleAlpha(min(innerAlphaProgress, outerAlphaProgress))
+
+ // Trigger draw callback.
+ renderEffectCallback.onDraw(
+ RenderEffect.createRuntimeShaderEffect(
+ rippleRevealShader,
+ RippleRevealShader.BACKGROUND_UNIFORM
+ )
+ )
+ }
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ stateChangedCallback?.onAnimationEnd()
+ }
+ }
+ )
+ animator.start()
+ stateChangedCallback?.onAnimationStart()
+ }
+
+ interface AnimationStateChangedCallback {
+ fun onAnimationStart()
+ fun onAnimationEnd()
+ }
+
+ private companion object {
+ private const val TIME_SCALE_FACTOR = 0.00175f
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectConfig.kt
new file mode 100644
index 0000000..9675f19
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectConfig.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.surfaceeffects.revealeffect
+
+import android.graphics.Color
+
+/** Defines parameters needed for [RippleRevealEffect]. */
+data class RippleRevealEffectConfig(
+ /** Total duration of the animation. */
+ val duration: Float = 0f,
+ /** Timestamp of when the inner mask starts fade out. (Linear fadeout) */
+ val innerFadeOutStart: Float = 0f,
+ /** Timestamp of when the outer mask starts fade out. (Linear fadeout) */
+ val outerFadeOutStart: Float = 0f,
+ /** Center x position of the effect. */
+ val centerX: Float = 0f,
+ /** Center y position of the effect. */
+ val centerY: Float = 0f,
+ /** Start radius of the inner circle. */
+ val innerRadiusStart: Float = 0f,
+ /** End radius of the inner circle. */
+ val innerRadiusEnd: Float = 0f,
+ /** Start radius of the outer circle. */
+ val outerRadiusStart: Float = 0f,
+ /** End radius of the outer circle. */
+ val outerRadiusEnd: Float = 0f,
+ /**
+ * Pixel density of the display. Do not pass a random value. The value must come from
+ * [context.resources.displayMetrics.density].
+ */
+ val pixelDensity: Float = 1f,
+ /**
+ * The amount the circle masks should be softened. Higher value will make the edge of the circle
+ * mask soft.
+ */
+ val blurAmount: Float = 0f,
+ /** Color of the inner circle mask. */
+ val innerColor: Int = Color.WHITE,
+ /** Color of the outer circle mask. */
+ val outerColor: Int = Color.WHITE,
+ /** Multiplier to make the sparkles visible. */
+ val sparkleStrength: Float = SPARKLE_STRENGTH,
+ /** Size of the sparkle. Expected range [0, 1]. */
+ val sparkleScale: Float = SPARKLE_SCALE
+) {
+ /** Default parameters. */
+ companion object {
+ const val SPARKLE_STRENGTH: Float = 0.3f
+ const val SPARKLE_SCALE: Float = 0.8f
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealShader.kt
new file mode 100644
index 0000000..a3f9795
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealShader.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.surfaceeffects.revealeffect
+
+import android.graphics.RuntimeShader
+import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
+import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
+
+/** Circular reveal effect with sparkles. */
+class RippleRevealShader : RuntimeShader(SHADER) {
+ // language=AGSL
+ companion object {
+ const val BACKGROUND_UNIFORM = "in_dst"
+ private const val MAIN =
+ """
+ uniform shader ${BACKGROUND_UNIFORM};
+ uniform half in_dstAlpha;
+ uniform half in_time;
+ uniform vec2 in_center;
+ uniform half in_innerRadius;
+ uniform half in_outerRadius;
+ uniform half in_sparkleStrength;
+ uniform half in_blur;
+ uniform half in_pixelDensity;
+ uniform half in_sparkleScale;
+ uniform half in_sparkleAlpha;
+ layout(color) uniform vec4 in_innerColor;
+ layout(color) uniform vec4 in_outerColor;
+
+ vec4 main(vec2 p) {
+ half innerMask = soften(sdCircle(p - in_center, in_innerRadius), in_blur);
+ half outerMask = soften(sdCircle(p - in_center, in_outerRadius), in_blur);
+
+ // Flip it since we are interested in the circle.
+ innerMask = 1.-innerMask;
+ outerMask = 1.-outerMask;
+
+ // Color two circles using the mask.
+ vec4 inColor = vec4(in_innerColor.rgb, 1.) * in_innerColor.a;
+ vec4 outColor = vec4(in_outerColor.rgb, 1.) * in_outerColor.a;
+ vec4 blend = mix(inColor, outColor, innerMask);
+
+ vec4 dst = vec4(in_dst.eval(p).rgb, 1.);
+ dst *= in_dstAlpha;
+
+ blend *= blend.a;
+ // Do normal blend with the background.
+ blend = blend + dst * (1. - blend.a);
+
+ half sparkle =
+ sparkles(p - mod(p, in_pixelDensity * in_sparkleScale), in_time);
+ // Add sparkles using additive blending.
+ blend += sparkle * in_sparkleStrength * in_sparkleAlpha;
+
+ // Mask everything at the end.
+ blend *= outerMask;
+
+ return blend;
+ }
+ """
+
+ private const val SHADER =
+ ShaderUtilLibrary.SHADER_LIB +
+ SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+ SdfShaderLibrary.CIRCLE_SDF +
+ MAIN
+ }
+
+ fun applyConfig(config: RippleRevealEffectConfig) {
+ setCenter(config.centerX, config.centerY)
+ setInnerRadius(config.innerRadiusStart)
+ setOuterRadius(config.outerRadiusStart)
+ setBlurAmount(config.blurAmount)
+ setPixelDensity(config.pixelDensity)
+ setSparkleScale(config.sparkleScale)
+ setSparkleStrength(config.sparkleStrength)
+ setInnerColor(config.innerColor)
+ setOuterColor(config.outerColor)
+ }
+
+ fun setTime(time: Float) {
+ setFloatUniform("in_time", time)
+ }
+
+ fun setCenter(centerX: Float, centerY: Float) {
+ setFloatUniform("in_center", centerX, centerY)
+ }
+
+ fun setInnerRadius(radius: Float) {
+ setFloatUniform("in_innerRadius", radius)
+ }
+
+ fun setOuterRadius(radius: Float) {
+ setFloatUniform("in_outerRadius", radius)
+ }
+
+ fun setBlurAmount(blurAmount: Float) {
+ setFloatUniform("in_blur", blurAmount)
+ }
+
+ fun setPixelDensity(density: Float) {
+ setFloatUniform("in_pixelDensity", density)
+ }
+
+ fun setSparkleScale(scale: Float) {
+ setFloatUniform("in_sparkleScale", scale)
+ }
+
+ fun setSparkleStrength(strength: Float) {
+ setFloatUniform("in_sparkleStrength", strength)
+ }
+
+ fun setInnerColor(color: Int) {
+ setColorUniform("in_innerColor", color)
+ }
+
+ fun setOuterColor(color: Int) {
+ setColorUniform("in_outerColor", color)
+ }
+
+ /** Sets the background alpha. Range [0,1]. */
+ fun setBackgroundAlpha(alpha: Float) {
+ setFloatUniform("in_dstAlpha", alpha)
+ }
+
+ /** Sets the sparkle alpha. Range [0,1]. */
+ fun setSparkleAlpha(alpha: Float) {
+ setFloatUniform("in_sparkleAlpha", alpha)
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt
new file mode 100644
index 0000000..1411c32
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.surfaceeffects.utils
+
+/** Copied from android.utils.MathUtils */
+object MathUtils {
+ fun constrainedMap(
+ rangeMin: Float,
+ rangeMax: Float,
+ valueMin: Float,
+ valueMax: Float,
+ value: Float
+ ): Float {
+ return lerp(rangeMin, rangeMax, lerpInvSat(valueMin, valueMax, value))
+ }
+
+ fun lerp(start: Float, stop: Float, amount: Float): Float {
+ return start + (stop - start) * amount
+ }
+
+ fun lerpInv(a: Float, b: Float, value: Float): Float {
+ return if (a != b) (value - a) / (b - a) else 0.0f
+ }
+
+ fun saturate(value: Float): Float {
+ return constrain(value, 0.0f, 1.0f)
+ }
+
+ fun lerpInvSat(a: Float, b: Float, value: Float): Float {
+ return saturate(lerpInv(a, b, value))
+ }
+
+ fun constrain(amount: Float, low: Float, high: Float): Float {
+ return if (amount < low) low else if (amount > high) high else amount
+ }
+}
diff --git a/packages/SystemUI/checks/Android.bp b/packages/SystemUI/checks/Android.bp
index 4cbc18c..f65d797 100644
--- a/packages/SystemUI/checks/Android.bp
+++ b/packages/SystemUI/checks/Android.bp
@@ -24,10 +24,7 @@
java_library_host {
name: "SystemUILintChecker",
- srcs: [
- "src/**/*.kt",
- "src/**/*.java",
- ],
+ srcs: ["src/**/*.kt"],
plugins: ["auto_service_plugin"],
libs: [
"auto_service_annotations",
@@ -38,35 +35,13 @@
java_test_host {
name: "SystemUILintCheckerTest",
- srcs: [
- "tests/**/*.kt",
- "tests/**/*.java",
- ],
+ defaults: ["AndroidLintCheckerTestDefaults"],
+ srcs: ["tests/**/*.kt"],
data: [
":framework",
- ":androidx.annotation_annotation",
+ ":androidx.annotation_annotation-nodeps",
],
static_libs: [
"SystemUILintChecker",
- "junit",
- "lint",
- "lint_tests",
],
- test_options: {
- unit_test: true,
- tradefed_options: [
- {
- // lint bundles in some classes that were built with older versions
- // of libraries, and no longer load. Since tradefed tries to load
- // all classes in the jar to look for tests, it crashes loading them.
- // Exclude these classes from tradefed's search.
- name: "exclude-paths",
- value: "org/apache",
- },
- {
- name: "exclude-paths",
- value: "META-INF",
- },
- ],
- },
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt
index 30e2a25..f9bf306 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt
@@ -34,7 +34,6 @@
* Checks if any class has implemented the `Dumpable` interface but has not registered itself with
* the `DumpManager`.
*/
-@Suppress("UnstableApiUsage")
class DumpableNotRegisteredDetector : Detector(), SourceCodeScanner {
private var isDumpable: Boolean = false
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
index 5840e8f..024c394 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -59,7 +59,7 @@
`BroadcastDispatcher` instead, which registers the receiver on a \
background thread. `BroadcastDispatcher` also improves our visibility \
into ANRs.""",
- moreInfo = "go/identifying-broadcast-threads",
+ moreInfo = "http://go/identifying-broadcast-threads",
category = Category.PERFORMANCE,
priority = 8,
severity = Severity.WARNING,
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
index 141dd05..f3b24a3 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
@@ -16,15 +16,8 @@
package com.android.internal.systemui.lint
-import com.android.annotations.NonNull
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
import com.android.tools.lint.checks.infrastructure.TestFiles.LibraryReferenceTestFile
import java.io.File
-import org.intellij.lang.annotations.Language
-
-@Suppress("UnstableApiUsage")
-@NonNull
-private fun indentedJava(@NonNull @Language("JAVA") source: String) = java(source).indented()
/*
* This file contains stubs of framework APIs and System UI classes for testing purposes only. The
@@ -33,16 +26,5 @@
internal val androidStubs =
arrayOf(
LibraryReferenceTestFile(File("framework.jar").canonicalFile),
- LibraryReferenceTestFile(File("androidx.annotation_annotation.jar").canonicalFile),
- indentedJava(
- """
-package com.android.systemui.settings;
-import android.content.pm.UserInfo;
-
-public interface UserTracker {
- int getUserId();
- UserInfo getUserInfo();
-}
-"""
- ),
+ LibraryReferenceTestFile(File("androidx.annotation_annotation-nodeps.jar").canonicalFile),
)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
index 4c4185d..c9bc8b3 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
@@ -19,17 +19,14 @@
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
-import org.junit.Ignore
import org.junit.Test
-@Suppress("UnstableApiUsage")
class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = BindServiceOnMainThreadDetector()
override fun getIssues(): List<Issue> = listOf(BindServiceOnMainThreadDetector.ISSUE)
- @Ignore
@Test
fun testBindService() {
lint()
@@ -37,7 +34,9 @@
TestFiles.java(
"""
package test.pkg;
+
import android.content.Context;
+ import android.content.Intent;
public class TestClass {
public void bind(Context context) {
@@ -48,13 +47,13 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:7: Warning: This method should be annotated with @WorkerThread because it calls bindService [BindServiceOnMainThread]
+ src/test/pkg/TestClass.java:9: Warning: This method should be annotated with @WorkerThread because it calls bindService [BindServiceOnMainThread]
context.bindService(intent, null, 0);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -62,7 +61,6 @@
)
}
- @Ignore
@Test
fun testBindServiceAsUser() {
lint()
@@ -70,7 +68,9 @@
TestFiles.java(
"""
package test.pkg;
+
import android.content.Context;
+ import android.content.Intent;
import android.os.UserHandle;
public class TestClass {
@@ -82,13 +82,13 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:8: Warning: This method should be annotated with @WorkerThread because it calls bindServiceAsUser [BindServiceOnMainThread]
+ src/test/pkg/TestClass.java:10: Warning: This method should be annotated with @WorkerThread because it calls bindServiceAsUser [BindServiceOnMainThread]
context.bindServiceAsUser(intent, null, 0, UserHandle.ALL);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -114,7 +114,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
@@ -147,7 +147,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
@@ -181,7 +181,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
@@ -219,12 +219,10 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
.expectClean()
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index 30b68f7..3788dda 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = BroadcastSentViaContextDetector()
@@ -30,7 +29,6 @@
@Test
fun testSendBroadcast() {
- println(stubs.size)
lint()
.files(
TestFiles.java(
@@ -47,7 +45,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -80,7 +78,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -114,7 +112,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -149,7 +147,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -176,7 +174,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -201,12 +199,10 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
.expectClean()
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
index ff150c8c..2c20321 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
@@ -21,11 +21,8 @@
import com.android.tools.lint.checks.infrastructure.TestMode
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
-import org.junit.Ignore
import org.junit.Test
-@Suppress("UnstableApiUsage")
-@Ignore("b/254533331")
class CleanArchitectureDependencyViolationDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector {
return CleanArchitectureDependencyViolationDetector()
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
index ee6e0ce..0652e69 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
@@ -24,7 +24,6 @@
import java.util.EnumSet
import org.junit.Test
-@Suppress("UnstableApiUsage")
class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = DemotingTestWithoutBugDetector()
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt
index 3d6cbc7..6c6c263 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class DumpableNotRegisteredDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = DumpableNotRegisteredDetector()
@@ -37,7 +36,8 @@
class SomeClass() {
}
- """.trimIndent()
+ """
+ .trimIndent()
),
*stubs,
)
@@ -67,7 +67,8 @@
pw.println("testDump");
}
}
- """.trimIndent()
+ """
+ .trimIndent()
),
*stubs,
)
@@ -97,7 +98,8 @@
pw.println("testDump");
}
}
- """.trimIndent()
+ """
+ .trimIndent()
),
*stubs,
)
@@ -127,7 +129,8 @@
pw.println("testDump");
}
}
- """.trimIndent()
+ """
+ .trimIndent()
),
*stubs,
)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
index ed3d14a..bb34d91 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = NonInjectedMainThreadDetector()
@@ -46,7 +45,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedMainThreadDetector.ISSUE)
.run()
@@ -79,7 +78,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedMainThreadDetector.ISSUE)
.run()
@@ -104,7 +103,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedMainThreadDetector.ISSUE)
.run()
@@ -136,7 +135,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedMainThreadDetector.ISSUE)
.run()
@@ -149,6 +148,4 @@
"""
)
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
index 846129a..fe5b576 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = NonInjectedServiceDetector()
@@ -44,7 +43,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
@@ -76,7 +75,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
@@ -109,7 +108,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
@@ -134,7 +133,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
@@ -147,6 +146,4 @@
"""
)
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index 0ac8f8e..3f12569dfc 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = RegisterReceiverViaContextDetector()
@@ -35,9 +34,8 @@
TestFiles.java(
"""
package test.pkg;
- import android.content.BroadcastReceiver;
+
import android.content.Context;
- import android.content.IntentFilter;
public class TestClass {
public void bind(Context context, BroadcastReceiver receiver,
@@ -48,13 +46,13 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:9: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+ src/test/pkg/TestClass.java:8: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
context.registerReceiver(receiver, filter, 0);
~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -69,9 +67,8 @@
TestFiles.java(
"""
package test.pkg;
- import android.content.BroadcastReceiver;
+
import android.content.Context;
- import android.content.IntentFilter;
@SuppressWarnings("RegisterReceiverViaContext")
public class TestClass {
@@ -83,7 +80,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
@@ -97,11 +94,8 @@
TestFiles.java(
"""
package test.pkg;
- import android.content.BroadcastReceiver;
+
import android.content.Context;
- import android.content.IntentFilter;
- import android.os.Handler;
- import android.os.UserHandle;
public class TestClass {
public void bind(Context context, BroadcastReceiver receiver,
@@ -113,13 +107,13 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:11: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+ src/test/pkg/TestClass.java:8: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
context.registerReceiverAsUser(receiver, UserHandle.ALL, filter,
~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -134,11 +128,8 @@
TestFiles.java(
"""
package test.pkg;
- import android.content.BroadcastReceiver;
+
import android.content.Context;
- import android.content.IntentFilter;
- import android.os.Handler;
- import android.os.UserHandle;
public class TestClass {
public void bind(Context context, BroadcastReceiver receiver,
@@ -150,19 +141,17 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:11: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+ src/test/pkg/TestClass.java:8: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
context.registerReceiverForAllUsers(receiver, filter, "permission",
~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
"""
)
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index 34a4249..7944ce3 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class SlowUserQueryDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = SlowUserQueryDetector()
@@ -182,5 +181,19 @@
.expectClean()
}
- private val stubs = androidStubs
+ private val stubs =
+ arrayOf(
+ *androidStubs,
+ java(
+ """
+package com.android.systemui.settings;
+import android.content.pm.UserInfo;
+public interface UserTracker {
+ int getUserId();
+ UserInfo getUserInfo();
+}
+"""
+ )
+ .indented()
+ )
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index 34becc6..756751c 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = SoftwareBitmapDetector()
@@ -45,7 +44,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(SoftwareBitmapDetector.ISSUE)
.run()
@@ -80,7 +79,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(SoftwareBitmapDetector.ISSUE)
.run()
@@ -102,12 +101,10 @@
}
"""
),
- *stubs
+ *androidStubs
)
.issues(SoftwareBitmapDetector.ISSUE)
.run()
.expectClean()
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
index efe4c90..fd00018 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
@@ -21,7 +21,6 @@
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = StaticSettingsProviderDetector()
@@ -85,7 +84,7 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(StaticSettingsProviderDetector.ISSUE)
.run()
@@ -226,12 +225,10 @@
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(StaticSettingsProviderDetector.ISSUE)
.run()
.expectClean()
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
index 3f93f07..29b3828 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
@@ -10,7 +10,6 @@
import org.junit.runners.JUnit4
import org.junit.runners.model.Statement
-@Suppress("UnstableApiUsage")
@RunWith(JUnit4::class)
abstract class SystemUILintDetectorTest : LintDetectorTest() {
@@ -18,7 +17,7 @@
@ClassRule
@JvmField
val libraryChecker: LibraryExists =
- LibraryExists("framework.jar", "androidx.annotation_annotation.jar")
+ LibraryExists("framework.jar", "androidx.annotation_annotation-nodeps.jar")
}
class LibraryExists(vararg val libraryNames: String) : TestRule {
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
index db73154..a4e82a7 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
@@ -18,28 +18,22 @@
package com.android.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class TestFunctionNameViolationDetectorTest : SystemUILintDetectorTest() {
- override fun getDetector(): Detector {
- return TestFunctionNameViolationDetector()
- }
+ override fun getDetector(): Detector = TestFunctionNameViolationDetector()
- override fun getIssues(): List<Issue> {
- return listOf(
- TestFunctionNameViolationDetector.ISSUE,
- )
- }
+ override fun getIssues(): List<Issue> = listOf(TestFunctionNameViolationDetector.ISSUE)
@Test
fun violations() {
lint()
.files(
- kotlin(
- """
+ TestFiles.kotlin(
+ """
package test.pkg.name
import org.junit.Test
@@ -64,13 +58,11 @@
}
}
"""
- .trimIndent()
- ),
- testAnnotationStub,
+ )
+ .indented(),
+ testAnnotationStub
)
- .issues(
- TestFunctionNameViolationDetector.ISSUE,
- )
+ .issues(TestFunctionNameViolationDetector.ISSUE)
.run()
.expectWarningCount(0)
.expect(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index d55d4e4..c22b50d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -21,6 +21,7 @@
import android.app.AlertDialog
import android.content.DialogInterface
import androidx.compose.animation.Crossfade
+import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
@@ -54,6 +55,7 @@
import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -66,6 +68,7 @@
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
@@ -75,6 +78,7 @@
import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.times
import com.android.compose.PlatformButton
+import com.android.compose.animation.Easings
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
@@ -665,10 +669,42 @@
modifier: Modifier = Modifier,
) {
val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState()
+ val appearFadeInAnimatable = remember { Animatable(0f) }
+ val appearMoveAnimatable = remember { Animatable(0f) }
+ val appearAnimationInitialOffset = with(LocalDensity.current) { 80.dp.toPx() }
actionButton?.let { actionButtonViewModel ->
+ LaunchedEffect(Unit) {
+ appearFadeInAnimatable.animateTo(
+ targetValue = 1f,
+ animationSpec =
+ tween(
+ durationMillis = 450,
+ delayMillis = 133,
+ easing = Easings.LegacyDecelerate,
+ )
+ )
+ }
+ LaunchedEffect(Unit) {
+ appearMoveAnimatable.animateTo(
+ targetValue = 1f,
+ animationSpec =
+ tween(
+ durationMillis = 450,
+ delayMillis = 133,
+ easing = Easings.StandardDecelerate,
+ )
+ )
+ }
+
Box(
- modifier = modifier,
+ modifier =
+ modifier.graphicsLayer {
+ // Translate the button up from an initially pushed-down position:
+ translationY = (1 - appearMoveAnimatable.value) * appearAnimationInitialOffset
+ // Fade the button in:
+ alpha = appearFadeInAnimatable.value
+ },
) {
Button(
onClick = actionButtonViewModel.onClick,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 07c2d3c..19d6038 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -52,6 +52,7 @@
import com.android.internal.R
import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel
+import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sqrt
@@ -72,15 +73,17 @@
centerDotsVertically: Boolean,
modifier: Modifier = Modifier,
) {
+ val scope = rememberCoroutineScope()
+ val density = LocalDensity.current
DisposableEffect(Unit) { onDispose { viewModel.onHidden() } }
val colCount = viewModel.columnCount
val rowCount = viewModel.rowCount
val dotColor = MaterialTheme.colorScheme.secondary
- val dotRadius = with(LocalDensity.current) { (DOT_DIAMETER_DP / 2).dp.toPx() }
+ val dotRadius = with(density) { (DOT_DIAMETER_DP / 2).dp.toPx() }
val lineColor = MaterialTheme.colorScheme.primary
- val lineStrokeWidth = with(LocalDensity.current) { LINE_STROKE_WIDTH_DP.dp.toPx() }
+ val lineStrokeWidth = with(density) { LINE_STROKE_WIDTH_DP.dp.toPx() }
// All dots that should be rendered on the grid.
val dots: List<PatternDotViewModel> by viewModel.dots.collectAsState()
@@ -101,7 +104,70 @@
integerResource(R.integer.lock_pattern_line_fade_out_duration)
val lineFadeOutAnimationDelayMs = integerResource(R.integer.lock_pattern_line_fade_out_delay)
- val scope = rememberCoroutineScope()
+ val dotAppearFadeInAnimatables = remember(dots) { dots.associateWith { Animatable(0f) } }
+ val dotAppearMoveUpAnimatables = remember(dots) { dots.associateWith { Animatable(0f) } }
+ val dotAppearMaxOffsetPixels =
+ remember(dots) {
+ dots.associateWith { dot -> with(density) { (80 + (20 * dot.y)).dp.toPx() } }
+ }
+ val dotAppearScaleAnimatables = remember(dots) { dots.associateWith { Animatable(0f) } }
+ LaunchedEffect(Unit) {
+ dotAppearFadeInAnimatables.forEach { (dot, animatable) ->
+ scope.launch {
+ // Maps a dot at x and y to an ordinal number to denote the order in which all dots
+ // are visited by the fade-in animation.
+ //
+ // The order is basically starting from the top-left most dot (at 0,0) and ending at
+ // the bottom-right most dot (at 2,2). The visitation order happens
+ // diagonal-by-diagonal. Here's a visual representation of the expected output:
+ // [0][1][3]
+ // [2][4][6]
+ // [5][7][8]
+ //
+ // There's an assumption here that the grid is 3x3. If it's not, this formula needs
+ // to be revisited.
+ check(viewModel.columnCount == 3 && viewModel.rowCount == 3)
+ val staggerOrder = max(0, min(8, 2 * (dot.x + dot.y) + (dot.y - 1)))
+
+ animatable.animateTo(
+ targetValue = 1f,
+ animationSpec =
+ tween(
+ delayMillis = 33 * staggerOrder,
+ durationMillis = 450,
+ easing = Easings.LegacyDecelerate,
+ )
+ )
+ }
+ }
+ dotAppearMoveUpAnimatables.forEach { (dot, animatable) ->
+ scope.launch {
+ animatable.animateTo(
+ targetValue = 1f,
+ animationSpec =
+ tween(
+ delayMillis = 0,
+ durationMillis = 450 + (33 * dot.y),
+ easing = Easings.StandardDecelerate,
+ )
+ )
+ }
+ }
+ dotAppearScaleAnimatables.forEach { (dot, animatable) ->
+ scope.launch {
+ animatable.animateTo(
+ targetValue = 1f,
+ animationSpec =
+ tween(
+ delayMillis = 33 * dot.y,
+ durationMillis = 450,
+ easing = Easings.LegacyDecelerate,
+ )
+ )
+ }
+ }
+ }
+
val view = LocalView.current
// When the current dot is changed, we need to update our animations.
@@ -322,10 +388,23 @@
// Draw each dot on the grid.
dots.forEach { dot ->
+ val initialOffset = checkNotNull(dotAppearMaxOffsetPixels[dot])
+ val appearOffset =
+ (1 - checkNotNull(dotAppearMoveUpAnimatables[dot]).value) * initialOffset
drawCircle(
- center = pixelOffset(dot, spacing, horizontalOffset, verticalOffset),
- color = dotColor,
- radius = dotRadius * (dotScalingAnimatables[dot]?.value ?: 1f),
+ center =
+ pixelOffset(
+ dot,
+ spacing,
+ horizontalOffset,
+ verticalOffset + appearOffset,
+ ),
+ color =
+ dotColor.copy(alpha = checkNotNull(dotAppearFadeInAnimatables[dot]).value),
+ radius =
+ dotRadius *
+ checkNotNull(dotScalingAnimatables[dot]).value *
+ checkNotNull(dotAppearScaleAnimatables[dot]).value,
)
}
}
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
index 4533f58..356bfe2 100644
--- 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
@@ -11,6 +11,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.dimensionResource
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
@@ -24,11 +25,10 @@
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
-import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
-import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.scene.ui.composable.SceneTransitionLayoutDataSource
@@ -75,6 +75,7 @@
viewModel: CommunalViewModel,
dataSourceDelegator: SceneDataSourceDelegator,
dialogFactory: SystemUIDialogFactory,
+ colors: CommunalColors,
) {
val coroutineScope = rememberCoroutineScope()
val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
@@ -135,7 +136,7 @@
emptyMap()
},
) {
- CommunalScene(viewModel, dialogFactory, modifier = modifier)
+ CommunalScene(viewModel, colors, dialogFactory, modifier = modifier)
}
}
}
@@ -143,15 +144,18 @@
/** Scene containing the glanceable hub UI. */
@Composable
private fun SceneScope.CommunalScene(
- viewModel: BaseCommunalViewModel,
+ viewModel: CommunalViewModel,
+ colors: CommunalColors,
dialogFactory: SystemUIDialogFactory,
modifier: Modifier = Modifier,
) {
+ val backgroundColor by colors.backgroundColor.collectAsState()
+
Box(
modifier =
Modifier.element(Communal.Elements.Scrim)
.fillMaxSize()
- .background(LocalAndroidColorScheme.current.outlineVariant),
+ .background(Color(backgroundColor.toArgb())),
)
Box(modifier.element(Communal.Elements.Content)) {
CommunalHub(viewModel = viewModel, dialogFactory = dialogFactory)
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 32c0313..ddfb5f6 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
@@ -32,6 +32,7 @@
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -45,6 +46,7 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridState
@@ -101,6 +103,9 @@
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.CustomAccessibilityAction
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.style.TextAlign
@@ -119,6 +124,7 @@
import com.android.internal.R.dimen.system_app_widget_background_radius
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
@@ -223,32 +229,33 @@
.motionEventSpy { onMotionEvent(viewModel) }
},
) {
- if (!viewModel.isEditMode && isEmptyState) {
- EmptyStateCta(
- contentPadding = contentPadding,
- viewModel = viewModel,
- )
- } else {
- CommunalHubLazyGrid(
- communalContent = communalContent,
- viewModel = viewModel,
- contentPadding = contentPadding,
- contentOffset = contentOffset,
- setGridCoordinates = { gridCoordinates = it },
- updateDragPositionForRemove = { offset ->
- isDraggingToRemove =
- isPointerWithinCoordinates(
- offset = gridCoordinates?.let { it.positionInWindow() + offset },
- containerToCheck = removeButtonCoordinates
- )
- isDraggingToRemove
- },
- onOpenWidgetPicker = onOpenWidgetPicker,
- gridState = gridState,
- contentListState = contentListState,
- selectedKey = selectedKey,
- widgetConfigurator = widgetConfigurator,
- )
+ AccessibilityContainer(viewModel) {
+ if (!viewModel.isEditMode && isEmptyState) {
+ EmptyStateCta(
+ contentPadding = contentPadding,
+ viewModel = viewModel,
+ )
+ } else {
+ CommunalHubLazyGrid(
+ communalContent = communalContent,
+ viewModel = viewModel,
+ contentPadding = contentPadding,
+ contentOffset = contentOffset,
+ setGridCoordinates = { gridCoordinates = it },
+ updateDragPositionForRemove = { offset ->
+ isDraggingToRemove =
+ isPointerWithinCoordinates(
+ offset = gridCoordinates?.let { it.positionInWindow() + offset },
+ containerToCheck = removeButtonCoordinates
+ )
+ isDraggingToRemove
+ },
+ gridState = gridState,
+ contentListState = contentListState,
+ selectedKey = selectedKey,
+ widgetConfigurator = widgetConfigurator,
+ )
+ }
}
// TODO(b/326060686): Remove this once keyguard indication area can persist over hub
@@ -385,7 +392,6 @@
contentListState: ContentListState,
setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
updateDragPositionForRemove: (offset: Offset) -> Boolean,
- onOpenWidgetPicker: (() -> Unit)? = null,
widgetConfigurator: WidgetConfigurator?,
) {
var gridModifier =
@@ -453,7 +459,6 @@
model = list[index],
viewModel = viewModel,
size = size,
- onOpenWidgetPicker = onOpenWidgetPicker,
selected = selected && !isDragging,
widgetConfigurator = widgetConfigurator,
)
@@ -731,7 +736,6 @@
size: SizeF,
selected: Boolean,
modifier: Modifier = Modifier,
- onOpenWidgetPicker: (() -> Unit)? = null,
widgetConfigurator: WidgetConfigurator? = null,
) {
when (model) {
@@ -1028,6 +1032,39 @@
)
}
+/** Container of the glanceable hub grid to enable accessibility actions when focused. */
+@Composable
+fun AccessibilityContainer(viewModel: BaseCommunalViewModel, content: @Composable () -> Unit) {
+ val context = LocalContext.current
+ val isFocusable by viewModel.isFocusable.collectAsState(initial = false)
+ Box(
+ modifier =
+ Modifier.fillMaxWidth().wrapContentHeight().thenIf(
+ isFocusable && !viewModel.isEditMode
+ ) {
+ Modifier.focusable(isFocusable).semantics {
+ contentDescription =
+ context.getString(
+ R.string.accessibility_content_description_for_communal_hub
+ )
+ customActions =
+ listOf(
+ CustomAccessibilityAction(
+ context.getString(
+ R.string.accessibility_action_label_close_communal_hub
+ )
+ ) {
+ viewModel.changeScene(CommunalScenes.Blank)
+ true
+ }
+ )
+ }
+ }
+ ) {
+ content()
+ }
+}
+
/**
* Returns the `contentPadding` of the grid. Use the vertical padding to push the grid content area
* below the toolbar and let the grid take the max size. This ensures the item can be dragged
@@ -1104,7 +1141,7 @@
val CardHeightHalf = 282.dp
val CardHeightThird = 177.33.dp
val CardOutlineWidth = 3.dp
- val GridTopSpacing = 72.dp
+ val GridTopSpacing = 64.dp
val GridHeight = CardHeightFull + GridTopSpacing
val Spacing = 16.dp
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
index dee2559..37fe798 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -161,9 +161,9 @@
private var isOnRemoveButton = false
fun onStarted() {
- // assume item will be added to the second to last position before CTA tile.
+ // assume item will be added to the end.
+ contentListState.list.add(placeHolder)
placeHolderIndex = contentListState.list.size - 1
- placeHolderIndex?.let { contentListState.list.add(it, placeHolder) }
}
fun onMoved(event: DragAndDropEvent) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
index 52cbffb..9afb4d5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.ui.composable
import com.android.systemui.keyguard.ui.composable.blueprint.CommunalBlueprintModule
-import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule
import com.android.systemui.keyguard.ui.composable.blueprint.ShortcutsBesideUdfpsBlueprintModule
import com.android.systemui.keyguard.ui.composable.section.OptionalSectionModule
import dagger.Module
@@ -26,7 +25,6 @@
includes =
[
CommunalBlueprintModule::class,
- DefaultBlueprintModule::class,
OptionalSectionModule::class,
ShortcutsBesideUdfpsBlueprintModule::class,
],
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 28e92aa..3152535 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -26,9 +26,11 @@
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -38,11 +40,9 @@
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.composable.section.TopAreaSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.IntoSet
import java.util.Optional
import javax.inject.Inject
+import kotlin.math.roundToInt
/**
* Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
@@ -68,6 +68,7 @@
val isUdfpsVisible = viewModel.isUdfpsVisible
val shouldUseSplitNotificationShade by
viewModel.shouldUseSplitNotificationShade.collectAsState()
+ val unfoldTranslations by viewModel.unfoldTranslations.collectAsState()
LockscreenLongPress(
viewModel = viewModel.longPress,
@@ -79,10 +80,25 @@
Column(
modifier = Modifier.fillMaxSize(),
) {
- with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(statusBarSection) {
+ StatusBar(
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ horizontal = { unfoldTranslations.start.roundToInt() },
+ )
+ )
+ }
Box {
- with(topAreaSection) { DefaultClockLayout() }
+ with(topAreaSection) {
+ DefaultClockLayout(
+ modifier =
+ Modifier.graphicsLayer {
+ translationX = unfoldTranslations.start
+ }
+ )
+ }
if (shouldUseSplitNotificationShade) {
with(notificationSection) {
Notifications(
@@ -127,8 +143,18 @@
// Aligned to bottom and NOT constrained by the lock icon.
with(bottomAreaSection) {
- Shortcut(isStart = true, applyPadding = true)
- Shortcut(isStart = false, applyPadding = true)
+ Shortcut(
+ isStart = true,
+ applyPadding = true,
+ modifier =
+ Modifier.graphicsLayer { translationX = unfoldTranslations.start },
+ )
+ Shortcut(
+ isStart = false,
+ applyPadding = true,
+ modifier =
+ Modifier.graphicsLayer { translationX = unfoldTranslations.end },
+ )
}
with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
},
@@ -201,8 +227,3 @@
}
}
}
-
-@Module
-interface DefaultBlueprintModule {
- @Binds @IntoSet fun blueprint(blueprint: DefaultBlueprint): ComposableLockscreenSceneBlueprint
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprintModule.kt
similarity index 62%
copy from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
copy to packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprintModule.kt
index b84b01e..e0bb26e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprintModule.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.dagger
+package com.android.systemui.keyguard.ui.composable.blueprint
-import javax.inject.Qualifier
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
-/** Wifi logs for inputs into [WifiRepositoryViaTrackerLib]. */
-@Qualifier
-@MustBeDocumented
[email protected](AnnotationRetention.RUNTIME)
-annotation class WifiTrackerLibInputLog
+@Module
+interface DefaultBlueprintModule {
+ @Binds @IntoSet fun blueprint(blueprint: DefaultBlueprint): ComposableLockscreenSceneBlueprint
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index b8f00dc..9d31955 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -26,9 +26,11 @@
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -43,6 +45,7 @@
import dagger.multibindings.IntoSet
import java.util.Optional
import javax.inject.Inject
+import kotlin.math.roundToInt
/**
* Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
@@ -68,6 +71,7 @@
val isUdfpsVisible = viewModel.isUdfpsVisible
val shouldUseSplitNotificationShade by
viewModel.shouldUseSplitNotificationShade.collectAsState()
+ val unfoldTranslations by viewModel.unfoldTranslations.collectAsState()
LockscreenLongPress(
viewModel = viewModel.longPress,
@@ -79,10 +83,25 @@
Column(
modifier = Modifier.fillMaxSize(),
) {
- with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(statusBarSection) {
+ StatusBar(
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ horizontal = { unfoldTranslations.start.roundToInt() },
+ )
+ )
+ }
Box {
- with(topAreaSection) { DefaultClockLayout() }
+ with(topAreaSection) {
+ DefaultClockLayout(
+ modifier =
+ Modifier.graphicsLayer {
+ translationX = unfoldTranslations.start
+ },
+ )
+ }
if (shouldUseSplitNotificationShade) {
with(notificationSection) {
Notifications(
@@ -111,12 +130,26 @@
}
// Constrained to the left of the lock icon (in left-to-right layouts).
- with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
+ with(bottomAreaSection) {
+ Shortcut(
+ isStart = true,
+ applyPadding = false,
+ modifier =
+ Modifier.graphicsLayer { translationX = unfoldTranslations.start },
+ )
+ }
with(lockSection) { LockIcon() }
// Constrained to the right of the lock icon (in left-to-right layouts).
- with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
+ with(bottomAreaSection) {
+ Shortcut(
+ isStart = false,
+ applyPadding = false,
+ modifier =
+ Modifier.graphicsLayer { translationX = unfoldTranslations.end },
+ )
+ }
// Aligned to bottom and constrained to below the lock icon.
Column(modifier = Modifier.fillMaxWidth()) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
index 2a99039..238a230 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
@@ -19,6 +19,8 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.boundsInWindow
@@ -39,9 +41,12 @@
params: BurnInParameters,
isClock: Boolean = false,
): Modifier {
- val burnIn = viewModel.movement(params)
+ val translationYState = remember { mutableStateOf(0F) }
+ val copiedParams = params.copy(translationY = { translationYState.value })
+ val burnIn = viewModel.movement(copiedParams)
val translationX by burnIn.map { it.translationX.toFloat() }.collectAsState(initial = 0f)
val translationY by burnIn.map { it.translationY.toFloat() }.collectAsState(initial = 0f)
+ translationYState.value = translationY
val scaleViewModel by
burnIn
.map {
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 02a12e4..c6c6f57 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
@@ -407,15 +407,16 @@
AndroidView(
factory = { context ->
ModernShadeCarrierGroupMobileView.constructAndBind(
- context = context,
- logger = viewModel.mobileIconsViewModel.logger,
- slot = "mobile_carrier_shade_group",
- viewModel =
- (viewModel.mobileIconsViewModel.viewModelForSub(
- subId,
- StatusBarLocation.SHADE_CARRIER_GROUP
- ) as ShadeCarrierGroupMobileIconViewModel),
- )
+ context = context,
+ logger = viewModel.mobileIconsViewModel.logger,
+ slot = "mobile_carrier_shade_group",
+ viewModel =
+ (viewModel.mobileIconsViewModel.viewModelForSub(
+ subId,
+ StatusBarLocation.SHADE_CARRIER_GROUP
+ ) as ShadeCarrierGroupMobileIconViewModel),
+ )
+ .also { it.setOnClickListener { viewModel.onShadeCarrierGroupClicked() } }
},
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 9bd6f81..84b1a4b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -62,6 +62,7 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneFloatAsState
+import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
@@ -289,6 +290,18 @@
remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
val tileSquishiness by
animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
+ val unfoldTranslationXForStartSide by
+ viewModel
+ .unfoldTranslationX(
+ isOnStartSide = true,
+ )
+ .collectAsState(0f)
+ val unfoldTranslationXForEndSide by
+ viewModel
+ .unfoldTranslationX(
+ isOnStartSide = false,
+ )
+ .collectAsState(0f)
val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
val density = LocalDensity.current
@@ -337,10 +350,18 @@
modifier =
Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
.then(brightnessMirrorShowingModifier)
+ .padding(
+ horizontal = { unfoldTranslationXForStartSide.roundToInt() },
+ )
)
Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
- Box(modifier = Modifier.weight(1f)) {
+ Box(
+ modifier =
+ Modifier.weight(1f).graphicsLayer {
+ translationX = unfoldTranslationXForStartSide
+ },
+ ) {
BrightnessMirror(
viewModel = viewModel.brightnessMirrorViewModel,
qsSceneAdapter = viewModel.qsSceneAdapter,
@@ -407,7 +428,7 @@
Modifier.weight(1f)
.fillMaxHeight()
.padding(bottom = navBarBottomHeight)
- .then(brightnessMirrorShowingModifier),
+ .then(brightnessMirrorShowingModifier)
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index fc511e1..0893b9d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -69,9 +69,19 @@
role = Role.Button
contentDescription = label
},
- color = MaterialTheme.colorScheme.primaryContainer,
+ color =
+ if (viewModel.isActive) {
+ MaterialTheme.colorScheme.tertiaryContainer
+ } else {
+ MaterialTheme.colorScheme.surface
+ },
shape = RoundedCornerShape(28.dp),
- contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ contentColor =
+ if (viewModel.isActive) {
+ MaterialTheme.colorScheme.onTertiaryContainer
+ } else {
+ MaterialTheme.colorScheme.onSurface
+ },
onClick = onClick,
) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 780e3f2..4f3a6c8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -40,14 +40,14 @@
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.android.systemui.common.ui.compose.Icon
-import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
import kotlinx.coroutines.flow.StateFlow
/** [ComposeVolumePanelUiComponent] implementing a toggleable button from a bottom row. */
class ToggleButtonComponent(
- private val viewModelFlow: StateFlow<ToggleButtonViewModel?>,
+ private val viewModelFlow: StateFlow<ButtonViewModel?>,
private val onCheckedChange: (isChecked: Boolean) -> Unit
) : ComposeVolumePanelUiComponent {
@@ -64,10 +64,10 @@
) {
BottomComponentButtonSurface {
val colors =
- if (viewModel.isChecked) {
+ if (viewModel.isActive) {
ButtonDefaults.buttonColors(
- containerColor = MaterialTheme.colorScheme.primaryContainer,
- contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+ contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
)
} else {
ButtonDefaults.buttonColors(
@@ -81,7 +81,7 @@
role = Role.Switch
contentDescription = label
},
- onClick = { onCheckedChange(!viewModel.isChecked) },
+ onClick = { onCheckedChange(!viewModel.isActive) },
shape = RoundedCornerShape(28.dp),
colors = colors
) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
index c743314..51e2064 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
@@ -30,9 +30,11 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
@@ -119,6 +121,7 @@
) {
for (itemIndex in items.indices) {
val item = items[itemIndex]
+ val isSelected = itemIndex == scope.selectedIndex
Row(
modifier =
Modifier.height(48.dp)
@@ -126,7 +129,7 @@
.semantics {
item.contentDescription?.let { contentDescription = it }
role = Role.Switch
- selected = itemIndex == scope.selectedIndex
+ selected = isSelected
}
.clickable(
interactionSource = null,
@@ -137,7 +140,11 @@
verticalAlignment = Alignment.CenterVertically,
) {
if (item.icon !== Empty) {
- with(items[itemIndex]) { icon() }
+ CompositionLocalProvider(
+ LocalContentColor provides colors.getIconColor(isSelected)
+ ) {
+ with(items[itemIndex]) { icon() }
+ }
}
}
}
@@ -163,7 +170,10 @@
) {
val item = items[itemIndex]
if (item.icon !== Empty) {
- with(items[itemIndex]) { label() }
+ val textColor = colors.getLabelColor(itemIndex == scope.selectedIndex)
+ CompositionLocalProvider(LocalContentColor provides textColor) {
+ with(items[itemIndex]) { label() }
+ }
}
}
}
@@ -265,8 +275,22 @@
val indicatorColor: Color,
/** Color of the indicator background. */
val indicatorBackgroundColor: Color,
+ /** Color of the icon. */
+ val iconColor: Color,
+ /** Color of the icon when it's selected. */
+ val selectedIconColor: Color,
+ /** Color of the label. */
+ val labelColor: Color,
+ /** Color of the label when it's selected. */
+ val selectedLabelColor: Color,
)
+private fun VolumePanelRadioButtonBarColors.getIconColor(selected: Boolean): Color =
+ if (selected) selectedIconColor else iconColor
+
+private fun VolumePanelRadioButtonBarColors.getLabelColor(selected: Boolean): Color =
+ if (selected) selectedLabelColor else labelColor
+
object VolumePanelRadioButtonBarDefaults {
val DefaultIndicatorBackgroundPadding = 8.dp
@@ -283,12 +307,20 @@
*/
@Composable
fun defaultColors(
- indicatorColor: Color = MaterialTheme.colorScheme.primaryContainer,
+ indicatorColor: Color = MaterialTheme.colorScheme.tertiaryContainer,
indicatorBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+ iconColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
+ selectedIconColor: Color = MaterialTheme.colorScheme.onTertiaryContainer,
+ labelColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
+ selectedLabelColor: Color = MaterialTheme.colorScheme.onSurface,
): VolumePanelRadioButtonBarColors =
VolumePanelRadioButtonBarColors(
indicatorColor = indicatorColor,
indicatorBackgroundColor = indicatorBackgroundColor,
+ iconColor = iconColor,
+ selectedIconColor = selectedIconColor,
+ labelColor = labelColor,
+ selectedLabelColor = selectedLabelColor,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index 9a98bde..12d2bc2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.spatialaudio.ui.composable
import androidx.compose.foundation.basicMarquee
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -29,7 +30,6 @@
import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
-import com.android.systemui.common.ui.compose.toColor
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
@@ -52,7 +52,7 @@
VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN,
0,
null,
- viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isChecked }
+ viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isActive }
)
volumePanelPopup.show(expandable, { Title() }, { Content(it) })
}
@@ -85,21 +85,16 @@
for (buttonViewModel in enabledModelStates) {
val label = buttonViewModel.button.label.toString()
item(
- isSelected = buttonViewModel.button.isChecked,
+ isSelected = buttonViewModel.button.isActive,
onItemSelected = { viewModel.setEnabled(buttonViewModel.model) },
contentDescription = label,
- icon = {
- Icon(
- icon = buttonViewModel.button.icon,
- tint = buttonViewModel.iconColor.toColor(),
- )
- },
+ icon = { Icon(icon = buttonViewModel.button.icon) },
label = {
Text(
modifier = Modifier.basicMarquee(),
text = label,
style = MaterialTheme.typography.labelMedium,
- color = buttonViewModel.labelColor.toColor(),
+ color = LocalContentColor.current,
textAlign = TextAlign.Center,
maxLines = 2
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index a54d005..a3467f2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -107,7 +107,7 @@
}
}
transition.AnimatedVisibility(
- visible = { it },
+ visible = { it || !isExpandable },
enter =
expandVertically(animationSpec = tween(durationMillis = EXPAND_DURATION_MILLIS)),
exit =
@@ -122,7 +122,7 @@
val sliderState by sliderViewModel.slider.collectAsState()
transition.AnimatedVisibility(
modifier = Modifier.padding(top = 16.dp),
- visible = { it },
+ visible = { it || !isExpandable },
enter = enterTransition(index = index, totalCount = viewModels.size),
exit = exitTransition(index = index, totalCount = viewModels.size)
) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 228d292..9f5ab3c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -61,7 +61,8 @@
modifier =
modifier.clearAndSetSemantics {
if (!state.isEnabled) disabled()
- contentDescription = state.label
+ contentDescription =
+ state.disabledMessage?.let { "${state.label}, $it" } ?: state.label
// provide a not animated value to the a11y because it fails to announce the
// settled value when it changes rapidly.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index 910cd5e..1bf541a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -16,7 +16,7 @@
package com.android.systemui.volume.panel.ui.composable
-import androidx.compose.foundation.clickable
+import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -38,6 +38,7 @@
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.dimensionResource
@@ -75,21 +76,13 @@
modifier =
modifier
.fillMaxSize()
- .clickable(onClick = onDismiss)
+ .volumePanelClick(onDismiss)
.volumePanelPaddings(isPortrait = isPortrait),
contentAlignment = Alignment.BottomCenter,
) {
val radius = dimensionResource(R.dimen.volume_panel_corner_radius)
Surface(
- modifier =
- Modifier.clickable(
- interactionSource = null,
- indication = null,
- onClick = {
- // prevent windowCloseOnTouchOutside from dismissing when tapped
- // on the panel itself.
- },
- ),
+ modifier = Modifier.volumePanelClick {},
shape = RoundedCornerShape(topStart = radius, topEnd = radius),
color = MaterialTheme.colorScheme.surfaceContainer,
) {
@@ -185,3 +178,13 @@
)
}
}
+
+/**
+ * For some reason adding clickable modifier onto the VolumePanel affects the traversal order:
+ * b/331155283.
+ *
+ * TODO(b/334870995) revert this to Modifier.clickable
+ */
+@Composable
+private fun Modifier.volumePanelClick(onClick: () -> Unit) =
+ pointerInput(onClick) { detectTapGestures { onClick() } }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index dc3b612..418c6bb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -44,10 +44,27 @@
internal val scope = SceneScopeImpl(layoutImpl, this)
var content by mutableStateOf(content)
- var userActions by mutableStateOf(actions)
+ private var _userActions by mutableStateOf(checkValid(actions))
var zIndex by mutableFloatStateOf(zIndex)
var targetSize by mutableStateOf(IntSize.Zero)
+ var userActions
+ get() = _userActions
+ set(value) {
+ _userActions = checkValid(value)
+ }
+
+ private fun checkValid(
+ userActions: Map<UserAction, UserActionResult>
+ ): Map<UserAction, UserActionResult> {
+ userActions.forEach { (action, result) ->
+ if (key == result.toScene) {
+ error("Transition to the same scene is not supported. Scene $key, action $action")
+ }
+ }
+ return userActions
+ }
+
@Composable
@OptIn(ExperimentalComposeUiApi::class)
fun Content(modifier: Modifier = Modifier) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 723a182..2eaccb4 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -48,10 +48,14 @@
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.test.assertSizeIsEqualTo
import com.android.compose.test.subjects.DpOffsetSubject
import com.android.compose.test.subjects.assertThat
import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -62,7 +66,7 @@
private val LayoutSize = 300.dp
}
- private var currentScene by mutableStateOf(TestScenes.SceneA)
+ private var currentScene by mutableStateOf(SceneA)
private lateinit var layoutState: SceneTransitionLayoutState
// We use createAndroidComposeRule() here and not createComposeRule() because we need an
@@ -84,15 +88,15 @@
modifier = Modifier.size(LayoutSize),
) {
scene(
- TestScenes.SceneA,
- userActions = mapOf(Back to TestScenes.SceneB),
+ SceneA,
+ userActions = mapOf(Back to SceneB),
) {
Box(Modifier.fillMaxSize()) {
SharedFoo(size = 50.dp, childOffset = 0.dp, Modifier.align(Alignment.TopEnd))
Text("SceneA")
}
}
- scene(TestScenes.SceneB) {
+ scene(SceneB) {
Box(Modifier.fillMaxSize()) {
SharedFoo(
size = 100.dp,
@@ -102,7 +106,7 @@
Text("SceneB")
}
}
- scene(TestScenes.SceneC) {
+ scene(SceneC) {
Box(Modifier.fillMaxSize()) {
SharedFoo(
size = 150.dp,
@@ -144,42 +148,42 @@
rule.onNodeWithText("SceneB").assertDoesNotExist()
rule.onNodeWithText("SceneC").assertDoesNotExist()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
// Change to scene B. Only that scene is displayed.
- currentScene = TestScenes.SceneB
+ currentScene = SceneB
rule.onNodeWithText("SceneA").assertDoesNotExist()
rule.onNodeWithText("SceneB").assertIsDisplayed()
rule.onNodeWithText("SceneC").assertDoesNotExist()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
}
@Test
fun testBack() {
rule.setContent { TestContent() }
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
rule.activity.onBackPressed()
rule.waitForIdle()
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
}
@Test
fun testTransitionState() {
rule.setContent { TestContent() }
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
// We will advance the clock manually.
rule.mainClock.autoAdvance = false
// Change the current scene. Until composition is triggered, this won't change the layout
// state.
- currentScene = TestScenes.SceneB
+ currentScene = SceneB
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
// On the next frame, we will recompose because currentScene changed, which will start the
// transition (i.e. it will change the transitionState to be a Transition) in a
@@ -187,8 +191,8 @@
rule.mainClock.advanceTimeByFrame()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
val transition = layoutState.transitionState as TransitionState.Transition
- assertThat(transition.fromScene).isEqualTo(TestScenes.SceneA)
- assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+ assertThat(transition.fromScene).isEqualTo(SceneA)
+ assertThat(transition.toScene).isEqualTo(SceneB)
assertThat(transition.progress).isEqualTo(0f)
// Then, on the next frame, the animator we started gets its initial value and clock
@@ -216,7 +220,7 @@
// B.
rule.mainClock.advanceTimeByFrame()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
}
@Test
@@ -242,7 +246,7 @@
// Go to scene B and let the animation start. See [testLayoutState()] and
// [androidx.compose.ui.test.MainTestClock] to understand why we need to advance the clock
// by 2 frames to be at the start of the animation.
- currentScene = TestScenes.SceneB
+ currentScene = SceneB
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
@@ -251,7 +255,7 @@
// Foo is shared between Scene A and Scene B, and is therefore placed/drawn in Scene B given
// that B has a higher zIndex than A.
- sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneB))
+ sharedFoo = rule.onNode(isElement(TestElements.Foo, SceneB))
// In scene B, foo is at the top start (x = 0, y = 0) of the layout and has a size of
// 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we
@@ -273,7 +277,7 @@
.of(DpOffset(25.dp, 25.dp))
// Animate to scene C, let the animation start then go to the middle of the transition.
- currentScene = TestScenes.SceneC
+ currentScene = SceneC
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
@@ -285,7 +289,7 @@
val expectedLeft = 0.dp
val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress
- sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneC))
+ sharedFoo = rule.onNode(isElement(TestElements.Foo, SceneC))
assertThat((layoutState.transitionState as TransitionState.Transition).progress)
.isEqualTo(interpolatedProgress)
sharedFoo.assertWidthIsEqualTo(expectedSize)
@@ -302,15 +306,15 @@
// Wait for the transition to C to finish.
rule.mainClock.advanceTimeBy(TestTransitionDuration)
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneC)
// Go back to scene A. This should happen instantly (once the animation started, i.e. after
// 2 frames) given that we use a snap() animation spec.
- currentScene = TestScenes.SceneA
+ currentScene = SceneA
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
}
@Test
@@ -346,4 +350,28 @@
)
}
}
+
+ @Test
+ fun userActionFromSceneAToSceneA_throwsNotSupported() {
+ val exception: IllegalStateException =
+ assertThrows(IllegalStateException::class.java) {
+ rule.setContent {
+ SceneTransitionLayout(
+ state =
+ updateSceneTransitionLayoutState(
+ currentScene = currentScene,
+ onChangeScene = { currentScene = it },
+ transitions = EmptyTestTransitions
+ ),
+ modifier = Modifier.size(LayoutSize),
+ ) {
+ // from SceneA to SceneA
+ scene(SceneA, userActions = mapOf(Back to SceneA), content = {})
+ }
+ }
+ }
+
+ assertThat(exception).hasMessageThat().contains(Back.toString())
+ assertThat(exception).hasMessageThat().contains(SceneA.debugName)
+ }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 003c572..f1f1b57 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -28,8 +28,8 @@
import android.util.AttributeSet
import android.util.MathUtils.constrainedMap
import android.util.TypedValue
-import android.view.View
import android.view.View.MeasureSpec.EXACTLY
+import android.view.View
import android.widget.TextView
import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
@@ -88,6 +88,10 @@
private var textAnimator: TextAnimator? = null
private var onTextAnimatorInitialized: Runnable? = null
+ private var translateForCenterAnimation = false
+ private val parentWidth: Int
+ get() = (parent as View).measuredWidth
+
// last text size which is not constrained by view height
private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE
@VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator =
@@ -116,14 +120,14 @@
try {
dozingWeightInternal = animatableClockViewAttributes.getInt(
R.styleable.AnimatableClockView_dozeWeight,
- 100
+ /* default = */ 100
)
lockScreenWeightInternal = animatableClockViewAttributes.getInt(
R.styleable.AnimatableClockView_lockScreenWeight,
- 300
+ /* default = */ 300
)
chargeAnimationDelay = animatableClockViewAttributes.getInt(
- R.styleable.AnimatableClockView_chargeAnimationDelay, 200
+ R.styleable.AnimatableClockView_chargeAnimationDelay, /* default = */ 200
)
} finally {
animatableClockViewAttributes.recycle()
@@ -134,12 +138,12 @@
defStyleAttr, defStyleRes
)
- isSingleLineInternal =
- try {
- textViewAttributes.getBoolean(android.R.styleable.TextView_singleLine, false)
- } finally {
- textViewAttributes.recycle()
- }
+ try {
+ isSingleLineInternal = textViewAttributes.getBoolean(
+ android.R.styleable.TextView_singleLine, /* default = */ false)
+ } finally {
+ textViewAttributes.recycle()
+ }
refreshFormat()
}
@@ -206,6 +210,7 @@
super.setTextSize(TypedValue.COMPLEX_UNIT_PX,
min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F))
}
+
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val animator = textAnimator
if (animator == null) {
@@ -215,18 +220,27 @@
} else {
animator.updateLayout(layout)
}
+
if (migratedClocks && hasCustomPositionUpdatedAnimation) {
// Expand width to avoid clock being clipped during stepping animation
- setMeasuredDimension(measuredWidth +
- MeasureSpec.getSize(widthMeasureSpec) / 2, measuredHeight)
+ val targetWidth = measuredWidth + MeasureSpec.getSize(widthMeasureSpec) / 2
+
+ // This comparison is effectively a check if we're in splitshade or not
+ translateForCenterAnimation = parentWidth > targetWidth
+ if (translateForCenterAnimation) {
+ setMeasuredDimension(targetWidth, measuredHeight)
+ }
+ } else {
+ translateForCenterAnimation = false
}
}
override fun onDraw(canvas: Canvas) {
- if (migratedClocks && hasCustomPositionUpdatedAnimation) {
- canvas.save()
- canvas.translate((parent as View).measuredWidth / 4F, 0F)
+ canvas.save()
+ if (translateForCenterAnimation) {
+ canvas.translate(parentWidth / 4f, 0f)
}
+
logger.d({ "onDraw($str1)"}) { str1 = text.toString() }
// Use textAnimator to render text if animation is enabled.
// Otherwise default to using standard draw functions.
@@ -236,9 +250,8 @@
} else {
super.onDraw(canvas)
}
- if (migratedClocks && hasCustomPositionUpdatedAnimation) {
- canvas.restore()
- }
+
+ canvas.restore()
}
override fun invalidate() {
@@ -527,9 +540,9 @@
* means it finished moving.
*/
fun offsetGlyphsForStepClockAnimation(
- clockStartLeft: Int,
- clockMoveDirection: Int,
- moveFraction: Float
+ clockStartLeft: Int,
+ clockMoveDirection: Int,
+ moveFraction: Float
) {
val isMovingToCenter = if (isLayoutRtl) clockMoveDirection < 0 else clockMoveDirection > 0
val currentMoveAmount = left - clockStartLeft
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 2d4b63e..ed2d20c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -50,6 +50,7 @@
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
@@ -65,8 +66,7 @@
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.FakeSceneDataSource
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
@@ -87,7 +87,6 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.GlobalSettings
import com.google.common.truth.Truth
-import dagger.Lazy
import java.util.Optional
import junit.framework.Assert
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -171,7 +170,7 @@
private lateinit var sceneInteractor: SceneInteractor
private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
private lateinit var deviceEntryInteractor: DeviceEntryInteractor
- @Mock private lateinit var primaryBouncerInteractor: Lazy<PrimaryBouncerInteractor>
+ @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
private lateinit var fakeSceneDataSource: FakeSceneDataSource
@@ -217,9 +216,13 @@
)
mSetFlagsRule.disableFlags(
FLAG_SIDEFPS_CONTROLLER_REFACTOR,
- AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
- AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
)
+ if (!SceneContainerFlag.isEnabled) {
+ mSetFlagsRule.disableFlags(
+ AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
+ )
+ }
keyguardPasswordViewController =
KeyguardPasswordViewController(
@@ -268,7 +271,6 @@
falsingManager,
userSwitcherController,
featureFlags,
- kosmos.sceneContainerFlags,
globalSettings,
sessionTracker,
Optional.of(sideFpsController),
@@ -283,7 +285,7 @@
deviceProvisionedController,
faceAuthAccessibilityDelegate,
keyguardTransitionInteractor,
- primaryBouncerInteractor,
+ { primaryBouncerInteractor },
) {
deviceEntryInteractor
}
@@ -804,17 +806,17 @@
}
@Test
+ @EnableSceneContainer
fun dismissesKeyguard_whenSceneChangesToGone() =
kosmos.testScope.runTest {
- kosmos.fakeSceneContainerFlags.enabled = true
// Upon init, we have never dismisses the keyguard.
underTest.onInit()
runCurrent()
- verify(viewMediatorCallback, never()).keyguardDone(anyInt())
+ verify(primaryBouncerInteractor, never())
+ .notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
// Once the view is attached, we start listening but simply going to the bouncer scene
- // is
- // not enough to trigger a dismissal of the keyguard.
+ // is not enough to trigger a dismissal of the keyguard.
underTest.onViewAttached()
fakeSceneDataSource.pause()
sceneInteractor.changeScene(Scenes.Bouncer, "reason")
@@ -830,7 +832,8 @@
fakeSceneDataSource.unpause(expectedScene = Scenes.Bouncer)
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Bouncer)
runCurrent()
- verify(viewMediatorCallback, never()).keyguardDone(anyInt())
+ verify(primaryBouncerInteractor, never())
+ .notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
// While listening, going from the bouncer scene to the gone scene, does dismiss the
// keyguard.
@@ -852,11 +855,11 @@
fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
runCurrent()
- verify(viewMediatorCallback).keyguardDone(anyInt())
+ verify(primaryBouncerInteractor).notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
// While listening, moving back to the bouncer scene does not dismiss the keyguard
// again.
- clearInvocations(viewMediatorCallback)
+ clearInvocations(primaryBouncerInteractor)
fakeSceneDataSource.pause()
sceneInteractor.changeScene(Scenes.Bouncer, "reason")
sceneTransitionStateFlow.value =
@@ -871,7 +874,8 @@
fakeSceneDataSource.unpause(expectedScene = Scenes.Bouncer)
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Bouncer)
runCurrent()
- verify(viewMediatorCallback, never()).keyguardDone(anyInt())
+ verify(primaryBouncerInteractor, never())
+ .notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
// Detaching the view stops listening, so moving from the bouncer scene to the gone
// scene
@@ -891,7 +895,8 @@
fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
runCurrent()
- verify(viewMediatorCallback, never()).keyguardDone(anyInt())
+ verify(primaryBouncerInteractor, never())
+ .notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
// While not listening, moving to the lockscreen does not dismiss the keyguard.
fakeSceneDataSource.pause()
@@ -908,7 +913,8 @@
fakeSceneDataSource.unpause(expectedScene = Scenes.Lockscreen)
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
runCurrent()
- verify(viewMediatorCallback, never()).keyguardDone(anyInt())
+ verify(primaryBouncerInteractor, never())
+ .notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
// Reattaching the view starts listening again so moving from the bouncer scene to the
// gone scene now does dismiss the keyguard again, this time from lockscreen.
@@ -927,7 +933,7 @@
fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
runCurrent()
- verify(viewMediatorCallback).keyguardDone(anyInt())
+ verify(primaryBouncerInteractor).notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
similarity index 98%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index 9f52ae9..04c4efb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
import static com.google.common.truth.Truth.assertThat;
@@ -45,9 +45,9 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.scrim.ScrimController;
+import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
-import com.android.systemui.dreams.touch.scrim.ScrimController;
-import com.android.systemui.dreams.touch.scrim.ScrimManager;
import com.android.systemui.settings.FakeUserTracker;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shared.system.InputChannelCompat;
@@ -88,7 +88,7 @@
FlingAnimationUtils mFlingAnimationUtilsClosing;
@Mock
- DreamTouchHandler.TouchSession mTouchSession;
+ TouchHandler.TouchSession mTouchSession;
BouncerSwipeTouchHandler mTouchHandler;
@@ -258,7 +258,7 @@
}
private static void onSessionStartHelper(BouncerSwipeTouchHandler touchHandler,
- DreamTouchHandler.TouchSession touchSession,
+ TouchHandler.TouchSession touchSession,
NotificationShadeWindowController notificationShadeWindowController) {
touchHandler.onSessionStart(touchSession);
verify(notificationShadeWindowController).setForcePluginOpen(eq(true), any());
@@ -677,8 +677,8 @@
@Test
public void testTouchSessionOnRemovedCalledTwice() {
mTouchHandler.onSessionStart(mTouchSession);
- ArgumentCaptor<DreamTouchHandler.TouchSession.Callback> onRemovedCallbackCaptor =
- ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.Callback.class);
+ ArgumentCaptor<TouchHandler.TouchSession.Callback> onRemovedCallbackCaptor =
+ ArgumentCaptor.forClass(TouchHandler.TouchSession.Callback.class);
verify(mTouchSession).registerCallback(onRemovedCallbackCaptor.capture());
onRemovedCallbackCaptor.getValue().onRemoved();
onRemovedCallbackCaptor.getValue().onRemoved();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
similarity index 95%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
index 6aa821f..27bffd0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
import static com.google.common.truth.Truth.assertThat;
@@ -52,7 +52,7 @@
ShadeViewController mShadeViewController;
@Mock
- DreamTouchHandler.TouchSession mTouchSession;
+ TouchHandler.TouchSession mTouchSession;
ShadeTouchHandler mTouchHandler;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimControllerTest.java
similarity index 96%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimControllerTest.java
index 7cdd478..099771c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimControllerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/ScrimManagerTest.java
similarity index 96%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/ScrimManagerTest.java
index ebbcf98..82de50c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/ScrimManagerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
import static com.google.common.truth.Truth.assertThat;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index caf9219..1cd9d76 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -32,7 +32,6 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.testKosmos
@@ -86,7 +85,6 @@
AuthenticationRepositoryImpl(
applicationScope = testScope.backgroundScope,
backgroundDispatcher = kosmos.testDispatcher,
- flags = kosmos.sceneContainerFlags,
clock = clock,
getSecurityMode = getSecurityMode,
userRepository = userRepository,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 85774c6..60b48f2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -571,7 +571,7 @@
// THEN the view layout is never updated
verify(windowManager, never()).updateViewLayout(any(), any())
- // CLEANUPL we hide to end the job that listens for the finishedGoingToSleep signal
+ // CLEANUP we hide to end the job that listens for the finishedGoingToSleep signal
controllerOverlay.hide()
}
}
@@ -595,7 +595,7 @@
controllerOverlay.updateOverlayParams(overlayParams)
// THEN the view layout is updated
- verify(windowManager, never()).updateViewLayout(any(), any())
+ verify(windowManager).updateViewLayout(any(), any())
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
index 741cde8..d850f17 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
@@ -29,10 +29,10 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
@@ -56,6 +56,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class BouncerActionButtonInteractorTest : SysuiTestCase() {
@Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
@@ -75,7 +76,6 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- kosmos.fakeSceneContainerFlags.enabled = true
mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index b0d03b1..361b078 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -18,6 +18,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -25,14 +26,15 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
+import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
@@ -48,11 +50,13 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class BouncerInteractorTest : SysuiTestCase() {
- private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val authenticationInteractor = kosmos.authenticationInteractor
+ private val uiEventLoggerFake = kosmos.uiEventLoggerFake
private lateinit var underTest: BouncerInteractor
@@ -83,6 +87,7 @@
// Thus, when auth method is sim, we expect to skip here.
assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
.isEqualTo(AuthenticationResult.SKIPPED)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0)
}
@Test
@@ -104,6 +109,8 @@
// Wrong 6-digit pin
assertThat(underTest.authenticate(listOf(1, 2, 3, 5, 5, 6), tryAutoConfirm = true))
.isEqualTo(AuthenticationResult.FAILED)
+ assertThat(uiEventLoggerFake[0].eventId)
+ .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_FAILURE.id)
// Correct input.
assertThat(
@@ -113,6 +120,9 @@
)
)
.isEqualTo(AuthenticationResult.SUCCEEDED)
+ assertThat(uiEventLoggerFake[1].eventId)
+ .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS.id)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
}
@Test
@@ -148,6 +158,8 @@
// Wrong input.
assertThat(underTest.authenticate("alohamora".toList()))
.isEqualTo(AuthenticationResult.FAILED)
+ assertThat(uiEventLoggerFake[0].eventId)
+ .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_FAILURE.id)
// Too short input.
assertThat(
@@ -165,6 +177,9 @@
// Correct input.
assertThat(underTest.authenticate("password".toList()))
.isEqualTo(AuthenticationResult.SUCCEEDED)
+ assertThat(uiEventLoggerFake[1].eventId)
+ .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS.id)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
}
@Test
@@ -187,6 +202,8 @@
assertThat(wrongPattern.size)
.isAtLeast(kosmos.fakeAuthenticationRepository.minPatternLength)
assertThat(underTest.authenticate(wrongPattern)).isEqualTo(AuthenticationResult.FAILED)
+ assertThat(uiEventLoggerFake[0].eventId)
+ .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_FAILURE.id)
// Too short input.
val tooShortPattern =
@@ -200,6 +217,9 @@
// Correct input.
assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
.isEqualTo(AuthenticationResult.SUCCEEDED)
+ assertThat(uiEventLoggerFake[1].eventId)
+ .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS.id)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 0db0e07..b83c0ce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -34,10 +34,10 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
@@ -60,6 +60,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class BouncerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -70,7 +71,6 @@
@Before
fun setUp() {
- kosmos.fakeSceneContainerFlags.enabled = true
underTest = kosmos.bouncerViewModel
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 5bb36a0..256687b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -389,6 +389,61 @@
assertThat(isAnimationEnabled).isTrue()
}
+ @Test
+ fun onPinButtonClicked_whenInputSameLengthAsHintedPin_ignoresClick() =
+ testScope.runTest {
+ val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
+ val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+ assertThat(hintedPinLength).isEqualTo(FakeAuthenticationRepository.HINTING_PIN_LENGTH)
+ lockDeviceAndOpenPinBouncer()
+
+ repeat(FakeAuthenticationRepository.HINTING_PIN_LENGTH - 1) { repetition ->
+ underTest.onPinButtonClicked(repetition + 1)
+ runCurrent()
+ }
+ kosmos.fakeAuthenticationRepository.pauseCredentialChecking()
+ // If credential checking were not paused, this would check the credentials and succeed.
+ underTest.onPinButtonClicked(FakeAuthenticationRepository.HINTING_PIN_LENGTH)
+ runCurrent()
+
+ // This one should be ignored because the user has already entered a number of digits
+ // that's equal to the length of the hinting PIN length. It should result in a PIN
+ // that's exactly the same length as the hinting PIN length.
+ underTest.onPinButtonClicked(FakeAuthenticationRepository.HINTING_PIN_LENGTH + 1)
+ runCurrent()
+
+ assertThat(pin)
+ .isEqualTo(
+ buildList {
+ repeat(FakeAuthenticationRepository.HINTING_PIN_LENGTH) { index ->
+ add(index + 1)
+ }
+ }
+ )
+
+ kosmos.fakeAuthenticationRepository.unpauseCredentialChecking()
+ runCurrent()
+ assertThat(pin).isEmpty()
+ }
+
+ @Test
+ fun onPinButtonClicked_whenPinNotHinted_doesNotIgnoreClick() =
+ testScope.runTest {
+ val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(false)
+ val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+ assertThat(hintedPinLength).isNull()
+ lockDeviceAndOpenPinBouncer()
+
+ repeat(FakeAuthenticationRepository.HINTING_PIN_LENGTH + 1) { repetition ->
+ underTest.onPinButtonClicked(repetition + 1)
+ runCurrent()
+ }
+
+ assertThat(pin).hasSize(FakeAuthenticationRepository.HINTING_PIN_LENGTH + 1)
+ }
+
private fun TestScope.switchToScene(toScene: SceneKey) {
val currentScene by collectLastValue(sceneInteractor.currentScene)
val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index f21e969..497180b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -52,6 +52,7 @@
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -61,7 +62,6 @@
import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.fakeUserTracker
@@ -698,10 +698,9 @@
}
@Test
+ @EnableSceneContainer
fun isCommunalShowing_whenSceneContainerEnabled() =
testScope.runTest {
- kosmos.fakeSceneContainerFlags.enabled = true
-
// Verify default is false
val isCommunalShowing by collectLastValue(underTest.isCommunalShowing)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 5caf35b..37a6ac6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -38,6 +38,7 @@
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.TrustAgentDisabled
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UnattendedUpdate
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UserLockdown
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.fakeSystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
@@ -47,7 +48,6 @@
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -62,6 +62,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class DeviceEntryInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -74,7 +75,6 @@
@Before
fun setUp() {
- kosmos.fakeSceneContainerFlags.enabled = true
underTest = kosmos.deviceEntryInteractor
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 2af6566..41bc1dc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -39,10 +39,10 @@
import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
import com.android.systemui.complication.ComplicationHostViewController;
-import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
import com.android.systemui.statusbar.BlurUtils;
import org.junit.Before;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
deleted file mode 100644
index c143468..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ /dev/null
@@ -1,549 +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.systemui.dreams;
-
-import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.service.dreams.IDreamOverlay;
-import android.service.dreams.IDreamOverlayCallback;
-import android.service.dreams.IDreamOverlayClient;
-import android.service.dreams.IDreamOverlayClientCallback;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.WindowManagerImpl;
-
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleRegistry;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.complication.ComplicationLayoutEngine;
-import com.android.systemui.dreams.complication.HideComplicationTouchHandler;
-import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
-import com.android.systemui.dreams.dagger.DreamOverlayComponent;
-import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
-import com.android.systemui.touch.TouchInsetManager;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.utils.leaks.LeakCheckedTest;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class DreamOverlayServiceTest extends SysuiTestCase {
- private static final ComponentName LOW_LIGHT_COMPONENT = new ComponentName("package",
- "lowlight");
-
- private static final ComponentName HOME_CONTROL_PANEL_DREAM_COMPONENT =
- new ComponentName("package", "homeControlPanel");
- private static final String DREAM_COMPONENT = "package/dream";
- private static final String WINDOW_NAME = "test";
- private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
- private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
-
- @Mock
- DreamOverlayLifecycleOwner mLifecycleOwner;
-
- @Mock
- LifecycleRegistry mLifecycleRegistry;
-
- @Rule
- public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
-
- WindowManager.LayoutParams mWindowParams;
-
- @Mock
- IDreamOverlayCallback mDreamOverlayCallback;
-
- @Mock
- WindowManagerImpl mWindowManager;
-
- @Mock
- com.android.systemui.complication.dagger.ComplicationComponent.Factory
- mComplicationComponentFactory;
-
- @Mock
- com.android.systemui.complication.dagger.ComplicationComponent mComplicationComponent;
-
- @Mock
- ComplicationLayoutEngine mComplicationVisibilityController;
-
- @Mock
- ComplicationComponent.Factory mDreamComplicationComponentFactory;
-
- @Mock
- ComplicationComponent mDreamComplicationComponent;
-
- @Mock
- HideComplicationTouchHandler mHideComplicationTouchHandler;
-
- @Mock
- DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
-
- @Mock
- DreamOverlayComponent mDreamOverlayComponent;
-
- @Mock
- DreamOverlayContainerView mDreamOverlayContainerView;
-
- @Mock
- DreamOverlayContainerViewController mDreamOverlayContainerViewController;
-
- @Mock
- KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
- @Mock
- DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
-
- @Mock
- DreamOverlayStateController mStateController;
-
- @Mock
- ViewGroup mDreamOverlayContainerViewParent;
-
- @Mock
- TouchInsetManager mTouchInsetManager;
-
- @Mock
- UiEventLogger mUiEventLogger;
-
- @Mock
- DreamOverlayCallbackController mDreamOverlayCallbackController;
-
- @Captor
- ArgumentCaptor<View> mViewCaptor;
-
- DreamOverlayService mService;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
-
- when(mDreamOverlayComponent.getDreamOverlayContainerViewController())
- .thenReturn(mDreamOverlayContainerViewController);
- when(mLifecycleOwner.getRegistry())
- .thenReturn(mLifecycleRegistry);
- when(mDreamOverlayComponent.getDreamOverlayTouchMonitor())
- .thenReturn(mDreamOverlayTouchMonitor);
- when(mComplicationComponentFactory
- .create(any(), any(), any(), any()))
- .thenReturn(mComplicationComponent);
- when(mComplicationComponent.getVisibilityController())
- .thenReturn(mComplicationVisibilityController);
- when(mDreamComplicationComponent.getHideComplicationTouchHandler())
- .thenReturn(mHideComplicationTouchHandler);
- when(mDreamComplicationComponentFactory
- .create(any(), any()))
- .thenReturn(mDreamComplicationComponent);
- when(mDreamOverlayComponentFactory
- .create(any(), any(), any(), any()))
- .thenReturn(mDreamOverlayComponent);
- when(mDreamOverlayContainerViewController.getContainerView())
- .thenReturn(mDreamOverlayContainerView);
-
- mWindowParams = new WindowManager.LayoutParams();
- mService = new DreamOverlayService(
- mContext,
- mLifecycleOwner,
- mMainExecutor,
- mWindowManager,
- mComplicationComponentFactory,
- mDreamComplicationComponentFactory,
- mDreamOverlayComponentFactory,
- mStateController,
- mKeyguardUpdateMonitor,
- mUiEventLogger,
- mTouchInsetManager,
- LOW_LIGHT_COMPONENT,
- HOME_CONTROL_PANEL_DREAM_COMPONENT,
- mDreamOverlayCallbackController,
- WINDOW_NAME);
- }
-
- public IDreamOverlayClient getClient() throws RemoteException {
- final IBinder proxy = mService.onBind(new Intent());
- final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
- final IDreamOverlayClientCallback callback =
- Mockito.mock(IDreamOverlayClientCallback.class);
- overlay.getClient(callback);
- final ArgumentCaptor<IDreamOverlayClient> clientCaptor =
- ArgumentCaptor.forClass(IDreamOverlayClient.class);
- verify(callback).onDreamOverlayClient(clientCaptor.capture());
-
- return clientCaptor.getValue();
- }
-
- @Test
- public void testOnStartMetricsLogged() throws Exception {
- final IDreamOverlayClient client = getClient();
-
- // Inform the overlay service of dream starting.
- client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
- false /*shouldShowComplication*/);
- mMainExecutor.runAllReady();
-
- verify(mUiEventLogger).log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
- verify(mUiEventLogger).log(
- DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
- }
-
- @Test
- public void testOverlayContainerViewAddedToWindow() throws Exception {
- final IDreamOverlayClient client = getClient();
-
- // Inform the overlay service of dream starting.
- client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
- false /*shouldShowComplication*/);
- mMainExecutor.runAllReady();
-
- verify(mWindowManager).addView(any(), any());
- }
-
- // Validates that {@link DreamOverlayService} properly handles the case where the dream's
- // window is no longer valid by the time start is called.
- @Test
- public void testInvalidWindowAddStart() throws Exception {
- final IDreamOverlayClient client = getClient();
-
- doThrow(new WindowManager.BadTokenException()).when(mWindowManager).addView(any(), any());
- // Inform the overlay service of dream starting.
- client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
- false /*shouldShowComplication*/);
- mMainExecutor.runAllReady();
-
- verify(mWindowManager).addView(any(), any());
-
- verify(mStateController).setOverlayActive(false);
- verify(mStateController).setLowLightActive(false);
- verify(mStateController).setEntryAnimationsFinished(false);
-
- verify(mStateController, never()).setOverlayActive(true);
- verify(mUiEventLogger, never()).log(
- DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
-
- verify(mDreamOverlayCallbackController, never()).onStartDream();
- }
-
- @Test
- public void testDreamOverlayContainerViewControllerInitialized() throws Exception {
- final IDreamOverlayClient client = getClient();
-
- // Inform the overlay service of dream starting.
- client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
- false /*shouldShowComplication*/);
- mMainExecutor.runAllReady();
-
- verify(mDreamOverlayContainerViewController).init();
- }
-
- @Test
- public void testDreamOverlayContainerViewRemovedFromOldParentWhenInitialized()
- throws Exception {
- when(mDreamOverlayContainerView.getParent())
- .thenReturn(mDreamOverlayContainerViewParent)
- .thenReturn(null);
-
- final IDreamOverlayClient client = getClient();
-
- // Inform the overlay service of dream starting.
- client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
- false /*shouldShowComplication*/);
- mMainExecutor.runAllReady();
-
- verify(mDreamOverlayContainerViewParent).removeView(mDreamOverlayContainerView);
- }
-
- @Test
- public void testShouldShowComplicationsSetByStartDream() throws RemoteException {
- final IDreamOverlayClient client = getClient();
-
- // Inform the overlay service of dream starting.
- client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
- true /*shouldShowComplication*/);
- mMainExecutor.runAllReady();
-
- assertThat(mService.shouldShowComplications()).isTrue();
- }
-
- @Test
- public void testLowLightSetByStartDream() throws RemoteException {
- final IDreamOverlayClient client = getClient();
-
- // Inform the overlay service of dream starting.
- client.startDream(mWindowParams, mDreamOverlayCallback,
- LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
- mMainExecutor.runAllReady();
-
- assertThat(mService.getDreamComponent()).isEqualTo(LOW_LIGHT_COMPONENT);
- verify(mStateController).setLowLightActive(true);
- }
-
- @Test
- public void testHomeControlPanelSetsByStartDream() throws RemoteException {
- final IDreamOverlayClient client = getClient();
-
- // Inform the overlay service of dream starting.
- client.startDream(mWindowParams, mDreamOverlayCallback,
- HOME_CONTROL_PANEL_DREAM_COMPONENT.flattenToString(),
- false /*shouldShowComplication*/);
- mMainExecutor.runAllReady();
- assertThat(mService.getDreamComponent()).isEqualTo(HOME_CONTROL_PANEL_DREAM_COMPONENT);
- verify(mStateController).setHomeControlPanelActive(true);
- }
-
- @Test
- public void testOnEndDream() throws RemoteException {
- final IDreamOverlayClient client = getClient();
-
- // Inform the overlay service of dream starting.
- client.startDream(mWindowParams, mDreamOverlayCallback,
- LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
- mMainExecutor.runAllReady();
-
- // Verify view added.
- verify(mWindowManager).addView(mViewCaptor.capture(), any());
-
- // Service destroyed.
- mService.onEndDream();
- mMainExecutor.runAllReady();
-
- // Verify view removed.
- verify(mWindowManager).removeView(mViewCaptor.getValue());
-
- // Verify state correctly set.
- verify(mStateController).setOverlayActive(false);
- verify(mStateController).setLowLightActive(false);
- verify(mStateController).setEntryAnimationsFinished(false);
- }
-
- @Test
- public void testImmediateEndDream() throws Exception {
- final IDreamOverlayClient client = getClient();
-
- // Start the dream, but don't execute any Runnables put on the executor yet. We delay
- // executing Runnables as the timing isn't guaranteed and we want to verify that the overlay
- // starts and finishes in the proper order even if Runnables are delayed.
- client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
- false /*shouldShowComplication*/);
- // Immediately end the dream.
- client.endDream();
- // Run any scheduled Runnables.
- mMainExecutor.runAllReady();
-
- // The overlay starts then finishes.
- InOrder inOrder = inOrder(mWindowManager);
- inOrder.verify(mWindowManager).addView(mViewCaptor.capture(), any());
- inOrder.verify(mWindowManager).removeView(mViewCaptor.getValue());
- }
-
- @Test
- public void testEndDreamDuringStartDream() throws Exception {
- final IDreamOverlayClient client = getClient();
-
- // Schedule the endDream call in the middle of the startDream implementation, as any
- // ordering is possible.
- doAnswer(invocation -> {
- client.endDream();
- return null;
- }).when(mStateController).setOverlayActive(true);
-
- // Start the dream.
- client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
- false /*shouldShowComplication*/);
- mMainExecutor.runAllReady();
-
- // The overlay starts then finishes.
- InOrder inOrder = inOrder(mWindowManager);
- inOrder.verify(mWindowManager).addView(mViewCaptor.capture(), any());
- inOrder.verify(mWindowManager).removeView(mViewCaptor.getValue());
- }
-
- @Test
- public void testDestroy() throws RemoteException {
- final IDreamOverlayClient client = getClient();
-
- // Inform the overlay service of dream starting.
- client.startDream(mWindowParams, mDreamOverlayCallback,
- LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
- mMainExecutor.runAllReady();
-
- // Verify view added.
- verify(mWindowManager).addView(mViewCaptor.capture(), any());
-
- // Service destroyed.
- mService.onDestroy();
- mMainExecutor.runAllReady();
-
- // Verify view removed.
- verify(mWindowManager).removeView(mViewCaptor.getValue());
-
- // Verify state correctly set.
- verify(mKeyguardUpdateMonitor).removeCallback(any());
- verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
- verify(mStateController).setOverlayActive(false);
- verify(mStateController).setLowLightActive(false);
- verify(mStateController).setEntryAnimationsFinished(false);
- }
-
- @Test
- public void testDoNotRemoveViewOnDestroyIfOverlayNotStarted() {
- // Service destroyed without ever starting dream.
- mService.onDestroy();
- mMainExecutor.runAllReady();
-
- // Verify no view is removed.
- verify(mWindowManager, never()).removeView(any());
-
- // Verify state still correctly set.
- verify(mKeyguardUpdateMonitor).removeCallback(any());
- verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
- verify(mStateController).setOverlayActive(false);
- verify(mStateController).setLowLightActive(false);
- }
-
- @Test
- public void testDecorViewNotAddedToWindowAfterDestroy() throws Exception {
- final IDreamOverlayClient client = getClient();
-
- // Destroy the service.
- mService.onDestroy();
- mMainExecutor.runAllReady();
-
- // Inform the overlay service of dream starting.
- client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
- false /*shouldShowComplication*/);
- mMainExecutor.runAllReady();
-
- verify(mWindowManager, never()).addView(any(), any());
- }
-
- @Test
- public void testNeverRemoveDecorViewIfNotAdded() {
- // Service destroyed before dream started.
- mService.onDestroy();
- mMainExecutor.runAllReady();
-
- verify(mWindowManager, never()).removeView(any());
- }
-
- @Test
- public void testResetCurrentOverlayWhenConnectedToNewDream() throws RemoteException {
- final IDreamOverlayClient client = getClient();
-
- // Inform the overlay service of dream starting. Do not show dream complications.
- client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
- false /*shouldShowComplication*/);
- mMainExecutor.runAllReady();
-
- // Verify that a new window is added.
- verify(mWindowManager).addView(mViewCaptor.capture(), any());
- final View windowDecorView = mViewCaptor.getValue();
-
- // Assert that the overlay is not showing complications.
- assertThat(mService.shouldShowComplications()).isFalse();
-
- clearInvocations(mDreamOverlayComponent);
- clearInvocations(mWindowManager);
-
- // New dream starting with dream complications showing. Note that when a new dream is
- // binding to the dream overlay service, it receives the same instance of IBinder as the
- // first one.
- client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
- true /*shouldShowComplication*/);
- mMainExecutor.runAllReady();
-
- // Assert that the overlay is showing complications.
- assertThat(mService.shouldShowComplications()).isTrue();
-
- // Verify that the old overlay window has been removed, and a new one created.
- verify(mWindowManager).removeView(windowDecorView);
- verify(mWindowManager).addView(any(), any());
-
- // Verify that new instances of overlay container view controller and overlay touch monitor
- // are created.
- verify(mDreamOverlayComponent).getDreamOverlayContainerViewController();
- verify(mDreamOverlayComponent).getDreamOverlayTouchMonitor();
- }
-
- @Test
- public void testWakeUp() throws RemoteException {
- final IDreamOverlayClient client = getClient();
-
- // Inform the overlay service of dream starting.
- client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
- true /*shouldShowComplication*/);
- mMainExecutor.runAllReady();
-
- mService.onWakeUp();
- verify(mDreamOverlayContainerViewController).wakeUp();
- verify(mDreamOverlayCallbackController).onWakeUp();
- }
-
- @Test
- public void testWakeUpBeforeStartDoesNothing() {
- mService.onWakeUp();
- verify(mDreamOverlayContainerViewController, never()).wakeUp();
- }
-
- @Test
- public void testSystemFlagShowForAllUsersSetOnWindow() throws RemoteException {
- final IDreamOverlayClient client = getClient();
-
- // Inform the overlay service of dream starting. Do not show dream complications.
- client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
- false /*shouldShowComplication*/);
- mMainExecutor.runAllReady();
-
- final ArgumentCaptor<WindowManager.LayoutParams> paramsCaptor =
- ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
-
- // Verify that a new window is added.
- verify(mWindowManager).addView(any(), paramsCaptor.capture());
-
- assertThat((paramsCaptor.getValue().privateFlags & SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
- == SYSTEM_FLAG_SHOW_FOR_ALL_USERS).isTrue();
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
new file mode 100644
index 0000000..b18a8ec
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -0,0 +1,573 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams
+
+import android.content.ComponentName
+import android.content.Intent
+import android.os.RemoteException
+import android.service.dreams.IDreamOverlay
+import android.service.dreams.IDreamOverlayCallback
+import android.service.dreams.IDreamOverlayClient
+import android.service.dreams.IDreamOverlayClientCallback
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.WindowManagerImpl
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.ambient.touch.TouchMonitor
+import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
+import com.android.systemui.complication.ComplicationHostViewController
+import com.android.systemui.complication.ComplicationLayoutEngine
+import com.android.systemui.complication.dagger.ComplicationComponent
+import com.android.systemui.dreams.complication.HideComplicationTouchHandler
+import com.android.systemui.dreams.dagger.DreamOverlayComponent
+import com.android.systemui.touch.TouchInsetManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import org.mockito.invocation.InvocationOnMock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DreamOverlayServiceTest : SysuiTestCase() {
+ private val mFakeSystemClock = FakeSystemClock()
+ private val mMainExecutor = FakeExecutor(mFakeSystemClock)
+
+ @Mock lateinit var mLifecycleOwner: DreamOverlayLifecycleOwner
+
+ @Mock lateinit var mLifecycleRegistry: LifecycleRegistry
+
+ lateinit var mWindowParams: WindowManager.LayoutParams
+
+ @Mock lateinit var mDreamOverlayCallback: IDreamOverlayCallback
+
+ @Mock lateinit var mWindowManager: WindowManagerImpl
+
+ @Mock lateinit var mComplicationComponentFactory: ComplicationComponent.Factory
+
+ @Mock lateinit var mComplicationComponent: ComplicationComponent
+
+ @Mock lateinit var mComplicationHostViewController: ComplicationHostViewController
+
+ @Mock lateinit var mComplicationVisibilityController: ComplicationLayoutEngine
+
+ @Mock
+ lateinit var mDreamComplicationComponentFactory:
+ com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
+
+ @Mock
+ lateinit var mDreamComplicationComponent:
+ com.android.systemui.dreams.complication.dagger.ComplicationComponent
+
+ @Mock lateinit var mHideComplicationTouchHandler: HideComplicationTouchHandler
+
+ @Mock lateinit var mDreamOverlayComponentFactory: DreamOverlayComponent.Factory
+
+ @Mock lateinit var mDreamOverlayComponent: DreamOverlayComponent
+
+ @Mock lateinit var mAmbientTouchComponentFactory: AmbientTouchComponent.Factory
+
+ @Mock lateinit var mAmbientTouchComponent: AmbientTouchComponent
+
+ @Mock lateinit var mDreamOverlayContainerView: DreamOverlayContainerView
+
+ @Mock lateinit var mDreamOverlayContainerViewController: DreamOverlayContainerViewController
+
+ @Mock lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
+
+ @Mock lateinit var mTouchMonitor: TouchMonitor
+
+ @Mock lateinit var mStateController: DreamOverlayStateController
+
+ @Mock lateinit var mDreamOverlayContainerViewParent: ViewGroup
+
+ @Mock lateinit var mTouchInsetManager: TouchInsetManager
+
+ @Mock lateinit var mUiEventLogger: UiEventLogger
+
+ @Mock lateinit var mDreamOverlayCallbackController: DreamOverlayCallbackController
+
+ @Captor var mViewCaptor: ArgumentCaptor<View>? = null
+ var mService: DreamOverlayService? = null
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ whenever(mDreamOverlayComponent.getDreamOverlayContainerViewController())
+ .thenReturn(mDreamOverlayContainerViewController)
+ whenever(mComplicationComponent.getComplicationHostViewController())
+ .thenReturn(mComplicationHostViewController)
+ whenever(mLifecycleOwner.registry).thenReturn(mLifecycleRegistry)
+ whenever(mComplicationComponentFactory.create(any(), any(), any(), any()))
+ .thenReturn(mComplicationComponent)
+ whenever(mComplicationComponent.getVisibilityController())
+ .thenReturn(mComplicationVisibilityController)
+ whenever(mDreamComplicationComponent.getHideComplicationTouchHandler())
+ .thenReturn(mHideComplicationTouchHandler)
+ whenever(mDreamComplicationComponentFactory.create(any(), any()))
+ .thenReturn(mDreamComplicationComponent)
+ whenever(mDreamOverlayComponentFactory.create(any(), any(), any()))
+ .thenReturn(mDreamOverlayComponent)
+ whenever(mAmbientTouchComponentFactory.create(any(), any()))
+ .thenReturn(mAmbientTouchComponent)
+ whenever(mAmbientTouchComponent.getTouchMonitor()).thenReturn(mTouchMonitor)
+ whenever(mDreamOverlayContainerViewController.containerView)
+ .thenReturn(mDreamOverlayContainerView)
+ mWindowParams = WindowManager.LayoutParams()
+ mService =
+ DreamOverlayService(
+ mContext,
+ mLifecycleOwner,
+ mMainExecutor,
+ mWindowManager,
+ mComplicationComponentFactory,
+ mDreamComplicationComponentFactory,
+ mDreamOverlayComponentFactory,
+ mAmbientTouchComponentFactory,
+ mStateController,
+ mKeyguardUpdateMonitor,
+ mUiEventLogger,
+ mTouchInsetManager,
+ LOW_LIGHT_COMPONENT,
+ HOME_CONTROL_PANEL_DREAM_COMPONENT,
+ mDreamOverlayCallbackController,
+ WINDOW_NAME
+ )
+ }
+
+ @get:Throws(RemoteException::class)
+ val client: IDreamOverlayClient
+ get() {
+ val proxy = mService!!.onBind(Intent())
+ val overlay = IDreamOverlay.Stub.asInterface(proxy)
+ val callback = Mockito.mock(IDreamOverlayClientCallback::class.java)
+ overlay.getClient(callback)
+ val clientCaptor = ArgumentCaptor.forClass(IDreamOverlayClient::class.java)
+ Mockito.verify(callback).onDreamOverlayClient(clientCaptor.capture())
+ return clientCaptor.value
+ }
+
+ @Test
+ fun testOnStartMetricsLogged() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+ Mockito.verify(mUiEventLogger)
+ .log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_ENTER_START)
+ Mockito.verify(mUiEventLogger)
+ .log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START)
+ }
+
+ @Test
+ fun testOverlayContainerViewAddedToWindow() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+ Mockito.verify(mWindowManager).addView(any(), any())
+ }
+
+ // Validates that {@link DreamOverlayService} properly handles the case where the dream's
+ // window is no longer valid by the time start is called.
+ @Test
+ fun testInvalidWindowAddStart() {
+ val client = client
+ Mockito.doThrow(WindowManager.BadTokenException())
+ .`when`(mWindowManager)
+ .addView(any(), any())
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+ Mockito.verify(mWindowManager).addView(any(), any())
+ Mockito.verify(mStateController).setOverlayActive(false)
+ Mockito.verify(mStateController).setLowLightActive(false)
+ Mockito.verify(mStateController).setEntryAnimationsFinished(false)
+ Mockito.verify(mStateController, Mockito.never()).setOverlayActive(true)
+ Mockito.verify(mUiEventLogger, Mockito.never())
+ .log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START)
+ Mockito.verify(mDreamOverlayCallbackController, Mockito.never()).onStartDream()
+ }
+
+ @Test
+ fun testDreamOverlayContainerViewControllerInitialized() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+ Mockito.verify(mDreamOverlayContainerViewController).init()
+ }
+
+ @Test
+ fun testDreamOverlayContainerViewRemovedFromOldParentWhenInitialized() {
+ whenever(mDreamOverlayContainerView.parent)
+ .thenReturn(mDreamOverlayContainerViewParent)
+ .thenReturn(null)
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+ Mockito.verify(mDreamOverlayContainerViewParent).removeView(mDreamOverlayContainerView)
+ }
+
+ @Test
+ fun testShouldShowComplicationsSetByStartDream() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ true /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+ Truth.assertThat(mService!!.shouldShowComplications()).isTrue()
+ }
+
+ @Test
+ fun testLowLightSetByStartDream() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ LOW_LIGHT_COMPONENT.flattenToString(),
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+ Truth.assertThat(mService!!.dreamComponent).isEqualTo(LOW_LIGHT_COMPONENT)
+ Mockito.verify(mStateController).setLowLightActive(true)
+ }
+
+ @Test
+ fun testHomeControlPanelSetsByStartDream() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ HOME_CONTROL_PANEL_DREAM_COMPONENT.flattenToString(),
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+ Truth.assertThat(mService!!.dreamComponent).isEqualTo(HOME_CONTROL_PANEL_DREAM_COMPONENT)
+ Mockito.verify(mStateController).setHomeControlPanelActive(true)
+ }
+
+ @Test
+ fun testOnEndDream() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ LOW_LIGHT_COMPONENT.flattenToString(),
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+
+ // Verify view added.
+ Mockito.verify(mWindowManager).addView(mViewCaptor!!.capture(), any())
+
+ // Service destroyed.
+ mService!!.onEndDream()
+ mMainExecutor.runAllReady()
+
+ // Verify view removed.
+ Mockito.verify(mWindowManager).removeView(mViewCaptor!!.value)
+
+ // Verify state correctly set.
+ Mockito.verify(mStateController).setOverlayActive(false)
+ Mockito.verify(mStateController).setLowLightActive(false)
+ Mockito.verify(mStateController).setEntryAnimationsFinished(false)
+ }
+
+ @Test
+ fun testImmediateEndDream() {
+ val client = client
+
+ // Start the dream, but don't execute any Runnables put on the executor yet. We delay
+ // executing Runnables as the timing isn't guaranteed and we want to verify that the overlay
+ // starts and finishes in the proper order even if Runnables are delayed.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*shouldShowComplication*/
+ )
+ // Immediately end the dream.
+ client.endDream()
+ // Run any scheduled Runnables.
+ mMainExecutor.runAllReady()
+
+ // The overlay starts then finishes.
+ val inOrder = Mockito.inOrder(mWindowManager)
+ inOrder.verify(mWindowManager).addView(mViewCaptor!!.capture(), any())
+ inOrder.verify(mWindowManager).removeView(mViewCaptor!!.value)
+ }
+
+ @Test
+ fun testEndDreamDuringStartDream() {
+ val client = client
+
+ // Schedule the endDream call in the middle of the startDream implementation, as any
+ // ordering is possible.
+ Mockito.doAnswer { invocation: InvocationOnMock? ->
+ client.endDream()
+ null
+ }
+ .`when`(mStateController)
+ .setOverlayActive(true)
+
+ // Start the dream.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+
+ // The overlay starts then finishes.
+ val inOrder = Mockito.inOrder(mWindowManager)
+ inOrder.verify(mWindowManager).addView(mViewCaptor!!.capture(), any())
+ inOrder.verify(mWindowManager).removeView(mViewCaptor!!.value)
+ }
+
+ @Test
+ fun testDestroy() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ LOW_LIGHT_COMPONENT.flattenToString(),
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+
+ // Verify view added.
+ Mockito.verify(mWindowManager).addView(mViewCaptor!!.capture(), any())
+
+ // Service destroyed.
+ mService!!.onDestroy()
+ mMainExecutor.runAllReady()
+
+ // Verify view removed.
+ Mockito.verify(mWindowManager).removeView(mViewCaptor!!.value)
+
+ // Verify state correctly set.
+ Mockito.verify(mKeyguardUpdateMonitor).removeCallback(any())
+ Mockito.verify(mLifecycleRegistry).currentState = Lifecycle.State.DESTROYED
+ Mockito.verify(mStateController).setOverlayActive(false)
+ Mockito.verify(mStateController).setLowLightActive(false)
+ Mockito.verify(mStateController).setEntryAnimationsFinished(false)
+ }
+
+ @Test
+ fun testDoNotRemoveViewOnDestroyIfOverlayNotStarted() {
+ // Service destroyed without ever starting dream.
+ mService!!.onDestroy()
+ mMainExecutor.runAllReady()
+
+ // Verify no view is removed.
+ Mockito.verify(mWindowManager, Mockito.never()).removeView(any())
+
+ // Verify state still correctly set.
+ Mockito.verify(mKeyguardUpdateMonitor).removeCallback(any())
+ Mockito.verify(mLifecycleRegistry).currentState = Lifecycle.State.DESTROYED
+ Mockito.verify(mStateController).setOverlayActive(false)
+ Mockito.verify(mStateController).setLowLightActive(false)
+ }
+
+ @Test
+ fun testDecorViewNotAddedToWindowAfterDestroy() {
+ val client = client
+
+ // Destroy the service.
+ mService!!.onDestroy()
+ mMainExecutor.runAllReady()
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+ Mockito.verify(mWindowManager, Mockito.never()).addView(any(), any())
+ }
+
+ @Test
+ fun testNeverRemoveDecorViewIfNotAdded() {
+ // Service destroyed before dream started.
+ mService!!.onDestroy()
+ mMainExecutor.runAllReady()
+ Mockito.verify(mWindowManager, Mockito.never()).removeView(any())
+ }
+
+ @Test
+ fun testResetCurrentOverlayWhenConnectedToNewDream() {
+ val client = client
+
+ // Inform the overlay service of dream starting. Do not show dream complications.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+
+ // Verify that a new window is added.
+ Mockito.verify(mWindowManager).addView(mViewCaptor!!.capture(), any())
+ val windowDecorView = mViewCaptor!!.value
+
+ // Assert that the overlay is not showing complications.
+ Truth.assertThat(mService!!.shouldShowComplications()).isFalse()
+ Mockito.clearInvocations(mDreamOverlayComponent)
+ Mockito.clearInvocations(mAmbientTouchComponent)
+ Mockito.clearInvocations(mWindowManager)
+
+ // New dream starting with dream complications showing. Note that when a new dream is
+ // binding to the dream overlay service, it receives the same instance of IBinder as the
+ // first one.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ true /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+
+ // Assert that the overlay is showing complications.
+ Truth.assertThat(mService!!.shouldShowComplications()).isTrue()
+
+ // Verify that the old overlay window has been removed, and a new one created.
+ Mockito.verify(mWindowManager).removeView(windowDecorView)
+ Mockito.verify(mWindowManager).addView(any(), any())
+
+ // Verify that new instances of overlay container view controller and overlay touch monitor
+ // are created.
+ Mockito.verify(mDreamOverlayComponent).getDreamOverlayContainerViewController()
+ Mockito.verify(mAmbientTouchComponent).getTouchMonitor()
+ }
+
+ @Test
+ fun testWakeUp() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ true /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+ mService!!.onWakeUp()
+ Mockito.verify(mDreamOverlayContainerViewController).wakeUp()
+ Mockito.verify(mDreamOverlayCallbackController).onWakeUp()
+ }
+
+ @Test
+ fun testWakeUpBeforeStartDoesNothing() {
+ mService!!.onWakeUp()
+ Mockito.verify(mDreamOverlayContainerViewController, Mockito.never()).wakeUp()
+ }
+
+ @Test
+ fun testSystemFlagShowForAllUsersSetOnWindow() {
+ val client = client
+
+ // Inform the overlay service of dream starting. Do not show dream complications.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+ val paramsCaptor = ArgumentCaptor.forClass(WindowManager.LayoutParams::class.java)
+
+ // Verify that a new window is added.
+ Mockito.verify(mWindowManager).addView(any(), paramsCaptor.capture())
+ Truth.assertThat(
+ paramsCaptor.value.privateFlags and
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS ==
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
+ )
+ .isTrue()
+ }
+
+ companion object {
+ private val LOW_LIGHT_COMPONENT = ComponentName("package", "lowlight")
+ private val HOME_CONTROL_PANEL_DREAM_COMPONENT =
+ ComponentName("package", "homeControlPanel")
+ private const val DREAM_COMPONENT = "package/dream"
+ private const val WINDOW_NAME = "test"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index 39db2be..f561c53 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -36,11 +36,8 @@
import android.content.Context;
import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
import android.provider.Settings;
+import android.testing.TestableLooper;
import android.view.View;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -51,6 +48,7 @@
import com.android.systemui.log.core.FakeLogBuffer;
import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -72,6 +70,7 @@
import java.util.concurrent.Executor;
@SmallTest
[email protected](setAsMainLooper = true)
@RunWith(AndroidJUnit4.class)
public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
private static final String NOTIFICATION_INDICATOR_FORMATTER_STRING =
@@ -80,12 +79,6 @@
@Mock
MockDreamOverlayStatusBarView mView;
@Mock
- ConnectivityManager mConnectivityManager;
- @Mock
- NetworkCapabilities mNetworkCapabilities;
- @Mock
- Network mNetwork;
- @Mock
TouchInsetManager.TouchInsetSession mTouchSession;
@Mock
Resources mResources;
@@ -121,6 +114,8 @@
private final Executor mMainExecutor = Runnable::run;
+ private final FakeWifiRepository mWifiRepository = new FakeWifiRepository();
+
DreamOverlayStatusBarViewController mController;
@Before
@@ -137,7 +132,6 @@
mView,
mResources,
mMainExecutor,
- mConnectivityManager,
mTouchSession,
mAlarmManager,
mNextAlarmController,
@@ -149,6 +143,7 @@
mDreamOverlayStatusBarItemsProvider,
mDreamOverlayStateController,
mUserTracker,
+ mWifiRepository,
mLogBuffer);
}
@@ -164,42 +159,24 @@
}
@Test
- public void testOnViewAttachedRegistersNetworkCallback() {
+ public void testWifiIconShownWhenWifiUnavailable() {
mController.onViewAttached();
- verify(mConnectivityManager)
- .registerNetworkCallback(any(NetworkRequest.class), any(
- ConnectivityManager.NetworkCallback.class));
- }
+ mController.updateWifiUnavailableStatusIcon(false);
- @Test
- public void testOnViewAttachedShowsWifiIconWhenWifiUnavailable() {
- when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
- .thenReturn(false);
- when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
- mController.onViewAttached();
verify(mView).showIcon(
DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, true, null);
}
@Test
- public void testOnViewAttachedHidesWifiIconWhenWifiAvailable() {
- when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
- .thenReturn(true);
- when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+ public void testWifiIconHiddenWhenWifiAvailable() {
mController.onViewAttached();
+ mController.updateWifiUnavailableStatusIcon(true);
+
verify(mView).showIcon(
DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, false, null);
}
@Test
- public void testOnViewAttachedShowsWifiIconWhenNetworkCapabilitiesUnavailable() {
- when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(null);
- mController.onViewAttached();
- verify(mView).showIcon(
- DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, true, null);
- }
-
- @Test
public void testOnViewAttachedShowsAlarmIconWhenAlarmExists() {
final AlarmManager.AlarmClockInfo alarmClockInfo =
new AlarmManager.AlarmClockInfo(1L, null);
@@ -282,7 +259,6 @@
mView,
mResources,
mMainExecutor,
- mConnectivityManager,
mTouchSession,
mAlarmManager,
mNextAlarmController,
@@ -294,6 +270,7 @@
mDreamOverlayStatusBarItemsProvider,
mDreamOverlayStateController,
mUserTracker,
+ mWifiRepository,
mLogBuffer);
controller.onViewAttached();
verify(mView, never()).showIcon(
@@ -319,13 +296,6 @@
}
@Test
- public void testOnViewDetachedUnregistersNetworkCallback() {
- mController.onViewDetached();
- verify(mConnectivityManager)
- .unregisterNetworkCallback(any(ConnectivityManager.NetworkCallback.class));
- }
-
- @Test
public void testOnViewDetachedRemovesCallbacks() {
mController.onViewDetached();
verify(mNextAlarmController).removeCallback(any());
@@ -343,61 +313,6 @@
}
@Test
- public void testWifiIconHiddenWhenWifiBecomesAvailable() {
- // Make sure wifi starts out unavailable when onViewAttached is called, and then returns
- // true on the second query.
- when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
- .thenReturn(false).thenReturn(true);
- when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
- mController.onViewAttached();
-
- final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture =
- ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
- verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture());
- callbackCapture.getValue().onAvailable(mNetwork);
-
- verify(mView).showIcon(
- DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, false, null);
- }
-
- @Test
- public void testWifiIconShownWhenWifiBecomesUnavailable() {
- // Make sure wifi starts out available when onViewAttached is called, then returns false
- // on the second query.
- when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
- .thenReturn(true).thenReturn(false);
- when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
- mController.onViewAttached();
-
- final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture =
- ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
- verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture());
- callbackCapture.getValue().onLost(mNetwork);
-
- verify(mView).showIcon(
- DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, true, null);
- }
-
- @Test
- public void testWifiIconHiddenWhenCapabilitiesChange() {
- // Make sure wifi starts out unavailable when onViewAttached is called.
- when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
- .thenReturn(false);
- when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
- mController.onViewAttached();
-
- final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture =
- ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
- verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture());
- when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
- .thenReturn(true);
- callbackCapture.getValue().onCapabilitiesChanged(mNetwork, mNetworkCapabilities);
-
- verify(mView).showIcon(
- DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, false, null);
- }
-
- @Test
public void testNotificationsIconShownWhenNotificationAdded() {
mController.onViewAttached();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
index a434158..8481586 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
@@ -35,9 +35,9 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.TouchHandler;
import com.android.systemui.complication.Complication;
import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.touch.TouchInsetManager;
@@ -74,7 +74,7 @@
MotionEvent mMotionEvent;
@Mock
- DreamTouchHandler.TouchSession mSession;
+ TouchHandler.TouchSession mSession;
@Mock
DreamOverlayStateController mStateController;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
index 8dcf903..29fbee0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -31,6 +31,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.TouchHandler;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -54,7 +55,7 @@
@Mock
CentralSurfaces mCentralSurfaces;
@Mock
- DreamTouchHandler.TouchSession mTouchSession;
+ TouchHandler.TouchSession mTouchSession;
CommunalTouchHandler mTouchHandler;
@Mock
Lifecycle mLifecycle;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index 3889703..e332656 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -18,9 +18,6 @@
import android.os.VibrationEffect
import android.testing.TestableLooper.RunWithLooper
-import android.view.MotionEvent
-import android.view.View
-import androidx.test.core.view.MotionEventBuilder
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -29,18 +26,15 @@
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
-import com.android.systemui.kosmos.backgroundCoroutineContext
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
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.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
@@ -50,7 +44,6 @@
class QSLongPressEffectTest : SysuiTestCase() {
@Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule()
- @Mock private lateinit var testView: View
@get:Rule val animatorTestRule = AnimatorTestRule(this)
private val kosmos = testKosmos()
private val vibratorHelper = kosmos.vibratorHelper
@@ -73,7 +66,6 @@
QSLongPressEffect(
vibratorHelper,
kosmos.keyguardInteractor,
- CoroutineScope(kosmos.backgroundCoroutineContext),
)
longPressEffect.initializeEffect(effectDuration)
}
@@ -133,8 +125,7 @@
@Test
fun onActionDown_whileIdle_startsWait() = testWithScope {
// GIVEN an action down event occurs
- val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
- longPressEffect.onTouch(testView, downEvent)
+ longPressEffect.handleActionDown()
// THEN the effect moves to the TIMEOUT_WAIT state
val state by collectLastValue(longPressEffect.state)
@@ -144,8 +135,7 @@
@Test
fun onActionCancel_whileWaiting_goesIdle() = testWhileWaiting {
// GIVEN an action cancel occurs
- val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
- longPressEffect.onTouch(testView, cancelEvent)
+ longPressEffect.handleActionCancel()
// THEN the effect goes back to idle and does not start
val state by collectLastValue(longPressEffect.state)
@@ -159,8 +149,7 @@
val action by collectLastValue(longPressEffect.actionType)
// GIVEN an action up occurs
- val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
- longPressEffect.onTouch(testView, upEvent)
+ longPressEffect.handleActionUp()
// THEN the action to invoke is the click action and the effect does not start
assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CLICK)
@@ -182,8 +171,7 @@
animatorTestRule.advanceTimeBy(effectDuration / 2L)
// WHEN an action up occurs
- val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
- longPressEffect.onTouch(testView, upEvent)
+ longPressEffect.handleActionUp()
// THEN the effect gets reversed at 50% progress
assertEffectReverses(0.5f)
@@ -195,8 +183,7 @@
animatorTestRule.advanceTimeBy(effectDuration / 2L)
// WHEN an action cancel occurs
- val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
- longPressEffect.onTouch(testView, cancelEvent)
+ longPressEffect.handleActionCancel()
// THEN the effect gets reversed at 50% progress
assertEffectReverses(0.5f)
@@ -230,12 +217,10 @@
animatorTestRule.advanceTimeBy(effectDuration / 2L)
// GIVEN an action cancel occurs and the effect gets reversed
- val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
- longPressEffect.onTouch(testView, cancelEvent)
+ longPressEffect.handleActionCancel()
// GIVEN an action down occurs
- val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
- longPressEffect.onTouch(testView, downEvent)
+ longPressEffect.handleActionDown()
// THEN the effect resets
assertEffectResets()
@@ -247,8 +232,7 @@
animatorTestRule.advanceTimeBy(effectDuration / 2L)
// GIVEN an action cancel occurs and the effect gets reversed
- val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
- longPressEffect.onTouch(testView, cancelEvent)
+ longPressEffect.handleActionCancel()
// GIVEN that the animation completes after a sufficient amount of time
animatorTestRule.advanceTimeBy(effectDuration.toLong())
@@ -258,9 +242,6 @@
assertThat(state).isEqualTo(QSLongPressEffect.State.IDLE)
}
- private fun buildMotionEvent(action: Int): MotionEvent =
- MotionEventBuilder.newBuilder().setAction(action).build()
-
private fun testWithScope(test: suspend TestScope.() -> Unit) =
with(kosmos) { testScope.runTest { test() } }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 1dd5d07..12f8918 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -37,8 +38,6 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -76,7 +75,6 @@
repository = repository,
commandQueue = commandQueue,
powerInteractor = PowerInteractorFactory.create().powerInteractor,
- sceneContainerFlags = kosmos.sceneContainerFlags,
bouncerRepository = bouncerRepository,
configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
shadeRepository = shadeRepository,
@@ -249,9 +247,9 @@
}
@Test
+ @EnableSceneContainer
fun animationDozingTransitions() =
testScope.runTest {
- kosmos.fakeSceneContainerFlags.enabled = true
val isAnimate by collectLastValue(underTest.animateDozingTransitions)
underTest.setAnimateDozingTransitions(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModelTest.kt
new file mode 100644
index 0000000..d0f434a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModelTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+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.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AccessibilityActionsViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ private lateinit var underTest: AccessibilityActionsViewModel
+
+ @Before
+ fun setUp() {
+ underTest = kosmos.accessibilityActionsViewModelKosmos
+ }
+
+ @Test
+ fun isOnKeyguard_isFalse_whenTransitioningAwayFromLockscreen() =
+ testScope.runTest {
+ val isOnKeyguard by collectLastValue(underTest.isOnKeyguard)
+
+ // Shade not opened.
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ // Transitioning away from lock screen.
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.RUNNING,
+ value = 0.5f,
+ )
+ assertThat(isOnKeyguard).isEqualTo(false)
+
+ // Transitioned to bouncer.
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ transitionState = TransitionState.FINISHED,
+ value = 1f,
+ )
+ assertThat(isOnKeyguard).isEqualTo(false)
+ }
+
+ @Test
+ fun isOnKeyguard_isFalse_whenTransitioningToLockscreenIsRunning() =
+ testScope.runTest {
+ val isOnKeyguard by collectLastValue(underTest.isOnKeyguard)
+
+ // Shade is not opened.
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ // Starts transitioning to lock screen.
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ assertThat(isOnKeyguard).isEqualTo(false)
+
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.RUNNING,
+ value = 0.5f,
+ )
+ assertThat(isOnKeyguard).isEqualTo(false)
+
+ // Transition has finished.
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.FINISHED,
+ value = 1f,
+ )
+ assertThat(isOnKeyguard).isEqualTo(true)
+ }
+
+ @Test
+ fun isOnKeyguard_isTrue_whenKeyguardStateIsLockscreen_andShadeIsNotOpened() =
+ testScope.runTest {
+ val isOnKeyguard by collectLastValue(underTest.isOnKeyguard)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ )
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+
+ assertThat(isOnKeyguard).isEqualTo(true)
+ }
+
+ @Test
+ fun isOnKeyguard_isFalse_whenKeyguardStateIsLockscreen_andShadeOpened() =
+ testScope.runTest {
+ val isOnKeyguard by collectLastValue(underTest.isOnKeyguard)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ )
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+
+ assertThat(isOnKeyguard).isEqualTo(false)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index e9a8257..3497183 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -21,17 +21,21 @@
import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.authController
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
+import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import java.util.Locale
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -137,4 +141,47 @@
.isFalse()
}
}
+
+ @Test
+ fun unfoldTranslations() =
+ with(kosmos) {
+ testScope.runTest {
+ val maxTranslation = prepareConfiguration()
+ val translations by collectLastValue(underTest.unfoldTranslations)
+
+ val unfoldProvider = fakeUnfoldTransitionProgressProvider
+ unfoldProvider.onTransitionStarted()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+
+ repeat(10) { repetition ->
+ val transitionProgress = 0.1f * (repetition + 1)
+ unfoldProvider.onTransitionProgress(transitionProgress)
+ assertThat(translations?.start)
+ .isEqualTo((1 - transitionProgress) * maxTranslation)
+ assertThat(translations?.end)
+ .isEqualTo(-(1 - transitionProgress) * maxTranslation)
+ }
+
+ unfoldProvider.onTransitionFinishing()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+
+ unfoldProvider.onTransitionFinished()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+ }
+ }
+
+ private fun prepareConfiguration(): Int {
+ val configuration = context.resources.configuration
+ configuration.setLayoutDirection(Locale.US)
+ kosmos.fakeConfigurationRepository.onConfigurationChange(configuration)
+ val maxTranslation = 10
+ kosmos.fakeConfigurationRepository.setDimensionPixelSize(
+ R.dimen.notification_side_paddings,
+ maxTranslation,
+ )
+ return maxTranslation
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
index 33eb90a..f685058 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.MediaTestHelper
+import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
@@ -172,12 +173,147 @@
val recommendationsLoadingModel =
SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE)
- underTest.setRecommedationsLoadingState(recommendationsLoadingModel)
+ underTest.setRecommendationsLoadingState(recommendationsLoadingModel)
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
}
+ @Test
+ fun addMediaControlPlayingThenRemote() =
+ testScope.runTest {
+ val sortedMedia by collectLastValue(underTest.sortedMedia)
+ val playingInstanceId = InstanceId.fakeInstanceId(123)
+ val remoteInstanceId = InstanceId.fakeInstanceId(321)
+ val playingData = createMediaData("app1", true, LOCAL, false, playingInstanceId)
+ val remoteData = createMediaData("app2", true, REMOTE, false, remoteInstanceId)
+
+ underTest.addSelectedUserMediaEntry(playingData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId))
+ underTest.addSelectedUserMediaEntry(remoteData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(remoteInstanceId))
+
+ assertThat(sortedMedia?.size).isEqualTo(2)
+ assertThat(sortedMedia?.values)
+ .containsExactly(
+ MediaCommonModel.MediaControl(playingInstanceId),
+ MediaCommonModel.MediaControl(remoteInstanceId)
+ )
+ }
+
+ @Test
+ fun switchMediaControlsPlaying() =
+ testScope.runTest {
+ val sortedMedia by collectLastValue(underTest.sortedMedia)
+ val playingInstanceId1 = InstanceId.fakeInstanceId(123)
+ val playingInstanceId2 = InstanceId.fakeInstanceId(321)
+ var playingData1 = createMediaData("app1", true, LOCAL, false, playingInstanceId1)
+ var playingData2 = createMediaData("app2", false, LOCAL, false, playingInstanceId2)
+
+ underTest.addSelectedUserMediaEntry(playingData1)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId1))
+ underTest.addSelectedUserMediaEntry(playingData2)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2))
+
+ assertThat(sortedMedia?.size).isEqualTo(2)
+ assertThat(sortedMedia?.values)
+ .containsExactly(
+ MediaCommonModel.MediaControl(playingInstanceId1),
+ MediaCommonModel.MediaControl(playingInstanceId2)
+ )
+ .inOrder()
+
+ playingData1 = createMediaData("app1", false, LOCAL, false, playingInstanceId1)
+ playingData2 = createMediaData("app2", true, LOCAL, false, playingInstanceId2)
+
+ underTest.addSelectedUserMediaEntry(playingData1)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId1))
+ underTest.addSelectedUserMediaEntry(playingData2)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2))
+
+ assertThat(sortedMedia?.size).isEqualTo(2)
+ assertThat(sortedMedia?.values)
+ .containsExactly(
+ MediaCommonModel.MediaControl(playingInstanceId2),
+ MediaCommonModel.MediaControl(playingInstanceId1)
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun fullOrderTest() =
+ testScope.runTest {
+ val sortedMedia by collectLastValue(underTest.sortedMedia)
+ val instanceId1 = InstanceId.fakeInstanceId(123)
+ val instanceId2 = InstanceId.fakeInstanceId(456)
+ val instanceId3 = InstanceId.fakeInstanceId(321)
+ val instanceId4 = InstanceId.fakeInstanceId(654)
+ val instanceId5 = InstanceId.fakeInstanceId(124)
+ val playingAndLocalData = createMediaData("app1", true, LOCAL, false, instanceId1)
+ val playingAndRemoteData = createMediaData("app2", true, REMOTE, false, instanceId2)
+ val stoppedAndLocalData = createMediaData("app3", false, LOCAL, false, instanceId3)
+ val stoppedAndRemoteData = createMediaData("app4", false, REMOTE, false, instanceId4)
+ val canResumeData = createMediaData("app5", false, LOCAL, true, instanceId5)
+
+ val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
+ val mediaRecommendations =
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = true,
+ recommendations = MediaTestHelper.getValidRecommendationList(icon),
+ )
+
+ underTest.addSelectedUserMediaEntry(stoppedAndLocalData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId3))
+
+ underTest.addSelectedUserMediaEntry(stoppedAndRemoteData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId4))
+
+ underTest.addSelectedUserMediaEntry(canResumeData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId5))
+
+ underTest.addSelectedUserMediaEntry(playingAndLocalData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId1))
+
+ underTest.addSelectedUserMediaEntry(playingAndRemoteData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId2))
+
+ underTest.setRecommendation(mediaRecommendations)
+ underTest.setRecommendationsLoadingState(
+ SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
+ )
+
+ assertThat(sortedMedia?.size).isEqualTo(6)
+ assertThat(sortedMedia?.values)
+ .containsExactly(
+ MediaCommonModel.MediaControl(instanceId1),
+ MediaCommonModel.MediaControl(instanceId2),
+ MediaCommonModel.MediaRecommendations(KEY_MEDIA_SMARTSPACE),
+ MediaCommonModel.MediaControl(instanceId4),
+ MediaCommonModel.MediaControl(instanceId3),
+ MediaCommonModel.MediaControl(instanceId5),
+ )
+ .inOrder()
+ }
+
+ private fun createMediaData(
+ app: String,
+ playing: Boolean,
+ playbackLocation: Int,
+ isResume: Boolean,
+ instanceId: InstanceId,
+ ): MediaData {
+ return MediaData(
+ playbackLocation = playbackLocation,
+ resumption = isResume,
+ notificationKey = "key: $app",
+ isPlaying = playing,
+ instanceId = instanceId
+ )
+ }
+
companion object {
+ private const val LOCAL = MediaData.PLAYBACK_LOCAL
+ private const val REMOTE = MediaData.PLAYBACK_CAST_LOCAL
private const val KEY = "KEY"
private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
index a0a1eb3..c15776e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
+import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
@@ -94,22 +95,29 @@
collectLastValue(underTest.hasActiveMediaOrRecommendation)
val hasActiveMedia by collectLastValue(underTest.hasActiveMedia)
val hasAnyMedia by collectLastValue(underTest.hasAnyMedia)
+ val sortedMedia by collectLastValue(underTest.sortedMedia)
val userMedia = MediaData(active = false)
val instanceId = userMedia.instanceId
mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
assertThat(hasActiveMediaOrRecommendation).isFalse()
assertThat(hasActiveMedia).isFalse()
assertThat(hasAnyMedia).isTrue()
+ assertThat(sortedMedia).containsExactly(MediaCommonModel.MediaControl(instanceId))
assertThat(mediaFilterRepository.removeSelectedUserMediaEntry(instanceId, userMedia))
.isTrue()
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Removed(instanceId)
+ )
assertThat(hasActiveMediaOrRecommendation).isFalse()
assertThat(hasActiveMedia).isFalse()
assertThat(hasAnyMedia).isFalse()
+ assertThat(sortedMedia).isEmpty()
}
@Test
@@ -119,6 +127,7 @@
collectLastValue(underTest.hasActiveMediaOrRecommendation)
val hasAnyMediaOrRecommendation by
collectLastValue(underTest.hasAnyMediaOrRecommendation)
+ val sortedMedia by collectLastValue(underTest.sortedMedia)
kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
@@ -131,14 +140,28 @@
val userMedia = MediaData(active = false)
mediaFilterRepository.setRecommendation(userMediaRecommendation)
+ mediaFilterRepository.setRecommendationsLoadingState(
+ SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
+ )
assertThat(hasActiveMediaOrRecommendation).isTrue()
assertThat(hasAnyMediaOrRecommendation).isTrue()
+ assertThat(sortedMedia)
+ .containsExactly(MediaCommonModel.MediaRecommendations(KEY_MEDIA_SMARTSPACE))
mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Loaded(userMedia.instanceId)
+ )
assertThat(hasActiveMediaOrRecommendation).isTrue()
assertThat(hasAnyMediaOrRecommendation).isTrue()
+ assertThat(sortedMedia)
+ .containsExactly(
+ MediaCommonModel.MediaRecommendations(KEY_MEDIA_SMARTSPACE),
+ MediaCommonModel.MediaControl(userMedia.instanceId)
+ )
+ .inOrder()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
index 1cba185..d9224d7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
@@ -187,7 +187,12 @@
underTest.startMediaOutputDialog(expandable, PACKAGE_NAME)
verify(kosmos.mediaOutputDialogManager)
- .createAndShowWithController(eq(PACKAGE_NAME), eq(true), eq(dialogTransitionController))
+ .createAndShowWithController(
+ eq(PACKAGE_NAME),
+ eq(true),
+ eq(dialogTransitionController),
+ eq(null)
+ )
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt
new file mode 100644
index 0000000..2e5fde8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor
+
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.reduceBrightColorsController
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class ReduceBrightColorsTileDataInteractorTest : SysuiTestCase() {
+
+ private val isAvailable = true
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val reduceBrightColorsController = kosmos.reduceBrightColorsController
+ private val underTest: ReduceBrightColorsTileDataInteractor =
+ ReduceBrightColorsTileDataInteractor(
+ testScope.testScheduler,
+ isAvailable,
+ reduceBrightColorsController
+ )
+
+ @Test
+ fun alwaysAvailable() =
+ testScope.runTest {
+ val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
+
+ assertThat(availability).hasSize(1)
+ assertThat(availability.last()).isEqualTo(isAvailable)
+ }
+
+ @Test
+ fun dataMatchesTheRepository() =
+ testScope.runTest {
+ val dataList: List<ReduceBrightColorsTileModel> by
+ collectValues(
+ underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ runCurrent()
+
+ reduceBrightColorsController.isReduceBrightColorsActivated = true
+ runCurrent()
+
+ reduceBrightColorsController.isReduceBrightColorsActivated = false
+ runCurrent()
+
+ assertThat(dataList).hasSize(3)
+ assertThat(dataList.map { it.isEnabled }).isEqualTo(listOf(false, true, false))
+ }
+
+ private companion object {
+ val TEST_USER = UserHandle.of(1)!!
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..6ea5e63
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.reduceBrightColorsController
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class ReduceBrightColorsTileUserActionInteractorTest : SysuiTestCase() {
+
+ private val kosmos = Kosmos()
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+ private val controller = kosmos.reduceBrightColorsController
+
+ private val underTest =
+ ReduceBrightColorsTileUserActionInteractor(
+ inputHandler,
+ controller,
+ )
+
+ @Test
+ fun handleClickWhenEnabled() = runTest {
+ val wasEnabled = true
+ controller.isReduceBrightColorsActivated = wasEnabled
+
+ underTest.handleInput(QSTileInputTestKtx.click(ReduceBrightColorsTileModel(wasEnabled)))
+
+ assertThat(controller.isReduceBrightColorsActivated).isEqualTo(!wasEnabled)
+ }
+
+ @Test
+ fun handleClickWhenDisabled() = runTest {
+ val wasEnabled = false
+ controller.isReduceBrightColorsActivated = wasEnabled
+
+ underTest.handleInput(QSTileInputTestKtx.click(ReduceBrightColorsTileModel(wasEnabled)))
+
+ assertThat(controller.isReduceBrightColorsActivated).isEqualTo(!wasEnabled)
+ }
+
+ @Test
+ fun handleLongClickWhenDisabled() = runTest {
+ val enabled = false
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(ReduceBrightColorsTileModel(enabled)))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS)
+ }
+ }
+
+ @Test
+ fun handleLongClickWhenEnabled() = runTest {
+ val enabled = true
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(ReduceBrightColorsTileModel(enabled)))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
new file mode 100644
index 0000000..10e9bd6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.reducebrightness.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.service.quicksettings.Tile
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.qs.tiles.impl.reducebrightness.qsReduceBrightColorsTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ReduceBrightColorsTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val config = kosmos.qsReduceBrightColorsTileConfig
+
+ private lateinit var mapper: ReduceBrightColorsTileMapper
+
+ @Before
+ fun setup() {
+ mapper =
+ ReduceBrightColorsTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(R.drawable.qs_extra_dim_icon_on, TestStubDrawable())
+ addOverride(R.drawable.qs_extra_dim_icon_off, TestStubDrawable())
+ }
+ .resources,
+ context.theme
+ )
+ }
+
+ @Test
+ fun disabledModel() {
+ val inputModel = ReduceBrightColorsTileModel(false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createReduceBrightColorsTileState(
+ QSTileState.ActivationState.INACTIVE,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun enabledModel() {
+ val inputModel = ReduceBrightColorsTileModel(true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState = createReduceBrightColorsTileState(QSTileState.ActivationState.ACTIVE)
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createReduceBrightColorsTileState(
+ activationState: QSTileState.ActivationState,
+ ): QSTileState {
+ val label =
+ context.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
+ return QSTileState(
+ {
+ Icon.Loaded(
+ context.getDrawable(
+ if (activationState == QSTileState.ActivationState.ACTIVE)
+ R.drawable.qs_extra_dim_icon_on
+ else R.drawable.qs_extra_dim_icon_off
+ )!!,
+ null
+ )
+ },
+ label,
+ activationState,
+ context.resources
+ .getStringArray(R.array.tile_states_reduce_brightness)[
+ if (activationState == QSTileState.ActivationState.ACTIVE) Tile.STATE_ACTIVE
+ else Tile.STATE_INACTIVE],
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ label,
+ null,
+ QSTileState.SideViewIcon.None,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileDataInteractorTest.kt
new file mode 100644
index 0000000..954f691
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileDataInteractorTest.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor
+
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
+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.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.screenrecord.domain.model.ScreenRecordTileModel
+import com.android.systemui.screenrecord.RecordingController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class ScreenRecordTileDataInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val controller = mock<RecordingController>()
+ private val underTest: ScreenRecordTileDataInteractor =
+ ScreenRecordTileDataInteractor(testScope.testScheduler, controller)
+
+ private val isRecording = ScreenRecordTileModel.Recording
+ private val isDoingNothing = ScreenRecordTileModel.DoingNothing
+ private val isStarting0 = ScreenRecordTileModel.Starting(0)
+
+ @Test
+ fun isAvailable_returnsTrue() = runTest {
+ val availability by collectLastValue(underTest.availability(TEST_USER))
+
+ assertThat(availability).isTrue()
+ }
+
+ @Test
+ fun dataMatchesController() =
+ testScope.runTest {
+ whenever(controller.isRecording).thenReturn(false)
+ whenever(controller.isStarting).thenReturn(false)
+
+ val callbackCaptor = argumentCaptor<RecordingController.RecordingStateChangeCallback>()
+
+ val lastModel by
+ collectLastValue(
+ underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ runCurrent()
+
+ verify(controller).addCallback(callbackCaptor.capture())
+ val callback = callbackCaptor.value
+
+ assertThat(lastModel).isEqualTo(isDoingNothing)
+
+ val expectedModelStartingIn1 = ScreenRecordTileModel.Starting(1)
+ callback.onCountdown(1)
+ assertThat(lastModel).isEqualTo(expectedModelStartingIn1)
+
+ val expectedModelStartingIn0 = isStarting0
+ callback.onCountdown(0)
+ assertThat(lastModel).isEqualTo(expectedModelStartingIn0)
+
+ callback.onCountdownEnd()
+ assertThat(lastModel).isEqualTo(isDoingNothing)
+
+ callback.onRecordingStart()
+ assertThat(lastModel).isEqualTo(isRecording)
+
+ callback.onRecordingEnd()
+ assertThat(lastModel).isEqualTo(isDoingNothing)
+ }
+
+ @Test
+ fun data_whenRecording_matchesController() =
+ testScope.runTest {
+ whenever(controller.isRecording).thenReturn(true)
+ whenever(controller.isStarting).thenReturn(false)
+
+ val lastModel by
+ collectLastValue(
+ underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ runCurrent()
+
+ assertThat(lastModel).isEqualTo(isRecording)
+ }
+
+ @Test
+ fun data_whenStarting_matchesController() =
+ testScope.runTest {
+ whenever(controller.isRecording).thenReturn(false)
+ whenever(controller.isStarting).thenReturn(true)
+
+ val lastModel by
+ collectLastValue(
+ underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ runCurrent()
+
+ assertThat(lastModel).isEqualTo(isStarting0)
+ }
+
+ @Test
+ fun data_whenRecordingAndStarting_matchesControllerRecording() =
+ testScope.runTest {
+ whenever(controller.isRecording).thenReturn(true)
+ whenever(controller.isStarting).thenReturn(true)
+
+ val lastModel by
+ collectLastValue(
+ underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ runCurrent()
+
+ assertThat(lastModel).isEqualTo(isRecording)
+ }
+
+ private companion object {
+ val TEST_USER = UserHandle.of(1)!!
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..b9321d5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor
+
+import android.app.Dialog
+import android.os.UserHandle
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.dialogTransitionAnimator
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.screenrecord.domain.model.ScreenRecordTileModel
+import com.android.systemui.screenrecord.RecordingController
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ScreenRecordTileUserActionInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val keyguardInteractor = kosmos.keyguardInteractor
+ private val dialogTransitionAnimator = mock<DialogTransitionAnimator>()
+ private val featureFlags = kosmos.featureFlagsClassic
+ private val activityStarter = kosmos.activityStarter
+ private val keyguardDismissUtil = mock<KeyguardDismissUtil>()
+ private val panelInteractor = mock<PanelInteractor>()
+ private val dialog = mock<Dialog>()
+ private val recordingController =
+ mock<RecordingController> {
+ whenever(
+ createScreenRecordDialog(
+ eq(context),
+ eq(featureFlags),
+ eq(dialogTransitionAnimator),
+ eq(activityStarter),
+ any()
+ )
+ )
+ .thenReturn(dialog)
+ }
+
+ private val underTest =
+ ScreenRecordTileUserActionInteractor(
+ context,
+ testScope.testScheduler,
+ testScope.testScheduler,
+ recordingController,
+ keyguardInteractor,
+ keyguardDismissUtil,
+ dialogTransitionAnimator,
+ panelInteractor,
+ mock<MediaProjectionMetricsLogger>(),
+ featureFlags,
+ activityStarter,
+ )
+
+ @Test
+ fun handleClick_whenStarting_cancelCountdown() = runTest {
+ val startingModel = ScreenRecordTileModel.Starting(0)
+
+ underTest.handleInput(QSTileInputTestKtx.click(startingModel))
+
+ verify(recordingController).cancelCountdown()
+ }
+
+ @Test
+ fun handleClick_whenRecording_stopRecording() = runTest {
+ val recordingModel = ScreenRecordTileModel.Recording
+
+ underTest.handleInput(QSTileInputTestKtx.click(recordingModel))
+
+ verify(recordingController).stopRecording()
+ }
+
+ @Test
+ fun handleClick_whenDoingNothing_createDialogDismissPanelShowDialog() = runTest {
+ val recordingModel = ScreenRecordTileModel.DoingNothing
+
+ underTest.handleInput(QSTileInputTestKtx.click(recordingModel))
+ val onStartRecordingClickedCaptor = argumentCaptor<Runnable>()
+ verify(recordingController)
+ .createScreenRecordDialog(
+ eq(context),
+ eq(featureFlags),
+ eq(dialogTransitionAnimator),
+ eq(activityStarter),
+ onStartRecordingClickedCaptor.capture()
+ )
+
+ val onDismissActionCaptor = argumentCaptor<OnDismissAction>()
+ verify(keyguardDismissUtil)
+ .executeWhenUnlocked(onDismissActionCaptor.capture(), eq(false), eq(true))
+ onDismissActionCaptor.value.onDismiss()
+ verify(dialog).show() // because the view was null
+
+ // When starting the recording, we collapse the shade and disable the dialog animation.
+ onStartRecordingClickedCaptor.value.run()
+ verify(dialogTransitionAnimator).disableAllCurrentDialogsExitAnimations()
+ verify(panelInteractor).collapsePanels()
+ }
+
+ /**
+ * When the input view is not null and keyguard is not showing, dialog should animate and show
+ */
+ @Test
+ fun handleClickFromView_whenDoingNothing_whenKeyguardNotShowing_showDialogFromView() = runTest {
+ val view = mock<View>()
+ kosmos.fakeKeyguardRepository.setKeyguardShowing(false)
+
+ val recordingModel = ScreenRecordTileModel.DoingNothing
+
+ underTest.handleInput(QSTileInputTestKtx.click(recordingModel, UserHandle.CURRENT, view))
+ val onStartRecordingClickedCaptor = argumentCaptor<Runnable>()
+ verify(recordingController)
+ .createScreenRecordDialog(
+ eq(context),
+ eq(featureFlags),
+ eq(dialogTransitionAnimator),
+ eq(activityStarter),
+ onStartRecordingClickedCaptor.capture()
+ )
+
+ val onDismissActionCaptor = argumentCaptor<OnDismissAction>()
+ verify(keyguardDismissUtil)
+ .executeWhenUnlocked(onDismissActionCaptor.capture(), eq(false), eq(true))
+ onDismissActionCaptor.value.onDismiss()
+ verify(dialogTransitionAnimator).showFromView(eq(dialog), eq(view), any(), eq(true))
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
new file mode 100644
index 0000000..d7b7ab6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.screenrecord.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.text.TextUtils
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.screenrecord.domain.model.ScreenRecordTileModel
+import com.android.systemui.qs.tiles.impl.screenrecord.domain.ui.ScreenRecordTileMapper
+import com.android.systemui.qs.tiles.impl.screenrecord.qsScreenRecordTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ScreenRecordTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val config = kosmos.qsScreenRecordTileConfig
+
+ private lateinit var mapper: ScreenRecordTileMapper
+
+ @Before
+ fun setup() {
+ mapper =
+ ScreenRecordTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(R.drawable.qs_screen_record_icon_on, TestStubDrawable())
+ addOverride(R.drawable.qs_screen_record_icon_off, TestStubDrawable())
+ }
+ .resources,
+ context.theme
+ )
+ }
+
+ @Test
+ fun activeStateMatchesRecordingDataModel() {
+ val inputModel = ScreenRecordTileModel.Recording
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createScreenRecordTileState(
+ QSTileState.ActivationState.ACTIVE,
+ R.drawable.qs_screen_record_icon_on,
+ context.getString(R.string.quick_settings_screen_record_stop),
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun activeStateMatchesStartingDataModel() {
+ val timeLeft = 0L
+ val inputModel = ScreenRecordTileModel.Starting(timeLeft)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createScreenRecordTileState(
+ QSTileState.ActivationState.ACTIVE,
+ R.drawable.qs_screen_record_icon_on,
+ String.format("%d...", timeLeft)
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun inactiveStateMatchesDisabledDataModel() {
+ val inputModel = ScreenRecordTileModel.DoingNothing
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createScreenRecordTileState(
+ QSTileState.ActivationState.INACTIVE,
+ R.drawable.qs_screen_record_icon_off,
+ context.getString(R.string.quick_settings_screen_record_start),
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createScreenRecordTileState(
+ activationState: QSTileState.ActivationState,
+ iconRes: Int,
+ secondaryLabel: String,
+ ): QSTileState {
+ val label = context.getString(R.string.quick_settings_screen_record_label)
+
+ return QSTileState(
+ { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ label,
+ activationState,
+ secondaryLabel,
+ setOf(QSTileState.UserAction.CLICK),
+ if (TextUtils.isEmpty(secondaryLabel)) label
+ else TextUtils.concat(label, ", ", secondaryLabel),
+ null,
+ if (activationState == QSTileState.ActivationState.INACTIVE)
+ QSTileState.SideViewIcon.Chevron
+ else QSTileState.SideViewIcon.None,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 139289a..3727c11 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -24,8 +24,8 @@
import com.android.compose.animation.scene.UserActionResult
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.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -34,18 +34,8 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
-import com.android.systemui.shade.domain.interactor.privacyChipInteractor
-import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
-import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
-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.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -64,8 +54,6 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
- private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private val qsFlexiglassAdapter = FakeQSSceneAdapter({ mock() })
private val footerActionsViewModel = mock<FooterActionsViewModel>()
private val footerActionsViewModelFactory =
@@ -74,45 +62,18 @@
}
private val footerActionsController = mock<FooterActionsController>()
- private var mobileIconsViewModel: MobileIconsViewModel =
- MobileIconsViewModel(
- logger = mock(),
- verboseLogger = mock(),
- interactor = mobileIconsInteractor,
- airplaneModeInteractor =
- AirplaneModeInteractor(
- FakeAirplaneModeRepository(),
- FakeConnectivityRepository(),
- FakeMobileConnectionsRepository(),
- ),
- constants = mock(),
- flags,
- scope = testScope.backgroundScope,
- )
private val sceneInteractor = kosmos.sceneInteractor
- private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
-
private lateinit var underTest: QuickSettingsSceneViewModel
@Before
fun setUp() {
- shadeHeaderViewModel =
- ShadeHeaderViewModel(
- applicationScope = testScope.backgroundScope,
- context = context,
- shadeInteractor = kosmos.shadeInteractor,
- mobileIconsInteractor = mobileIconsInteractor,
- mobileIconsViewModel = mobileIconsViewModel,
- privacyChipInteractor = kosmos.privacyChipInteractor,
- clockInteractor = kosmos.shadeHeaderClockInteractor,
- broadcastDispatcher = fakeBroadcastDispatcher,
- )
+ kosmos.fakeFeatureFlagsClassic.set(Flags.NEW_NETWORK_SLICE_UI, false)
underTest =
QuickSettingsSceneViewModel(
brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
- shadeHeaderViewModel = shadeHeaderViewModel,
+ shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
footerActionsViewModelFactory = footerActionsViewModelFactory,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 93302e3..65fd101 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -51,6 +51,7 @@
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -69,30 +70,22 @@
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.SceneContainerStartable
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
-import com.android.systemui.shade.domain.interactor.privacyChipInteractor
-import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
-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.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
import com.android.systemui.testKosmos
+import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -136,9 +129,10 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper
+@EnableSceneContainer
class SceneFrameworkIntegrationTest : SysuiTestCase() {
- private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneContainerConfig by lazy { kosmos.sceneContainerConfig }
private val sceneInteractor by lazy { kosmos.sceneInteractor }
@@ -180,25 +174,6 @@
)
}
- private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
-
- private var mobileIconsViewModel: MobileIconsViewModel =
- MobileIconsViewModel(
- logger = mock(),
- verboseLogger = mock(),
- interactor = mobileIconsInteractor,
- airplaneModeInteractor =
- AirplaneModeInteractor(
- FakeAirplaneModeRepository(),
- FakeConnectivityRepository(),
- FakeMobileConnectionsRepository(),
- ),
- constants = mock(),
- flags = kosmos.fakeFeatureFlagsClassic,
- scope = testScope.backgroundScope,
- )
-
- private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
private lateinit var shadeSceneViewModel: ShadeSceneViewModel
private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
@@ -241,23 +216,11 @@
bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor
bouncerViewModel = kosmos.bouncerViewModel
- shadeHeaderViewModel =
- ShadeHeaderViewModel(
- applicationScope = testScope.backgroundScope,
- context = context,
- shadeInteractor = kosmos.shadeInteractor,
- mobileIconsInteractor = mobileIconsInteractor,
- mobileIconsViewModel = mobileIconsViewModel,
- privacyChipInteractor = kosmos.privacyChipInteractor,
- clockInteractor = kosmos.shadeHeaderClockInteractor,
- broadcastDispatcher = fakeBroadcastDispatcher,
- )
-
shadeSceneViewModel =
ShadeSceneViewModel(
applicationScope = testScope.backgroundScope,
deviceEntryInteractor = deviceEntryInteractor,
- shadeHeaderViewModel = shadeHeaderViewModel,
+ shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
@@ -266,6 +229,7 @@
footerActionsController = kosmos.footerActionsController,
footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
sceneInteractor = sceneInteractor,
+ unfoldTransitionInteractor = kosmos.unfoldTransitionInteractor,
)
val displayTracker = FakeDisplayTracker(context)
@@ -275,15 +239,15 @@
applicationScope = testScope.backgroundScope,
sceneInteractor = sceneInteractor,
deviceEntryInteractor = deviceEntryInteractor,
+ deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
+ bouncerInteractor = bouncerInteractor,
keyguardInteractor = keyguardInteractor,
- flags = kosmos.fakeSceneContainerFlags,
sysUiState = sysUiState,
displayId = displayTracker.defaultDisplayId,
sceneLogger = mock(),
falsingCollector = kosmos.falsingCollector,
falsingManager = kosmos.falsingManager,
powerInteractor = powerInteractor,
- bouncerInteractor = bouncerInteractor,
simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
windowController = mock(),
@@ -292,7 +256,6 @@
headsUpInteractor = kosmos.headsUpNotificationInteractor,
occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor,
- deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
shadeInteractor = kosmos.shadeInteractor,
)
startable.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 7f7c24e..8e2eea1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -23,10 +23,10 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -39,9 +39,10 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class SceneContainerRepositoryTest : SysuiTestCase() {
- private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index b179c30..63f4816 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -23,13 +23,13 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
@@ -45,6 +45,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class SceneInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -55,7 +56,6 @@
@Before
fun setUp() {
- kosmos.fakeSceneContainerFlags.enabled = true
underTest = kosmos.sceneInteractor
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index d5e43f4..bfe5ef7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -31,7 +31,6 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
@@ -82,7 +81,6 @@
headsUpManager,
powerInteractor,
activeNotificationsInteractor,
- kosmos.sceneContainerFlags,
kosmos::sceneInteractor,
)
.apply { setUp(notificationPresenter, notificationsController) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 61adcd2..1472a4d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -54,7 +54,6 @@
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -102,7 +101,6 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
- private val sceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
@@ -124,15 +122,15 @@
applicationScope = testScope.backgroundScope,
sceneInteractor = sceneInteractor,
deviceEntryInteractor = deviceEntryInteractor,
+ deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
+ bouncerInteractor = bouncerInteractor,
keyguardInteractor = keyguardInteractor,
- flags = sceneContainerFlags,
sysUiState = sysUiState,
displayId = Display.DEFAULT_DISPLAY,
sceneLogger = mock(),
falsingCollector = falsingCollector,
falsingManager = kosmos.falsingManager,
powerInteractor = powerInteractor,
- bouncerInteractor = bouncerInteractor,
simBouncerInteractor = { kosmos.simBouncerInteractor },
authenticationInteractor = { authenticationInteractor },
windowController = windowController,
@@ -141,7 +139,6 @@
headsUpInteractor = kosmos.headsUpNotificationInteractor,
occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor,
- deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
shadeInteractor = kosmos.shadeInteractor,
)
}
@@ -1245,7 +1242,6 @@
"Cannot start on the Gone scene and have the device be locked at the same time."
}
- sceneContainerFlags.enabled = true
kosmos.fakeDeviceEntryRepository.setBypassEnabled(isBypassEnabled)
authenticationMethod?.let {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index 2938acf..ae5bf07 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -21,7 +21,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.kosmos.Kosmos
import com.google.common.truth.Truth
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,15 +33,11 @@
@DisableSceneContainer
fun isNotEnabled_withoutAconfigFlags() {
Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
- Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
- Truth.assertThat(Kosmos().sceneContainerFlags.isEnabled()).isEqualTo(false)
}
@Test
@EnableSceneContainer
fun isEnabled_withAconfigFlags() {
Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(true)
- Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(true)
- Truth.assertThat(Kosmos().sceneContainerFlags.isEnabled()).isEqualTo(true)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index 7b0127e..ea95aab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -23,13 +23,13 @@
import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.classifier.fakeFalsingManager
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
@@ -45,20 +45,20 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class SceneContainerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope by lazy { kosmos.testScope }
private val sceneInteractor by lazy { kosmos.sceneInteractor }
- private val fakeSceneDataSource = kosmos.fakeSceneDataSource
- private val sceneContainerConfig = kosmos.sceneContainerConfig
- private val falsingManager = kosmos.fakeFalsingManager
+ private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource }
+ private val sceneContainerConfig by lazy { kosmos.sceneContainerConfig }
+ private val falsingManager by lazy { kosmos.fakeFalsingManager }
private lateinit var underTest: SceneContainerViewModel
@Before
fun setUp() {
- kosmos.fakeSceneContainerFlags.enabled = true
underTest =
SceneContainerViewModel(
sceneInteractor = sceneInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
index cbbcce9..420418b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -22,6 +22,7 @@
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
@@ -30,7 +31,6 @@
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -52,6 +52,7 @@
@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class ShadeControllerSceneImplTest : SysuiTestCase() {
private val kosmos = Kosmos()
private val testScope = kosmos.testScope
@@ -64,7 +65,6 @@
@Before
fun setup() {
kosmos.testCase = this
- kosmos.fakeSceneContainerFlags.enabled = true
kosmos.fakeFeatureFlagsClassic.apply {
set(Flags.FULL_SCREEN_USER_SWITCHER, false)
set(Flags.NSSL_DEBUG_LINES, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
index e759b50..26f342a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
@@ -22,9 +22,9 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shared.recents.utilities.Utilities
import com.android.systemui.testKosmos
@@ -43,8 +43,9 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
@Ignore("b/328827631")
+@EnableSceneContainer
class ShadeBackActionInteractorImplTest : SysuiTestCase() {
- val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ val kosmos = testKosmos()
val testScope = kosmos.testScope
val sceneInteractor = kosmos.sceneInteractor
val underTest = kosmos.shadeBackActionInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 4c573d3..f89f18a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -2,29 +2,18 @@
import android.content.Intent
import android.provider.AlarmClock
+import android.provider.Settings
import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
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.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.activityStarter
-import com.android.systemui.shade.domain.interactor.privacyChipInteractor
-import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
-import com.android.systemui.shade.domain.interactor.shadeInteractor
-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.data.model.SubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argThat
-import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -40,43 +29,13 @@
class ShadeHeaderViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
+ private val mobileIconsInteractor = kosmos.fakeMobileIconsInteractor
- private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
- private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
-
- private var mobileIconsViewModel: MobileIconsViewModel =
- MobileIconsViewModel(
- logger = mock(),
- verboseLogger = mock(),
- interactor = mobileIconsInteractor,
- airplaneModeInteractor =
- AirplaneModeInteractor(
- FakeAirplaneModeRepository(),
- FakeConnectivityRepository(),
- FakeMobileConnectionsRepository(),
- ),
- constants = mock(),
- flags,
- scope = testScope.backgroundScope,
- )
-
- private lateinit var underTest: ShadeHeaderViewModel
+ private val underTest: ShadeHeaderViewModel = kosmos.shadeHeaderViewModel
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
- underTest =
- ShadeHeaderViewModel(
- applicationScope = testScope.backgroundScope,
- context = context,
- shadeInteractor = kosmos.shadeInteractor,
- mobileIconsInteractor = mobileIconsInteractor,
- mobileIconsViewModel = mobileIconsViewModel,
- privacyChipInteractor = kosmos.privacyChipInteractor,
- clockInteractor = kosmos.shadeHeaderClockInteractor,
- broadcastDispatcher = fakeBroadcastDispatcher,
- )
}
@Test
@@ -105,6 +64,19 @@
)
}
+ @Test
+ fun onShadeCarrierGroupClicked_launchesNetworkSettings() =
+ testScope.runTest {
+ val activityStarter = kosmos.activityStarter
+ underTest.onShadeCarrierGroupClicked()
+
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ argThat(IntentMatcherAction(Settings.ACTION_WIRELESS_SETTINGS)),
+ anyInt(),
+ )
+ }
+
companion object {
private val SUB_1 =
SubscriptionModel(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 7a681b3..2397de6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -24,11 +24,10 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
@@ -41,24 +40,19 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.domain.interactor.privacyChipInteractor
-import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.domain.startable.shadeStartable
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
-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.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.testKosmos
+import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
+import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import java.util.Locale
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -79,29 +73,8 @@
private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
private val shadeRepository by lazy { kosmos.shadeRepository }
- private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
- private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
-
- private var mobileIconsViewModel: MobileIconsViewModel =
- MobileIconsViewModel(
- logger = mock(),
- verboseLogger = mock(),
- interactor = mobileIconsInteractor,
- airplaneModeInteractor =
- AirplaneModeInteractor(
- FakeAirplaneModeRepository(),
- FakeConnectivityRepository(),
- FakeMobileConnectionsRepository(),
- ),
- constants = mock(),
- flags,
- scope = testScope.backgroundScope,
- )
-
private val qsSceneAdapter = FakeQSSceneAdapter({ mock() })
- private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
-
private lateinit var underTest: ShadeSceneViewModel
@Mock private lateinit var mediaDataManager: MediaDataManager
@@ -109,23 +82,12 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- shadeHeaderViewModel =
- ShadeHeaderViewModel(
- applicationScope = testScope.backgroundScope,
- context = context,
- shadeInteractor = kosmos.shadeInteractor,
- mobileIconsInteractor = mobileIconsInteractor,
- mobileIconsViewModel = mobileIconsViewModel,
- privacyChipInteractor = kosmos.privacyChipInteractor,
- clockInteractor = kosmos.shadeHeaderClockInteractor,
- broadcastDispatcher = fakeBroadcastDispatcher,
- )
underTest =
ShadeSceneViewModel(
applicationScope = testScope.backgroundScope,
deviceEntryInteractor = deviceEntryInteractor,
- shadeHeaderViewModel = shadeHeaderViewModel,
+ shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
qsSceneAdapter = qsSceneAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
@@ -134,6 +96,7 @@
footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
footerActionsController = kosmos.footerActionsController,
sceneInteractor = kosmos.sceneInteractor,
+ unfoldTransitionInteractor = kosmos.unfoldTransitionInteractor,
)
}
@@ -297,4 +260,59 @@
shadeRepository.setShadeMode(ShadeMode.Split)
assertThat(shadeMode).isEqualTo(ShadeMode.Split)
}
+
+ @Test
+ fun unfoldTransitionProgress() =
+ testScope.runTest {
+ val maxTranslation = prepareConfiguration()
+ val translations by
+ collectLastValue(
+ combine(
+ underTest.unfoldTranslationX(isOnStartSide = true),
+ underTest.unfoldTranslationX(isOnStartSide = false),
+ ) { start, end ->
+ Translations(
+ start = start,
+ end = end,
+ )
+ }
+ )
+
+ val unfoldProvider = kosmos.fakeUnfoldTransitionProgressProvider
+ unfoldProvider.onTransitionStarted()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+
+ repeat(10) { repetition ->
+ val transitionProgress = 0.1f * (repetition + 1)
+ unfoldProvider.onTransitionProgress(transitionProgress)
+ assertThat(translations?.start).isEqualTo((1 - transitionProgress) * maxTranslation)
+ assertThat(translations?.end).isEqualTo(-(1 - transitionProgress) * maxTranslation)
+ }
+
+ unfoldProvider.onTransitionFinishing()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+
+ unfoldProvider.onTransitionFinished()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+ }
+
+ private fun prepareConfiguration(): Int {
+ val configuration = context.resources.configuration
+ configuration.setLayoutDirection(Locale.US)
+ kosmos.fakeConfigurationRepository.onConfigurationChange(configuration)
+ val maxTranslation = 10
+ kosmos.fakeConfigurationRepository.setDimensionPixelSize(
+ R.dimen.notification_side_paddings,
+ maxTranslation
+ )
+ return maxTranslation
+ }
+
+ private data class Translations(
+ val start: Float,
+ val end: Float,
+ )
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index a3cf929..01e1aa59 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -23,11 +23,11 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -46,11 +46,11 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
- fakeSceneContainerFlags.enabled = true
fakeFeatureFlagsClassic.apply {
set(Flags.FULL_SCREEN_USER_SWITCHER, false)
set(Flags.NSSL_DEBUG_LINES, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 8f7a56d..a023033 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -52,7 +52,6 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.mockLargeScreenHeaderHelper
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -128,7 +127,7 @@
@Before
fun setUp() {
- assertThat(kosmos.sceneContainerFlags.isEnabled()).isEqualTo(SceneContainerFlag.isEnabled)
+ assertThat(SceneContainerFlag.isEnabled).isEqualTo(SceneContainerFlag.isEnabled)
overrideResource(R.bool.config_use_split_notification_shade, false)
movementFlow = MutableStateFlow(BurnInModel())
whenever(aodBurnInViewModel.movement(any())).thenReturn(movementFlow)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index f0498de..1501d9c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -51,7 +51,6 @@
statusBarStateController = statusBarStateController,
mainExecutor = mainExecutor,
legacyActivityStarter = { legacyActivityStarterInternal },
- activityStarterInternal = { activityStarterInternal },
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index 106b548..97c8d5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryHelper.ACTIVITY_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import kotlinx.coroutines.flow.MutableStateFlow
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
index d06a6e2..a6fdd03 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
@@ -16,24 +16,28 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
-import android.net.ConnectivityManager
import android.net.wifi.WifiManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.log.LogBuffer
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
-import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import com.android.wifitrackerlib.WifiPickerTracker
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -59,14 +63,21 @@
private lateinit var demoImpl: DemoWifiRepository
@Mock private lateinit var demoModeController: DemoModeController
- @Mock private lateinit var logger: WifiInputLogger
@Mock private lateinit var tableLogger: TableLogBuffer
- @Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var wifiManager: WifiManager
@Mock private lateinit var demoModeWifiDataSource: DemoModeWifiDataSource
+ @Mock private lateinit var wifiPickerTrackerFactory: WifiPickerTrackerFactory
+ @Mock private lateinit var wifiPickerTracker: WifiPickerTracker
+
+ private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock())
private val demoModelFlow = MutableStateFlow<FakeWifiEventModel?>(null)
private val mainExecutor = FakeExecutor(FakeSystemClock())
+ private val featureFlags =
+ FakeFeatureFlagsClassic().also {
+ it.set(Flags.INSTANT_TETHER, true)
+ it.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+ }
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -78,17 +89,18 @@
// Never start in demo mode
whenever(demoModeController.isInDemoMode).thenReturn(false)
+ whenever(wifiPickerTrackerFactory.create(any(), any(), any())).thenReturn(wifiPickerTracker)
+
realImpl =
WifiRepositoryImpl(
- fakeBroadcastDispatcher,
- connectivityManager,
- FakeConnectivityRepository(),
- logger,
- tableLogger,
+ featureFlags,
+ testScope.backgroundScope,
mainExecutor,
testDispatcher,
- testScope.backgroundScope,
+ wifiPickerTrackerFactory,
wifiManager,
+ wifiLogBuffer,
+ tableLogger,
)
whenever(demoModeWifiDataSource.wifiEvents).thenReturn(demoModelFlow)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
deleted file mode 100644
index cf20ba8..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ /dev/null
@@ -1,1323 +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.statusbar.pipeline.wifi.data.repository.prod
-
-import android.content.Intent
-import android.net.ConnectivityManager
-import android.net.Network
-import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
-import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
-import android.net.NetworkCapabilities.TRANSPORT_VPN
-import android.net.NetworkCapabilities.TRANSPORT_WIFI
-import android.net.TransportInfo
-import android.net.VpnTransportInfo
-import android.net.vcn.VcnTransportInfo
-import android.net.wifi.ScanResult
-import android.net.wifi.WifiInfo
-import android.net.wifi.WifiManager
-import android.net.wifi.WifiManager.TrafficStateCallback
-import android.net.wifi.WifiManager.UNKNOWN_SSID
-import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-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.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
-import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
-import com.android.systemui.util.concurrency.FakeExecutor
-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.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import java.util.concurrent.Executor
-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.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-/**
- * Note: Any new tests added here may also need to be added to [WifiRepositoryViaTrackerLibTest].
- */
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class WifiRepositoryImplTest : SysuiTestCase() {
-
- private lateinit var underTest: WifiRepositoryImpl
-
- @Mock private lateinit var logger: WifiInputLogger
- @Mock private lateinit var tableLogger: TableLogBuffer
- @Mock private lateinit var connectivityManager: ConnectivityManager
- @Mock private lateinit var wifiManager: WifiManager
- private lateinit var executor: Executor
- private lateinit var connectivityRepository: ConnectivityRepository
-
- private val dispatcher = StandardTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- executor = FakeExecutor(FakeSystemClock())
-
- connectivityRepository =
- ConnectivityRepositoryImpl(
- connectivityManager,
- ConnectivitySlots(context),
- context,
- mock(),
- mock(),
- testScope.backgroundScope,
- mock(),
- )
-
- underTest = createRepo()
- }
-
- @Test
- fun isWifiEnabled_initiallyGetsWifiManagerValue() =
- testScope.runTest {
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
-
- underTest = createRepo()
- testScope.runCurrent()
-
- assertThat(underTest.isWifiEnabled.value).isTrue()
- }
-
- @Test
- fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
- val latest by collectLastValue(underTest.isWifiEnabled)
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
- assertThat(latest).isTrue()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
- assertThat(latest).isFalse()
- }
-
- @Test
- fun isWifiEnabled_networkLost_valueUpdated() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
- val latest by collectLastValue(underTest.isWifiEnabled)
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- getNetworkCallback().onLost(NETWORK)
-
- assertThat(latest).isTrue()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- getNetworkCallback().onLost(NETWORK)
-
- assertThat(latest).isFalse()
- }
-
- @Test
- fun isWifiEnabled_intentsReceived_valueUpdated() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiEnabled)
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
- )
-
- assertThat(latest).isTrue()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
- )
-
- assertThat(latest).isFalse()
- }
-
- @Test
- fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
- val latest by collectLastValue(underTest.isWifiEnabled)
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
- )
- assertThat(latest).isFalse()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- getNetworkCallback().onLost(NETWORK)
- assertThat(latest).isTrue()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat(latest).isFalse()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
- )
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_initiallyGetsDefault() =
- testScope.runTest { assertThat(underTest.isWifiDefault.value).isFalse() }
-
- @Test
- fun isWifiDefault_wifiNetwork_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val wifiInfo = mock<WifiInfo>().apply { whenever(this.ssid).thenReturn(SSID) }
-
- getDefaultNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(latest).isTrue()
- }
-
- /** Regression test for b/266628069. */
- @Test
- fun isWifiDefault_transportInfoIsNotWifi_andNoWifiTransport_false() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val transportInfo =
- VpnTransportInfo(
- /* type= */ 0,
- /* sessionId= */ "sessionId",
- )
- val networkCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
- whenever(it.transportInfo).thenReturn(transportInfo)
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
-
- assertThat(latest).isFalse()
- }
-
- /** Regression test for b/266628069. */
- @Test
- fun isWifiDefault_transportInfoIsNotWifi_butHasWifiTransport_true() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val transportInfo =
- VpnTransportInfo(
- /* type= */ 0,
- /* sessionId= */ "sessionId",
- )
- val networkCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
- whenever(it.transportInfo).thenReturn(transportInfo)
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_carrierMergedViaCellular_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val carrierMergedInfo =
- mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
- whenever(this.transportInfo).thenReturn(carrierMergedInfo)
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_carrierMergedViaCellular_withVcnTransport_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_carrierMergedViaWifi_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val carrierMergedInfo =
- mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
- whenever(this.transportInfo).thenReturn(carrierMergedInfo)
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_carrierMergedViaWifi_withVcnTransport_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_cellularAndWifiTransports_usesCellular_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_cellularNotVcnNetwork_isFalse() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(mock())
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isFalse()
- }
-
- @Test
- fun isWifiDefault_isCarrierMergedViaUnderlyingWifi_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val underlyingNetwork = mock<Network>()
- val carrierMergedInfo =
- mock<WifiInfo>().apply {
- mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
- }
- val underlyingWifiCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(carrierMergedInfo)
- }
- whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
- .thenReturn(underlyingWifiCapabilities)
-
- // WHEN the main capabilities have an underlying carrier merged network via WIFI
- // transport and WifiInfo
- val mainCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(null)
- whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-
- // THEN the wifi network is carrier merged, so wifi is default
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_isCarrierMergedViaUnderlyingCellular_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val underlyingCarrierMergedNetwork = mock<Network>()
- val carrierMergedInfo =
- mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
- val underlyingCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
- }
- whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
- .thenReturn(underlyingCapabilities)
-
- // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
- // transport and VcnTransportInfo
- val mainCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(null)
- whenever(it.underlyingNetworks)
- .thenReturn(listOf(underlyingCarrierMergedNetwork))
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-
- // THEN the wifi network is carrier merged, so wifi is default
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_wifiNetworkLost_isFalse() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- // First, add a network
- getDefaultNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat(latest).isTrue()
-
- // WHEN the network is lost
- getDefaultNetworkCallback().onLost(NETWORK)
-
- // THEN we update to false
- assertThat(latest).isFalse()
- }
-
- @Test
- fun wifiNetwork_initiallyGetsDefault() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT)
- }
-
- @Test
- fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(true)
- }
- val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
-
- getNetworkCallback()
- .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun wifiNetwork_neverHasHotspot() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(true)
- }
- val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
-
- getNetworkCallback()
- .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
- .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE)
- }
-
- @Test
- fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
- }
-
- @Test
- fun wifiNetwork_isCarrierMergedViaUnderlyingWifi_flowHasCarrierMerged() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val underlyingNetwork = mock<Network>()
- val carrierMergedInfo =
- mock<WifiInfo>().apply {
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.isPrimary).thenReturn(true)
- }
- val underlyingWifiCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(carrierMergedInfo)
- }
- whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
- .thenReturn(underlyingWifiCapabilities)
-
- // WHEN the main capabilities have an underlying carrier merged network via WIFI
- // transport and WifiInfo
- val mainCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(null)
- whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-
- // THEN the wifi network is carrier merged
- assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
- }
-
- @Test
- fun wifiNetwork_isCarrierMergedViaUnderlyingCellular_flowHasCarrierMerged() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val underlyingCarrierMergedNetwork = mock<Network>()
- val carrierMergedInfo =
- mock<WifiInfo>().apply {
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.isPrimary).thenReturn(true)
- }
- val underlyingCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
- }
- whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
- .thenReturn(underlyingCapabilities)
-
- // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
- // transport and VcnTransportInfo
- val mainCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(null)
- whenever(it.underlyingNetworks)
- .thenReturn(listOf(underlyingCarrierMergedNetwork))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-
- // THEN the wifi network is carrier merged
- assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
- }
-
- @Test
- fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo),
- )
-
- assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java)
- }
-
- @Test
- fun wifiNetwork_isCarrierMerged_getsCorrectValues() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val rssi = -57
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.rssi).thenReturn(rssi)
- whenever(this.subscriptionId).thenReturn(567)
- }
-
- whenever(wifiManager.calculateSignalLevel(rssi)).thenReturn(2)
- whenever(wifiManager.maxSignalLevel).thenReturn(5)
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo),
- )
-
- assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
- val latestCarrierMerged = latest as WifiNetworkModel.CarrierMerged
- assertThat(latestCarrierMerged.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestCarrierMerged.subscriptionId).isEqualTo(567)
- assertThat(latestCarrierMerged.level).isEqualTo(2)
- // numberOfLevels = maxSignalLevel + 1
- assertThat(latestCarrierMerged.numberOfLevels).isEqualTo(6)
- }
-
- @Test
- fun wifiNetwork_notValidated_networkNotValidated() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = false)
- )
-
- assertThat((latest as WifiNetworkModel.Active).isValidated).isFalse()
- }
-
- @Test
- fun wifiNetwork_validated_networkValidated() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = true)
- )
-
- assertThat((latest as WifiNetworkModel.Active).isValidated).isTrue()
- }
-
- @Test
- fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(false)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- /** Regression test for b/266628069. */
- @Test
- fun wifiNetwork_transportInfoIsNotWifi_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val transportInfo =
- VpnTransportInfo(
- /* type= */ 0,
- /* sessionId= */ "sessionId",
- )
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(transportInfo))
-
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- @Test
- fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(false)
- }
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(wifiInfo))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- @Test
- fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(mock())
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- @Test
- fun wifiNetwork_cellularAndWifiTransports_usesCellular() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- // Start with the original network
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
- // WHEN we update to a new primary network
- val newNetworkId = 456
- val newNetwork =
- mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) }
- val newSsid = "CD"
- val newWifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(newSsid)
- whenever(this.isPrimary).thenReturn(true)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(newWifiInfo))
-
- // THEN we use the new network
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(newNetworkId)
- assertThat(latestActive.ssid).isEqualTo(newSsid)
- }
-
- @Test
- fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- // Start with the original network
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
- // WHEN we notify of a new but non-primary network
- val newNetworkId = 456
- val newNetwork =
- mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) }
- val newSsid = "EF"
- val newWifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(newSsid)
- whenever(this.isPrimary).thenReturn(false)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(newWifiInfo))
-
- // THEN we still use the original network
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun wifiNetwork_newNetworkCapabilities_flowHasNewData() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(true)
- }
-
- // Start with the original network
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo, isValidated = true)
- )
-
- // WHEN we keep the same network ID but change the SSID
- val newSsid = "CD"
- val newWifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(newSsid)
- whenever(this.isPrimary).thenReturn(true)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(newWifiInfo, isValidated = false)
- )
-
- // THEN we've updated to the new SSID
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(newSsid)
- assertThat(latestActive.isValidated).isFalse()
- }
-
- @Test
- fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- // WHEN we receive #onLost without any #onCapabilitiesChanged beforehand
- getNetworkCallback().onLost(NETWORK)
-
- // THEN there's no crash and we still have no network
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- @Test
- fun wifiNetwork_currentActiveNetworkLost_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
-
- // WHEN we lose our current network
- getNetworkCallback().onLost(NETWORK)
-
- // THEN we update to no network
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- /** Possible regression test for b/278618530. */
- @Test
- fun wifiNetwork_currentCarrierMergedNetworkLost_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
- assertThat((latest as WifiNetworkModel.CarrierMerged).networkId).isEqualTo(NETWORK_ID)
-
- // WHEN we lose our current network
- getNetworkCallback().onLost(NETWORK)
-
- // THEN we update to no network
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- @Test
- fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
-
- // WHEN we lose an unknown network
- val unknownNetwork = mock<Network>().apply { whenever(this.getNetId()).thenReturn(543) }
- getNetworkCallback().onLost(unknownNetwork)
-
- // THEN we still have our previous network
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
-
- // WHEN we update to a new network...
- val newNetworkId = 89
- val newNetwork =
- mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) }
- getNetworkCallback()
- .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- // ...and lose the old network
- getNetworkCallback().onLost(NETWORK)
-
- // THEN we still have the new network
- assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(newNetworkId)
- }
-
- /** Regression test for b/244173280. */
- @Test
- fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() =
- testScope.runTest {
- val latest1 by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
- assertThat(latest1 is WifiNetworkModel.Active).isTrue()
- val latest1Active = latest1 as WifiNetworkModel.Active
- assertThat(latest1Active.networkId).isEqualTo(NETWORK_ID)
- assertThat(latest1Active.ssid).isEqualTo(SSID)
-
- // WHEN we add a second subscriber after having already emitted a value
- val latest2 by collectLastValue(underTest.wifiNetwork)
-
- // THEN the second subscribe receives the already-emitted value
- assertThat(latest2 is WifiNetworkModel.Active).isTrue()
- val latest2Active = latest2 as WifiNetworkModel.Active
- assertThat(latest2Active.networkId).isEqualTo(NETWORK_ID)
- assertThat(latest2Active.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun secondaryNetworks_alwaysEmpty() =
- testScope.runTest {
- val latest by collectLastValue(underTest.secondaryNetworks)
- collectLastValue(underTest.wifiNetwork)
-
- // Even WHEN we do have non-primary wifi info
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(false)
- }
- val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
-
- getNetworkCallback()
- .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
-
- // THEN the secondary networks list is empty because this repo doesn't support it
- assertThat(latest).isEmpty()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- // A non-primary network is inactive
- whenever(this.isPrimary).thenReturn(false)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_carrierMergedNetwork_false() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_invalidNetwork_false() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo),
- )
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.ssid).thenReturn(null)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.ssid).thenReturn(UNKNOWN_SSID)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.ssid).thenReturn("FakeSsid")
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_activeToInactive_trueToFalse() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- // Start with active
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.ssid).thenReturn("FakeSsid")
- }
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
-
- // WHEN the network is lost
- getNetworkCallback().onLost(NETWORK)
- testScope.runCurrent()
-
- // THEN the isWifiConnected updates
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun wifiActivity_callbackGivesNone_activityFlowHasNone() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiActivity)
-
- getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE)
-
- assertThat(latest)
- .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
- }
-
- @Test
- fun wifiActivity_callbackGivesIn_activityFlowHasIn() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiActivity)
-
- getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN)
-
- assertThat(latest)
- .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false))
- }
-
- @Test
- fun wifiActivity_callbackGivesOut_activityFlowHasOut() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiActivity)
-
- getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT)
-
- assertThat(latest)
- .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true))
- }
-
- @Test
- fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiActivity)
-
- getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT)
-
- assertThat(latest)
- .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
- }
-
- @Test
- fun wifiScanResults_containsSsidList() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiScanResults)
-
- val scanResults =
- listOf(
- ScanResult().also { it.SSID = "ssid 1" },
- ScanResult().also { it.SSID = "ssid 2" },
- ScanResult().also { it.SSID = "ssid 3" },
- ScanResult().also { it.SSID = "ssid 4" },
- ScanResult().also { it.SSID = "ssid 5" },
- )
- whenever(wifiManager.scanResults).thenReturn(scanResults)
- getScanResultsCallback().onScanResultsAvailable()
-
- val expected =
- listOf(
- WifiScanEntry(ssid = "ssid 1"),
- WifiScanEntry(ssid = "ssid 2"),
- WifiScanEntry(ssid = "ssid 3"),
- WifiScanEntry(ssid = "ssid 4"),
- WifiScanEntry(ssid = "ssid 5"),
- )
-
- assertThat(latest).isEqualTo(expected)
- }
-
- @Test
- fun wifiScanResults_updates() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiScanResults)
-
- var scanResults =
- listOf(
- ScanResult().also { it.SSID = "ssid 1" },
- ScanResult().also { it.SSID = "ssid 2" },
- ScanResult().also { it.SSID = "ssid 3" },
- ScanResult().also { it.SSID = "ssid 4" },
- ScanResult().also { it.SSID = "ssid 5" },
- )
- whenever(wifiManager.scanResults).thenReturn(scanResults)
- getScanResultsCallback().onScanResultsAvailable()
-
- // New scan representing no results
- scanResults = emptyList()
- whenever(wifiManager.scanResults).thenReturn(scanResults)
- getScanResultsCallback().onScanResultsAvailable()
-
- assertThat(latest).isEmpty()
- }
-
- private fun createRepo(): WifiRepositoryImpl {
- return WifiRepositoryImpl(
- fakeBroadcastDispatcher,
- connectivityManager,
- connectivityRepository,
- logger,
- tableLogger,
- executor,
- dispatcher,
- testScope.backgroundScope,
- wifiManager,
- )
- }
-
- private fun getTrafficStateCallback(): TrafficStateCallback {
- testScope.runCurrent()
- val callbackCaptor = argumentCaptor<TrafficStateCallback>()
- verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
- }
-
- private fun getNetworkCallback(): ConnectivityManager.NetworkCallback {
- testScope.runCurrent()
- val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
- verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
- }
-
- private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
- testScope.runCurrent()
- val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
- verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
- return callbackCaptor.value!!
- }
-
- private fun getScanResultsCallback(): WifiManager.ScanResultsCallback {
- testScope.runCurrent()
- val callbackCaptor = argumentCaptor<WifiManager.ScanResultsCallback>()
- verify(wifiManager).registerScanResultsCallback(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
- }
-
- private fun createWifiNetworkCapabilities(
- transportInfo: TransportInfo,
- isValidated: Boolean = true,
- ): NetworkCapabilities {
- return mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(transportInfo)
- whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(isValidated)
- }
- }
-
- private companion object {
- const val NETWORK_ID = 45
- val NETWORK = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
- const val SSID = "AB"
- val PRIMARY_WIFI_INFO: WifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(true)
- }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt
new file mode 100644
index 0000000..12f08a3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt
@@ -0,0 +1,184 @@
+/*
+ * 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.unfold.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+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.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
+import com.google.common.truth.Truth.assertThat
+import java.util.Locale
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.async
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UnfoldTransitionInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val unfoldTransitionProgressProvider = kosmos.fakeUnfoldTransitionProgressProvider
+
+ private val underTest: UnfoldTransitionInteractor = kosmos.unfoldTransitionInteractor
+
+ @Test
+ fun waitForTransitionFinish_noEvents_doesNotComplete() =
+ testScope.runTest {
+ val deferred = async { underTest.waitForTransitionFinish() }
+
+ runCurrent()
+
+ assertThat(deferred.isCompleted).isFalse()
+ deferred.cancel()
+ }
+
+ @Test
+ fun waitForTransitionFinish_finishEvent_completes() =
+ testScope.runTest {
+ val deferred = async { underTest.waitForTransitionFinish() }
+
+ runCurrent()
+ unfoldTransitionProgressProvider.onTransitionFinished()
+ runCurrent()
+
+ assertThat(deferred.isCompleted).isTrue()
+ deferred.cancel()
+ }
+
+ @Test
+ fun waitForTransitionFinish_otherEvent_doesNotComplete() =
+ testScope.runTest {
+ val deferred = async { underTest.waitForTransitionFinish() }
+
+ runCurrent()
+ unfoldTransitionProgressProvider.onTransitionStarted()
+ runCurrent()
+
+ assertThat(deferred.isCompleted).isFalse()
+ deferred.cancel()
+ }
+
+ @Test
+ fun unfoldTranslationX_leftToRight() =
+ testScope.runTest {
+ val maxTranslation = prepareConfiguration(isLeftToRight = true)
+ val translations by
+ collectLastValue(
+ combine(
+ underTest.unfoldTranslationX(isOnStartSide = true),
+ underTest.unfoldTranslationX(isOnStartSide = false),
+ ) { start, end ->
+ Translations(
+ start = start,
+ end = end,
+ )
+ }
+ )
+ runCurrent()
+
+ unfoldTransitionProgressProvider.onTransitionStarted()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+
+ repeat(10) { repetition ->
+ val transitionProgress = 1 - 0.1f * (repetition + 1)
+ unfoldTransitionProgressProvider.onTransitionProgress(transitionProgress)
+ assertThat(translations?.start).isEqualTo((1 - transitionProgress) * maxTranslation)
+ assertThat(translations?.end).isEqualTo(-(1 - transitionProgress) * maxTranslation)
+ }
+
+ unfoldTransitionProgressProvider.onTransitionFinishing()
+ assertThat(translations?.start).isEqualTo(maxTranslation)
+ assertThat(translations?.end).isEqualTo(-maxTranslation)
+
+ unfoldTransitionProgressProvider.onTransitionFinished()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+ }
+
+ @Test
+ fun unfoldTranslationX_rightToLeft() =
+ testScope.runTest {
+ val maxTranslation = prepareConfiguration(isLeftToRight = false)
+ val translations by
+ collectLastValue(
+ combine(
+ underTest.unfoldTranslationX(isOnStartSide = true),
+ underTest.unfoldTranslationX(isOnStartSide = false),
+ ) { start, end ->
+ Translations(
+ start = start,
+ end = end,
+ )
+ }
+ )
+ runCurrent()
+
+ unfoldTransitionProgressProvider.onTransitionStarted()
+ assertThat(translations?.start).isEqualTo(-0f)
+ assertThat(translations?.end).isEqualTo(0f)
+
+ repeat(10) { repetition ->
+ val transitionProgress = 1 - 0.1f * (repetition + 1)
+ unfoldTransitionProgressProvider.onTransitionProgress(transitionProgress)
+ assertThat(translations?.start)
+ .isEqualTo(-(1 - transitionProgress) * maxTranslation)
+ assertThat(translations?.end).isEqualTo((1 - transitionProgress) * maxTranslation)
+ }
+
+ unfoldTransitionProgressProvider.onTransitionFinishing()
+ assertThat(translations?.start).isEqualTo(-maxTranslation)
+ assertThat(translations?.end).isEqualTo(maxTranslation)
+
+ unfoldTransitionProgressProvider.onTransitionFinished()
+ assertThat(translations?.start).isEqualTo(-0f)
+ assertThat(translations?.end).isEqualTo(0f)
+ }
+
+ private fun prepareConfiguration(
+ isLeftToRight: Boolean,
+ ): Int {
+ val configuration = context.resources.configuration
+ if (isLeftToRight) {
+ configuration.setLayoutDirection(Locale.US)
+ } else {
+ configuration.setLayoutDirection(Locale("he", "il"))
+ }
+ kosmos.fakeConfigurationRepository.onConfigurationChange(configuration)
+ val maxTranslation = 10
+ kosmos.fakeConfigurationRepository.setDimensionPixelSize(
+ R.dimen.notification_side_paddings,
+ maxTranslation
+ )
+ return maxTranslation
+ }
+
+ private data class Translations(
+ val start: Float,
+ val end: Float,
+ )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt
index fdeded8..4cf924a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt
@@ -64,7 +64,7 @@
val buttonViewModel by collectLastValue(underTest.buttonViewModel)
runCurrent()
- assertThat(buttonViewModel!!.isChecked).isFalse()
+ assertThat(buttonViewModel!!.isActive).isFalse()
}
}
}
@@ -78,7 +78,7 @@
val buttonViewModel by collectLastValue(underTest.buttonViewModel)
runCurrent()
- assertThat(buttonViewModel!!.isChecked).isTrue()
+ assertThat(buttonViewModel!!.isActive).isTrue()
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
new file mode 100644
index 0000000..55e46dc
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
@@ -0,0 +1,215 @@
+/*
+ * 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.systemui.wmshell
+
+import android.content.pm.UserInfo
+import android.graphics.Color
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
+import com.android.systemui.communal.util.fakeCommunalColors
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.dock.DockManager
+import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.ScreenLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.wakefulnessLifecycle
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.model.SysUiState
+import com.android.systemui.model.sysUiState
+import com.android.systemui.notetask.NoteTaskInitializer
+import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.settings.userTracker
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.commandQueue
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.wm.shell.desktopmode.DesktopMode
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.onehanded.OneHanded
+import com.android.wm.shell.onehanded.OneHandedEventCallback
+import com.android.wm.shell.onehanded.OneHandedTransitionCallback
+import com.android.wm.shell.pip.Pip
+import com.android.wm.shell.recents.RecentTasks
+import com.android.wm.shell.splitscreen.SplitScreen
+import com.android.wm.shell.sysui.ShellInterface
+import java.util.Optional
+import java.util.concurrent.Executor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+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.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [WMShell].
+ *
+ * Build/Install/Run: atest SystemUITests:WMShellTest
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WMShellTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+
+ @Mock private lateinit var mShellInterface: ShellInterface
+ @Mock private lateinit var mScreenLifecycle: ScreenLifecycle
+ @Mock private lateinit var mPip: Pip
+ @Mock private lateinit var mSplitScreen: SplitScreen
+ @Mock private lateinit var mOneHanded: OneHanded
+ @Mock private lateinit var mNoteTaskInitializer: NoteTaskInitializer
+ @Mock private lateinit var mDesktopMode: DesktopMode
+ @Mock private lateinit var mRecentTasks: RecentTasks
+
+ private val mCommandQueue: CommandQueue = kosmos.commandQueue
+ private val mConfigurationController: ConfigurationController = kosmos.configurationController
+ private val mKeyguardStateController: KeyguardStateController = kosmos.keyguardStateController
+ private val mKeyguardUpdateMonitor: KeyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
+ private val mSysUiState: SysUiState = kosmos.sysUiState
+ private val mWakefulnessLifecycle: WakefulnessLifecycle = kosmos.wakefulnessLifecycle
+ private val mUserTracker: UserTracker = kosmos.userTracker
+ private val mSysUiMainExecutor: Executor = kosmos.fakeExecutor
+ private val communalTransitionViewModel = kosmos.communalTransitionViewModel
+
+ private lateinit var underTest: WMShell
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val displayTracker = FakeDisplayTracker(mContext)
+
+ kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+ kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+
+ underTest =
+ WMShell(
+ mContext,
+ mShellInterface,
+ Optional.of(mPip),
+ Optional.of(mSplitScreen),
+ Optional.of(mOneHanded),
+ Optional.of(mDesktopMode),
+ Optional.of(mRecentTasks),
+ mCommandQueue,
+ mConfigurationController,
+ mKeyguardStateController,
+ mKeyguardUpdateMonitor,
+ mScreenLifecycle,
+ mSysUiState,
+ mWakefulnessLifecycle,
+ mUserTracker,
+ displayTracker,
+ mNoteTaskInitializer,
+ communalTransitionViewModel,
+ JavaAdapter(testScope.backgroundScope),
+ mSysUiMainExecutor
+ )
+ }
+
+ @Test
+ fun initPip_registersCommandQueueCallback() {
+ underTest.initPip(mPip)
+ verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks::class.java))
+ }
+
+ @Test
+ fun initOneHanded_registersCallbacks() {
+ underTest.initOneHanded(mOneHanded)
+ verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks::class.java))
+ verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer::class.java))
+ verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback::class.java))
+ verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback::class.java))
+ }
+
+ @Test
+ fun initDesktopMode_registersListener() {
+ underTest.initDesktopMode(mDesktopMode)
+ verify(mDesktopMode)
+ .addVisibleTasksListener(
+ any(VisibleTasksListener::class.java),
+ any(Executor::class.java)
+ )
+ }
+
+ @Test
+ fun initRecentTasks_registersListener() {
+ underTest.initRecentTasks(mRecentTasks)
+ verify(mRecentTasks).addAnimationStateListener(any(Executor::class.java), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_COMMUNAL_HUB)
+ fun initRecentTasks_setRecentsBackgroundColorWhenCommunal() =
+ testScope.runTest {
+ val black = Color.valueOf(Color.BLACK)
+ kosmos.fakeCommunalColors.setBackgroundColor(black)
+
+ kosmos.fakeKeyguardRepository.setKeyguardShowing(false)
+
+ underTest.initRecentTasks(mRecentTasks)
+ runCurrent()
+ verify(mRecentTasks).setTransitionBackgroundColor(null)
+ verify(mRecentTasks, never()).setTransitionBackgroundColor(black)
+
+ setDocked(true)
+ // Make communal available
+ kosmos.fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+ kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
+
+ runCurrent()
+
+ verify(mRecentTasks).setTransitionBackgroundColor(black)
+ }
+
+ private fun TestScope.setDocked(docked: Boolean) {
+ kosmos.fakeDockManager.setIsDocked(docked)
+ val event =
+ if (docked) {
+ DockManager.STATE_DOCKED
+ } else {
+ DockManager.STATE_NONE
+ }
+ kosmos.fakeDockManager.setDockEvent(event)
+ runCurrent()
+ }
+
+ private companion object {
+ val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 8e2bd9b..79bf5f1 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -267,6 +267,9 @@
/** True if the clock will react to tone changes in the seed color. */
val isReactiveToTone: Boolean = true,
+
+ /** True if the clock is large frame clock, which will use weather in compose. */
+ val useCustomClockScene: Boolean = false,
)
/** Render configuration options for a clock face. Modifies the way SystemUI behaves. */
@@ -283,6 +286,9 @@
* animation will be used (e.g. a simple translation).
*/
val hasCustomPositionUpdatedAnimation: Boolean = false,
+
+ /** True if the clock is large frame clock, which will use weatherBlueprint in compose. */
+ val useCustomClockScene: Boolean = false,
)
/** Structure for keeping clock-specific settings */
diff --git a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
index 45e2a9d..bfb2ed0 100644
--- a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
@@ -83,7 +83,7 @@
<string name="kg_primary_auth_locked_out_pin" msgid="5492230176361601475">"Demasiados intentos con PIN incorrecto"</string>
<string name="kg_primary_auth_locked_out_pattern" msgid="8266214607346180952">"Demasiados intentos con patrón incorrecto"</string>
<string name="kg_primary_auth_locked_out_password" msgid="6170245108400198659">"Demasiados intentos con contraseña incorrecta"</string>
- <string name="kg_too_many_failed_attempts_countdown" msgid="2038195171919795529">"{count,plural, =1{Vuelve a intentarlo en # segundo.}many{Vuelve a intentarlo en # segundos.}other{Vuelve a intentarlo en # segundos.}}"</string>
+ <string name="kg_too_many_failed_attempts_countdown" msgid="2038195171919795529">"{count,plural, =1{Vuelve a intentarlo en # segundo}many{Vuelve a intentarlo en # segundos}other{Vuelve a intentarlo en # segundos}}"</string>
<string name="kg_sim_pin_instructions" msgid="1942424305184242951">"Ingresa el PIN de la tarjeta SIM."</string>
<string name="kg_sim_pin_instructions_multi" msgid="3639863309953109649">"Ingresa el PIN de la tarjeta SIM de \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
<string name="kg_sim_lock_esim_instructions" msgid="5577169988158738030">"<xliff:g id="PREVIOUS_MSG">%1$s</xliff:g> Inhabilita la tarjeta eSIM para usar el dispositivo sin servicio móvil."</string>
diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml
index a762b49..2b01903 100644
--- a/packages/SystemUI/res-keyguard/values-hi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml
@@ -109,8 +109,8 @@
<string name="kg_prompt_reason_restart_pin" msgid="2672166323886110512">"डिवाइस रीस्टार्ट करने पर, पिन डालना ज़रूरी है"</string>
<string name="kg_prompt_reason_restart_password" msgid="3967993994418885887">"डिवाइस रीस्टार्ट करने पर, पासवर्ड डालना ज़रूरी है"</string>
<string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ज़्यादा सुरक्षा के लिए, इसके बजाय पैटर्न का इस्तेमाल करें"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ज़्यादा सुरक्षा के लिए, इसके बजाय पिन का इस्तेमाल करें"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ज़्यादा सुरक्षा के लिए, इसके बजाय पासवर्ड का इस्तेमाल करें"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ज़्यादा सुरक्षा के लिए, पिन का इस्तेमाल करें"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ज़्यादा सुरक्षा के लिए, पासवर्ड का इस्तेमाल करें"</string>
<string name="kg_prompt_added_security_pin" msgid="5487992065995475528">"अतिरिक्त सुरक्षा के लिए पिन ज़रूरी है"</string>
<string name="kg_prompt_added_security_pattern" msgid="1017068086102168544">"अतिरिक्त सुरक्षा के लिए पैटर्न ज़रूरी है"</string>
<string name="kg_prompt_added_security_password" msgid="6053156069765029006">"अतिरिक्त सुरक्षा के लिए पासवर्ड ज़रूरी है"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ja/strings.xml b/packages/SystemUI/res-keyguard/values-ja/strings.xml
index 71969c0..9752eca 100644
--- a/packages/SystemUI/res-keyguard/values-ja/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ja/strings.xml
@@ -80,9 +80,9 @@
<string name="kg_face_locked_out" msgid="2751559491287575">"顔認証でロックを解除できません。何度もログインに失敗したためログインできません。"</string>
<string name="kg_fp_locked_out" msgid="6228277682396768830">"指紋でロックを解除できません。何度もログインに失敗したためログインできません。"</string>
<string name="kg_trust_agent_disabled" msgid="5400691179958727891">"信頼エージェントは利用できません"</string>
- <string name="kg_primary_auth_locked_out_pin" msgid="5492230176361601475">"間違った PIN による試行回数が上限を超えました"</string>
- <string name="kg_primary_auth_locked_out_pattern" msgid="8266214607346180952">"間違ったパターンによる試行回数が上限を超えました"</string>
- <string name="kg_primary_auth_locked_out_password" msgid="6170245108400198659">"間違ったパスワードによる試行回数が上限を超えました"</string>
+ <string name="kg_primary_auth_locked_out_pin" msgid="5492230176361601475">"間違った PIN による試行回数が上限に達しました"</string>
+ <string name="kg_primary_auth_locked_out_pattern" msgid="8266214607346180952">"間違ったパターンによる試行回数が上限に達しました"</string>
+ <string name="kg_primary_auth_locked_out_password" msgid="6170245108400198659">"間違ったパスワードによる試行回数が上限に達しました"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="2038195171919795529">"{count,plural, =1{# 秒後にもう一度お試しください。}other{# 秒後にもう一度お試しください。}}"</string>
<string name="kg_sim_pin_instructions" msgid="1942424305184242951">"SIM PIN を入力してください。"</string>
<string name="kg_sim_pin_instructions_multi" msgid="3639863309953109649">"「<xliff:g id="CARRIER">%1$s</xliff:g>」の SIM PIN を入力してください。"</string>
diff --git a/packages/SystemUI/res-keyguard/values-km/strings.xml b/packages/SystemUI/res-keyguard/values-km/strings.xml
index b816748..733e29c 100644
--- a/packages/SystemUI/res-keyguard/values-km/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-km/strings.xml
@@ -80,9 +80,9 @@
<string name="kg_face_locked_out" msgid="2751559491287575">"មិនអាចដោះសោដោយប្រើមុខទេ។ ព្យាយាមច្រើនដងពេក។"</string>
<string name="kg_fp_locked_out" msgid="6228277682396768830">"មិនអាចដោះសោដោយប្រើស្នាមម្រាមដៃទេ។ ព្យាយាមច្រើនដងពេក។"</string>
<string name="kg_trust_agent_disabled" msgid="5400691179958727891">"ភ្នាក់ងារទុកចិត្តមិនទំនេរទេ"</string>
- <string name="kg_primary_auth_locked_out_pin" msgid="5492230176361601475">"ព្យាយាមច្រើនដងពេកដោយប្រើកូដ PIN មិនត្រឹមត្រូវ"</string>
- <string name="kg_primary_auth_locked_out_pattern" msgid="8266214607346180952">"ព្យាយាមច្រើនដងពេកដោយប្រើលំនាំមិនត្រឹមត្រូវ"</string>
- <string name="kg_primary_auth_locked_out_password" msgid="6170245108400198659">"ព្យាយាមច្រើនដងពេកដោយប្រើពាក្យសម្ងាត់មិនត្រឹមត្រូវ"</string>
+ <string name="kg_primary_auth_locked_out_pin" msgid="5492230176361601475">"ព្យាយាមដោយប្រើកូដ PIN មិនត្រឹមត្រូវច្រើនដងពេក"</string>
+ <string name="kg_primary_auth_locked_out_pattern" msgid="8266214607346180952">"ព្យាយាមដោយប្រើលំនាំមិនត្រឹមត្រូវច្រើនដងពេក"</string>
+ <string name="kg_primary_auth_locked_out_password" msgid="6170245108400198659">"ព្យាយាមដោយប្រើពាក្យសម្ងាត់មិនត្រឹមត្រូវច្រើនដងពេក"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="2038195171919795529">"{count,plural, =1{ព្យាយាមម្តងទៀតក្នុងរយៈពេល # វិនាទីទៀត។}other{ព្យាយាមម្តងទៀតក្នុងរយៈពេល # វិនាទីទៀត។}}"</string>
<string name="kg_sim_pin_instructions" msgid="1942424305184242951">"បញ្ចូលកូដ PIN របស់ស៊ីម។"</string>
<string name="kg_sim_pin_instructions_multi" msgid="3639863309953109649">"បញ្ចូលកូដ PIN របស់ស៊ីមសម្រាប់ \"<xliff:g id="CARRIER">%1$s</xliff:g>\"។"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml
index 6d2f0e5..de53f9f 100644
--- a/packages/SystemUI/res-keyguard/values-ky/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml
@@ -80,9 +80,9 @@
<string name="kg_face_locked_out" msgid="2751559491287575">"Жүз менен кулпусу ачылбай жатат. Өтө көп жолу аракет кылдыңыз."</string>
<string name="kg_fp_locked_out" msgid="6228277682396768830">"Манжа изи менен кулпусу ачылбай жатат. Өтө көп жолу аракет кылдыңыз."</string>
<string name="kg_trust_agent_disabled" msgid="5400691179958727891">"Ишеним агенти жеткиликсиз"</string>
- <string name="kg_primary_auth_locked_out_pin" msgid="5492230176361601475">"Туура эмес PIN код менен өтө көп аракет"</string>
- <string name="kg_primary_auth_locked_out_pattern" msgid="8266214607346180952">"Туура эмес графикалык ачкыч менен өтө көп аракет"</string>
- <string name="kg_primary_auth_locked_out_password" msgid="6170245108400198659">"Туура эмес сырсөз менен өтө көп аракет"</string>
+ <string name="kg_primary_auth_locked_out_pin" msgid="5492230176361601475">"PIN код өтө көп жолу туура эмес киргизилди."</string>
+ <string name="kg_primary_auth_locked_out_pattern" msgid="8266214607346180952">"Графикалык ачкыч өтө көп жолу туура эмес тартылды."</string>
+ <string name="kg_primary_auth_locked_out_password" msgid="6170245108400198659">"Сырсөздү өтө көп жолу туура эмес киргиздиңиз."</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="2038195171919795529">"{count,plural, =1{# секунддан кийин кайталаңыз.}other{# секунддан кийин кайталаңыз.}}"</string>
<string name="kg_sim_pin_instructions" msgid="1942424305184242951">"SIM-картанын PIN кодун киргизиңиз."</string>
<string name="kg_sim_pin_instructions_multi" msgid="3639863309953109649">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" SIM-картасынын PIN кодун киргизиңиз."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ms/strings.xml b/packages/SystemUI/res-keyguard/values-ms/strings.xml
index ecf843d..0112d5d 100644
--- a/packages/SystemUI/res-keyguard/values-ms/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ms/strings.xml
@@ -83,7 +83,7 @@
<string name="kg_primary_auth_locked_out_pin" msgid="5492230176361601475">"Terlalu banyak percubaan dengan PIN yang salah"</string>
<string name="kg_primary_auth_locked_out_pattern" msgid="8266214607346180952">"Terlalu banyak percubaan dengan corak yang salah"</string>
<string name="kg_primary_auth_locked_out_password" msgid="6170245108400198659">"Terlalu banyak percubaan dengan kata laluan yang salah"</string>
- <string name="kg_too_many_failed_attempts_countdown" msgid="2038195171919795529">"{count,plural, =1{Cuba lagi dalam # saat.}other{Cuba lagi dalam # saat.}}"</string>
+ <string name="kg_too_many_failed_attempts_countdown" msgid="2038195171919795529">"{count,plural, =1{Cuba lagi selepas # saat.}other{Cuba lagi selepas # saat.}}"</string>
<string name="kg_sim_pin_instructions" msgid="1942424305184242951">"Masukkan PIN SIM."</string>
<string name="kg_sim_pin_instructions_multi" msgid="3639863309953109649">"Masukkan PIN SIM untuk \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
<string name="kg_sim_lock_esim_instructions" msgid="5577169988158738030">"<xliff:g id="PREVIOUS_MSG">%1$s</xliff:g> Lumpuhkan eSIM untuk menggunakan peranti tanpa perkhidmatan mudah alih."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml
index aa2d080..fd90d08 100644
--- a/packages/SystemUI/res-keyguard/values-ru/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml
@@ -81,7 +81,7 @@
<string name="kg_fp_locked_out" msgid="6228277682396768830">"Превышен лимит попыток разблокировки отпечатком."</string>
<string name="kg_trust_agent_disabled" msgid="5400691179958727891">"Агент доверия недоступен."</string>
<string name="kg_primary_auth_locked_out_pin" msgid="5492230176361601475">"Слишком много неудачных попыток ввести PIN-код."</string>
- <string name="kg_primary_auth_locked_out_pattern" msgid="8266214607346180952">"Слишком много неудачных попыток ввести граф. ключ."</string>
+ <string name="kg_primary_auth_locked_out_pattern" msgid="8266214607346180952">"Слишком много неудачных попыток ввести графический ключ."</string>
<string name="kg_primary_auth_locked_out_password" msgid="6170245108400198659">"Слишком много неудачных попыток ввести пароль."</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="2038195171919795529">"{count,plural, =1{Повторите попытку через # секунду.}one{Повторите попытку через # секунду.}few{Повторите попытку через # секунды.}many{Повторите попытку через # секунд.}other{Повторите попытку через # секунды.}}"</string>
<string name="kg_sim_pin_instructions" msgid="1942424305184242951">"Введите PIN-код SIM-карты."</string>
diff --git a/packages/SystemUI/res-product/values-ar/strings.xml b/packages/SystemUI/res-product/values-ar/strings.xml
index 4d4d8d0..0ddb911 100644
--- a/packages/SystemUI/res-product/values-ar/strings.xml
+++ b/packages/SystemUI/res-product/values-ar/strings.xml
@@ -34,10 +34,10 @@
<string name="kg_failed_attempts_almost_at_erase_user" product="default" msgid="8110939900089863103">"أخطأت في محاولة فتح قفل الهاتف <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد <xliff:g id="NUMBER_1">%2$d</xliff:g> محاولة غير ناجحة أخرى، ستتم إزالة الملف الشخصي لهذا المستخدم، ومن ثم يتم حذف جميع بياناته."</string>
<string name="kg_failed_attempts_now_erasing_user" product="tablet" msgid="8509811676952707883">"أخطأت في محاولة فتح قفل الجهاز اللوحي <xliff:g id="NUMBER">%d</xliff:g> مرة. ستتم إزالة الملف الشخصي لهذا المستخدم، ومن ثم يتم حذف جميع بياناته."</string>
<string name="kg_failed_attempts_now_erasing_user" product="default" msgid="3051962486994265014">"أخطأت في محاولة فتح قفل الهاتف <xliff:g id="NUMBER">%d</xliff:g> مرة. ستتم إزالة الملف الشخصي لهذا المستخدم، ومن ثم يتم حذف جميع بياناته."</string>
- <string name="kg_failed_attempts_almost_at_erase_profile" product="tablet" msgid="1049523640263353830">"أخطأت في محاولة فتح قفل الجهاز اللوحي <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد <xliff:g id="NUMBER_1">%2$d</xliff:g> محاولة غير ناجحة أخرى، ستتم إزالة الملف الشخصي للعمل، ومن ثم يتم حذف جميع بيانات الملف الشخصي."</string>
- <string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="3280816298678433681">"أخطأت في محاولة فتح قفل الهاتف <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد <xliff:g id="NUMBER_1">%2$d</xliff:g> محاولة غير ناجحة أخرى، ستتم إزالة الملف الشخصي للعمل، ومن ثم يتم حذف جميع بيانات الملف الشخصي."</string>
- <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4417100487251371559">"أخطأت في محاولة فتح قفل الجهاز اللوحي <xliff:g id="NUMBER">%d</xliff:g> مرة. ستتم إزالة الملف الشخصي للعمل، ومن ثم يتم حذف جميع بياناته."</string>
- <string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4682221342671290678">"أخطأت في محاولة فتح قفل الهاتف <xliff:g id="NUMBER">%d</xliff:g> مرة. ستتم إزالة الملف الشخصي للعمل، ومن ثم يتم حذف جميع بياناته."</string>
+ <string name="kg_failed_attempts_almost_at_erase_profile" product="tablet" msgid="1049523640263353830">"أخطأت في محاولة فتح قفل الجهاز اللوحي <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد <xliff:g id="NUMBER_1">%2$d</xliff:g> محاولة غير ناجحة أخرى، ستتم إزالة ملف العمل، ومن ثم يتم حذف جميع بيانات الملف الشخصي."</string>
+ <string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="3280816298678433681">"أخطأت في محاولة فتح قفل الهاتف <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد <xliff:g id="NUMBER_1">%2$d</xliff:g> محاولة غير ناجحة أخرى، ستتم إزالة ملف العمل، ومن ثم يتم حذف جميع بيانات الملف الشخصي."</string>
+ <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4417100487251371559">"أخطأت في محاولة فتح قفل الجهاز اللوحي <xliff:g id="NUMBER">%d</xliff:g> مرة. ستتم إزالة ملف العمل، ومن ثم يتم حذف جميع بياناته."</string>
+ <string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4682221342671290678">"أخطأت في محاولة فتح قفل الهاتف <xliff:g id="NUMBER">%d</xliff:g> مرة. ستتم إزالة ملف العمل، ومن ثم يتم حذف جميع بياناته."</string>
<string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="1860049973474855672">"رسمت نقش فتح القفل بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد إجراء <xliff:g id="NUMBER_1">%2$d</xliff:g> محاولة غير ناجحة أخرى، ستُطالَب بفتح قفل الجهاز اللوحي باستخدام معلومات حساب بريد إلكتروني.\n\n يُرجى إعادة المحاولة خلال <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانية."</string>
<string name="kg_failed_attempts_almost_at_login" product="default" msgid="44112553371516141">"رسمت نقش فتح القفل بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد إجراء <xliff:g id="NUMBER_1">%2$d</xliff:g> محاولة غير ناجحة أخرى، ستُطالَب بفتح قفل الهاتف باستخدام حساب بريد إلكتروني.\n\n يُرجى إعادة المحاولة خلال <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانية."</string>
<string name="thermal_shutdown_title" product="default" msgid="8039593017174903505">"تم إطفاء الهاتف بسبب ارتفاع درجة حرارته"</string>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml b/packages/SystemUI/res/color/notification_focus_overlay_color.xml
similarity index 68%
rename from packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml
rename to packages/SystemUI/res/color/notification_focus_overlay_color.xml
index fff41c3..6a3c7a1 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml
+++ b/packages/SystemUI/res/color/notification_focus_overlay_color.xml
@@ -14,11 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<resources>
- <style name="TextAppearance.TopIntroText"
- parent="@android:style/TextAppearance.DeviceDefault">
- <item name="android:textSize">14sp</item>
- <item name="android:textColor">@color/settingslib_materialColorOnSurfaceVariant</item>
- </style>
-</resources>
\ No newline at end of file
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:state_focused="true" android:color="?androidprv:attr/materialColorSecondary" />
+ <item android:color="@color/transparent" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml b/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml
new file mode 100644
index 0000000..6e6e032
--- /dev/null
+++ b/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml
@@ -0,0 +1,43 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<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>
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/hearing_devices_preset_spinner_background_radius" />
+ <stroke
+ android:width="1dp"
+ android:color="?androidprv:attr/textColorTertiary" />
+ <solid android:color="@android:color/transparent"/>
+ </shape>
+ </item>
+ <item
+ android:end="20dp"
+ android:gravity="end|center_vertical">
+ <vector
+ android:width="@dimen/screenrecord_spinner_arrow_size"
+ android:height="@dimen/screenrecord_spinner_arrow_size"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?androidprv:attr/colorControlNormal">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
+ </vector>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_popup_background.xml b/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_popup_background.xml
new file mode 100644
index 0000000..f35975e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_popup_background.xml
@@ -0,0 +1,22 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/hearing_devices_preset_spinner_background_radius"/>
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainer" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml
index 3f903ae..587a5a0 100644
--- a/packages/SystemUI/res/drawable/notification_material_bg.xml
+++ b/packages/SystemUI/res/drawable/notification_material_bg.xml
@@ -28,4 +28,9 @@
<solid android:color="@color/notification_state_color_default" />
</shape>
</item>
+ <item>
+ <shape>
+ <stroke android:width="3dp" android:color="@color/notification_focus_overlay_color"/>
+ </shape>
+ </item>
</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/shelf_action_chip_background.xml b/packages/SystemUI/res/drawable/shelf_action_chip_background.xml
new file mode 100644
index 0000000..63600be
--- /dev/null
+++ b/packages/SystemUI/res/drawable/shelf_action_chip_background.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<ripple
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:color="@color/overlay_button_ripple">
+ <item android:id="@android:id/background">
+ <shape android:shape="rectangle">
+ <solid android:color="?androidprv:attr/materialColorSecondary"/>
+ <corners android:radius="10000dp"/> <!-- fully-rounded radius -->
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/layout/auth_biometric_icon.xml b/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
similarity index 67%
rename from packages/SystemUI/res/layout/auth_biometric_icon.xml
rename to packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
index b2df63d..bb8cece 100644
--- a/packages/SystemUI/res/layout/auth_biometric_icon.xml
+++ b/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2023 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -14,13 +14,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-
-<com.airbnb.lottie.LottieAnimationView
+<shape
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/biometric_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:contentDescription="@null"
- android:scaleType="fitXY"/>
\ No newline at end of file
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
+ <corners android:radius="10000dp"/> <!-- fully-rounded radius -->
+</shape>
diff --git a/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml b/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml
new file mode 100644
index 0000000..a5b44e5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape xmlns:android = "http://schemas.android.com/apk/res/android">
+ <size
+ android:width = "@dimen/overlay_action_chip_margin_start"
+ android:height = "0dp"/>
+</shape>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml b/packages/SystemUI/res/drawable/shelf_action_container_clipping_shape.xml
similarity index 68%
copy from packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml
copy to packages/SystemUI/res/drawable/shelf_action_container_clipping_shape.xml
index fff41c3..76779f9 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml
+++ b/packages/SystemUI/res/drawable/shelf_action_container_clipping_shape.xml
@@ -14,11 +14,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<resources>
- <style name="TextAppearance.TopIntroText"
- parent="@android:style/TextAppearance.DeviceDefault">
- <item name="android:textSize">14sp</item>
- <item name="android:textColor">@color/settingslib_materialColorOnSurfaceVariant</item>
- </style>
-
-</resources>
\ No newline at end of file
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <!-- We don't actually draw anything, just expressing the shape for clipping. -->
+ <solid android:color="#0000"/>
+ <corners android:radius="10000dp"/> <!-- fully-rounded radius -->
+</shape>
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 43ff7b60..76d10cc 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -256,6 +256,24 @@
app:constraint_referenced_ids="pair_new_device_button,bluetooth_auto_on_toggle_info_text" />
<Button
+ android:id="@+id/audio_sharing_button"
+ style="@style/Widget.Dialog.Button.BorderButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="9dp"
+ android:layout_marginBottom="@dimen/dialog_bottom_padding"
+ android:layout_marginEnd="@dimen/dialog_side_padding"
+ android:layout_marginStart="@dimen/dialog_side_padding"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:text="@string/quick_settings_bluetooth_audio_sharing_button"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/barrier"
+ app:layout_constraintVertical_bias="1"
+ android:visibility="gone" />
+
+ <Button
android:id="@+id/done_button"
style="@style/Widget.Dialog.Button"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
index a5cdaeb..8e1d0a5 100644
--- a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
@@ -17,6 +17,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/root"
style="@style/Widget.SliceView.Panel"
android:layout_width="wrap_content"
@@ -26,9 +27,33 @@
android:id="@+id/device_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintBottom_toTopOf="@id/pair_new_device_button" />
+ app:layout_constraintBottom_toTopOf="@id/preset_spinner" />
+
+ <Spinner
+ android:id="@+id/preset_spinner"
+ style="@style/BluetoothTileDialog.Device"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/hearing_devices_preset_spinner_height"
+ android:layout_marginTop="@dimen/hearing_devices_preset_spinner_margin"
+ android:layout_marginBottom="@dimen/hearing_devices_preset_spinner_margin"
+ android:gravity="center_vertical"
+ android:background="@drawable/hearing_devices_preset_spinner_background"
+ android:popupBackground="@drawable/hearing_devices_preset_spinner_popup_background"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/device_list"
+ app:layout_constraintBottom_toTopOf="@id/pair_new_device_button"
+ android:visibility="gone"/>
+
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/device_barrier"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierDirection="bottom"
+ app:constraint_referenced_ids="device_list,preset_spinner" />
<Button
android:id="@+id/pair_new_device_button"
@@ -41,7 +66,7 @@
android:contentDescription="@string/accessibility_hearing_device_pair_new_device"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toBottomOf="@id/device_list"
+ app:layout_constraintTop_toBottomOf="@id/device_barrier"
android:drawableStart="@drawable/ic_add"
android:drawablePadding="20dp"
android:drawableTint="?android:attr/textColorPrimary"
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 1eb05bf..e3c5a7d 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -36,8 +36,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
- android:focusable="true"
- android:accessibilityTraversalBefore="@android:id/edit"
+ android:focusable="false"
+ android:importantForAccessibility="yes"
android:clipToPadding="false"
android:clipChildren="false">
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index eeb64bd8..6a5b999f 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -20,39 +20,37 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <ImageView
+ <FrameLayout
android:id="@+id/actions_container_background"
android:visibility="gone"
- android:layout_height="0dp"
- android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
android:elevation="4dp"
- android:background="@drawable/action_chip_container_background"
- android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+ android:background="@drawable/shelf_action_chip_container_background"
+ android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="@+id/actions_container"
- app:layout_constraintEnd_toEndOf="@+id/actions_container"
- app:layout_constraintBottom_toTopOf="@id/guideline"/>
- <HorizontalScrollView
- android:id="@+id/actions_container"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
- android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
- android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
- android:elevation="4dp"
- android:scrollbars="none"
- app:layout_constraintHorizontal_bias="0"
- app:layout_constraintWidth_percent="1.0"
- app:layout_constraintWidth_max="wrap"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintBottom_toBottomOf="@id/actions_container_background">
- <LinearLayout
- android:id="@+id/screenshot_actions"
+ app:layout_constraintBottom_toTopOf="@id/guideline"
+ >
+ <HorizontalScrollView
+ android:id="@+id/actions_container"
android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- </HorizontalScrollView>
+ android:layout_height="wrap_content"
+ android:layout_marginVertical="@dimen/overlay_action_container_padding_vertical"
+ android:layout_marginHorizontal="@dimen/overlay_action_chip_margin_start"
+ android:background="@drawable/shelf_action_container_clipping_shape"
+ android:clipToOutline="true"
+ android:scrollbars="none">
+ <LinearLayout
+ android:id="@+id/screenshot_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:showDividers="middle"
+ android:divider="@drawable/shelf_action_chip_divider"
+ android:animateLayoutChanges="true"
+ />
+ </HorizontalScrollView>
+ </FrameLayout>
<View
android:id="@+id/screenshot_preview_border"
android:layout_width="0dp"
@@ -66,7 +64,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/screenshot_preview"
app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
- app:layout_constraintBottom_toTopOf="@id/actions_container"/>
+ app:layout_constraintBottom_toTopOf="@id/actions_container_background"/>
<ImageView
android:id="@+id/screenshot_preview"
android:layout_width="@dimen/overlay_x_scale"
diff --git a/packages/SystemUI/res/layout/shelf_action_chip.xml b/packages/SystemUI/res/layout/shelf_action_chip.xml
new file mode 100644
index 0000000..c7606e4
--- /dev/null
+++ b/packages/SystemUI/res/layout/shelf_action_chip.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:theme="@style/FloatingOverlay"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingVertical="@dimen/overlay_action_chip_padding_vertical"
+ android:gravity="center"
+ android:background="@drawable/shelf_action_chip_background"
+ >
+ <ImageView
+ android:id="@+id/overlay_action_chip_icon"
+ android:tint="?androidprv:attr/materialColorOnSecondary"
+ android:layout_width="@dimen/overlay_action_chip_icon_size"
+ android:layout_height="@dimen/overlay_action_chip_icon_size"/>
+ <TextView
+ android:id="@+id/overlay_action_chip_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:textSize="@dimen/overlay_action_chip_text_size"
+ android:textColor="?androidprv:attr/materialColorOnSecondary"/>
+</LinearLayout>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index ccea0fc..0e1bed8 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Sien alles"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Gebruik Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Gekoppel"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gestoor"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ontkoppel"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiveer"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Skakel dit môre outomaties weer aan"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Kenmerke soos Kitsdeel, Kry My Toestel en toestelligging gebruik Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth sal môre om 05:00 aanskakel"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batterykrag"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Oudio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Kopstuk"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 9e528e0c..3709a900 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"ሁሉንም ይመልከቱ"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ብሉቱዝን ይጠቀሙ"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ተገናኝቷል"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ተቀምጧል"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ግንኙነትን አቋርጥ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ያግብሩ"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ነገ እንደገና በራስ-ሰር አስጀምር"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"እንደ ፈጣን ማጋራት፣ የእኔን መሣሪያ አግኝ እና የመሣሪያ አካባቢ ያሉ ባህሪያት ብሉቱዝን ይጠቀማሉ"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"ብሉቱዝ ነገ ጠዋቱ 5 ሰዓት ላይ ይበራል"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ባትሪ"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ኦዲዮ"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ማዳመጫ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 759f957..0a7250a 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -75,7 +75,7 @@
<string name="global_action_smart_lock_disabled" msgid="6286551337177954859">"تم إيقاف ميزة \"إبقاء الجهاز مفتوحًا\"."</string>
<string name="remote_input_image_insertion_text" msgid="4850791636452521123">"أرسَل صورة"</string>
<string name="screenshot_saving_title" msgid="2298349784913287333">"جارٍ حفظ لقطة الشاشة..."</string>
- <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"جارٍ حفظ لقطة الشاشة في الملف الشخصي للعمل…"</string>
+ <string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"جارٍ حفظ لقطة الشاشة في ملف العمل…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"تم حفظ لقطة الشاشة."</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"تعذّر حفظ لقطة الشاشة"</string>
<string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"الشاشة الخارجية"</string>
@@ -90,13 +90,13 @@
<string name="screenshot_share_description" msgid="2861628935812656612">"مشاركة لقطة الشاشة"</string>
<string name="screenshot_scroll_label" msgid="2930198809899329367">"التقاط المزيد من المحتوى"</string>
<string name="screenshot_dismiss_description" msgid="4702341245899508786">"إغلاق لقطة الشاشة"</string>
- <string name="screenshot_dismiss_work_profile" msgid="3101530842987697045">"تجاهل رسالة الملف الشخصي للعمل"</string>
+ <string name="screenshot_dismiss_work_profile" msgid="3101530842987697045">"تجاهل رسالة ملف العمل"</string>
<string name="screenshot_preview_description" msgid="7606510140714080474">"معاينة لقطة الشاشة"</string>
<string name="screenshot_top_boundary_pct" msgid="2520148599096479332">"الحد العلوي <xliff:g id="PERCENT">%1$d</xliff:g> في المئة"</string>
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"الحد السفلى <xliff:g id="PERCENT">%1$d</xliff:g> في المئة"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"الحد الأيسر <xliff:g id="PERCENT">%1$d</xliff:g> في المئة"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"الحد الأيمن <xliff:g id="PERCENT">%1$d</xliff:g> في المئة"</string>
- <string name="screenshot_work_profile_notification" msgid="203041724052970693">"تم حفظ لقطة الشاشة في \"<xliff:g id="APP">%1$s</xliff:g>\" في الملف الشخصي للعمل."</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"تم حفظ لقطة الشاشة في \"<xliff:g id="APP">%1$s</xliff:g>\" في ملف العمل."</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"الملفات"</string>
<string name="screenshot_detected_template" msgid="7940376642921719915">"رصَد تطبيق \"<xliff:g id="APPNAME">%1$s</xliff:g>\" لقطة الشاشة هذه."</string>
<string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"رصَد تطبيق \"<xliff:g id="APPNAME">%1$s</xliff:g>\" والتطبيقات المفتوحة الأخرى لقطة الشاشة هذه."</string>
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"عرض الكل"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"استخدام البلوتوث"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"متّصل"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"محفوظ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"إلغاء الربط"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"تفعيل"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"تفعيل البلوتوث تلقائيًا مرة أخرى غدًا"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"يُستخدَم البلوتوث في ميزات مثل Quick Share و\"العثور على جهازي\" والموقع الجغرافي للجهاز"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"سيتم تفعيل البلوتوث غدًا الساعة 5 صباحًا"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"مستوى طاقة البطارية <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"صوت"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"سماعة الرأس"</string>
@@ -515,9 +523,9 @@
<string name="quick_settings_disclosure_named_management" msgid="3476472755775165827">"هذا الجهاز يخص <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>."</string>
<string name="quick_settings_disclosure_management_vpns" msgid="929181757984262902">"ينتمي هذا الجهاز إلى مؤسستك، ويتّصل بالإنترنت من خلال خدمات الشبكة الافتراضية الخاصة (VPN)."</string>
<string name="quick_settings_disclosure_named_management_vpns" msgid="3312645578322079185">"ينتمي هذا الجهاز إلى <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>، ويتّصل بالإنترنت من خلال خدمات الشبكة الافتراضية الخاصة (VPN)."</string>
- <string name="quick_settings_disclosure_managed_profile_monitoring" msgid="1423899084754272514">"يمكن لمؤسستك مراقبة حركة بيانات الشبكة في الملف الشخصي للعمل"</string>
+ <string name="quick_settings_disclosure_managed_profile_monitoring" msgid="1423899084754272514">"يمكن لمؤسستك مراقبة حركة بيانات الشبكة في ملف العمل"</string>
<string name="quick_settings_disclosure_named_managed_profile_monitoring" msgid="8321469176706219860">"يمكن لـ <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> مراقبة حركة بيانات الشبكة في ملفك الشخصي للعمل"</string>
- <string name="quick_settings_disclosure_managed_profile_network_activity" msgid="2636594621387832827">"تكون أنشطة شبكة الملف الشخصي للعمل مرئية لمشرف تكنولوجيا المعلومات."</string>
+ <string name="quick_settings_disclosure_managed_profile_network_activity" msgid="2636594621387832827">"تكون أنشطة شبكة ملف العمل مرئية لمشرف تكنولوجيا المعلومات."</string>
<string name="quick_settings_disclosure_monitoring" msgid="8548019955631378680">"قد تكون الشبكة خاضعة للمراقبة"</string>
<string name="quick_settings_disclosure_vpns" msgid="3586175303518266301">"هذا الجهاز متّصل بالإنترنت من خلال خدمات الشبكات الافتراضية الخاصة (VPN)."</string>
<string name="quick_settings_disclosure_managed_profile_named_vpn" msgid="153393105176944100">"تطبيقات العمل الخاصة بك متّصلة بالإنترنت من خلال <xliff:g id="VPN_APP">%1$s</xliff:g>."</string>
@@ -636,7 +644,7 @@
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"إعدادات شاشة القفل"</string>
<string name="qr_code_scanner_title" msgid="1938155688725760702">"ماسح ضوئي لرمز الاستجابة السريعة"</string>
<string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"جارٍ تعديل الحالة"</string>
- <string name="status_bar_work" msgid="5238641949837091056">"الملف الشخصي للعمل"</string>
+ <string name="status_bar_work" msgid="5238641949837091056">"ملف العمل"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"وضع الطيران"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"لن تسمع المنبّه القادم في <xliff:g id="WHEN">%1$s</xliff:g>"</string>
<string name="alarm_template" msgid="2234991538018805736">"في <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -646,7 +654,7 @@
<string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"قمر صناعي، الاتصال ضعيف"</string>
<string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"قمر صناعي، الاتصال جيد"</string>
<string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"قمر صناعي، الاتصال متوفّر"</string>
- <string name="accessibility_managed_profile" msgid="4703836746209377356">"الملف الشخصي للعمل"</string>
+ <string name="accessibility_managed_profile" msgid="4703836746209377356">"ملف العمل"</string>
<string name="tuner_warning_title" msgid="7721976098452135267">"متعة للبعض وليس للجميع"</string>
<string name="tuner_warning" msgid="1861736288458481650">"توفر لك أداة ضبط واجهة مستخدم النظام طرقًا إضافية لتعديل واجهة مستخدم Android وتخصيصها. ويمكن أن تطرأ تغييرات على هذه الميزات التجريبية أو يمكن أن تتعطل هذه الميزات أو تختفي في الإصدارات المستقبلية. عليك متابعة الاستخدام مع توخي الحذر."</string>
<string name="tuner_persistent_warning" msgid="230466285569307806">"يمكن أن تطرأ تغييرات على هذه الميزات التجريبية أو يمكن أن تتعطل هذه الميزات أو تختفي في الإصدارات المستقبلية. عليك متابعة الاستخدام مع توخي الحذر."</string>
@@ -1262,8 +1270,8 @@
<string name="video_camera" msgid="7654002575156149298">"كاميرا فيديو"</string>
<string name="call_from_work_profile_title" msgid="5418253516453177114">"لا يمكن الاتصال من تطبيق شخصي"</string>
<string name="call_from_work_profile_text" msgid="2856337395968118274">"تسمح لك مؤسستك بإجراء المكالمات من تطبيقات العمل فقط."</string>
- <string name="call_from_work_profile_action" msgid="2937701298133010724">"التبديل إلى الملف الشخصي للعمل"</string>
- <string name="install_dialer_on_work_profile_action" msgid="2014659711597862506">"تثبيت تطبيق الهاتف في الملف الشخصي للعمل"</string>
+ <string name="call_from_work_profile_action" msgid="2937701298133010724">"التبديل إلى ملف العمل"</string>
+ <string name="install_dialer_on_work_profile_action" msgid="2014659711597862506">"تثبيت تطبيق الهاتف في ملف العمل"</string>
<string name="call_from_work_profile_close" msgid="5830072964434474143">"إلغاء"</string>
<string name="lock_screen_settings" msgid="6152703934761402399">"تخصيص شاشة القفل"</string>
<string name="keyguard_unlock_to_customize_ls" msgid="2068542308086253819">"الفتح لتخصيص شاشة القفل"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 1f859ac..3311588 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"আটাইবোৰ চাওক"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ব্লুটুথ ব্যৱহাৰ কৰক"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"সংযুক্ত আছে"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ছেভ কৰা হৈছে"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"সংযোগ বিচ্ছিন্ন কৰক"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"সক্ৰিয় কৰক"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"কাইলৈ পুনৰ স্বয়ংক্ৰিয়ভাৱে অন কৰক"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Quick Share, Find My Device আৰু ডিভাইচৰ অৱস্থানৰ দৰে সুবিধাই ব্লুটুথ ব্যৱহাৰ কৰে"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"কাইলৈ পুৱা ৫ বজাত ব্লুটুথ অন হ’ব"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"বেটাৰী <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"অডিঅ’"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"হেডছেট"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index dc095c9..741cc41 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Hamısına baxın"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth aç"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Qoşulub"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Yadda saxlandı"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"əlaqəni kəsin"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivləşdirin"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Sabah avtomatik aktiv edin"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Cəld Paylaşım, Cihazın Tapılması və cihaz məkanı kimi funksiyalar Bluetooth istifadə edir"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth sabah 05:00-da aktiv olacaq"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batareya"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Qulaqlıq"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 2f86ca4..583ffaf 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Prikaži sve"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sačuvano"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekinite vezu"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivirajte"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatski ponovo uključi sutra"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Funkcije kao što su Quick Share, Pronađi moj uređaj i lokacija uređaja koriste Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth će se uključiti sutra u 5:00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Nivo baterije je <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index bc03d8c..50b407f 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Паглядзець усе"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Выкарыстоўваць Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Падключана"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Захавана"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"адключыць"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"актываваць"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Аўтаматычнае ўключэнне заўтра"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Bluetooth выкарыстоўваецца для вызначэння месцазнаходжання прылады, а таксама такімі функцыямі, як Хуткае абагульванне і Знайсці прыладу"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth будзе ўключаны заўтра ў 5 гадзін раніцы"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Узровень зараду: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Гук"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнітура"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Запіс праблемы"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Пачынайце"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Спыніцеся"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Справаздача"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"З чым была звязана праблема, якая вам сустрэлася?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Выберыце тып праблемы"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Запіс экрана"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index d80b889..3a28329 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Преглед на всички"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Използване на Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Установена е връзка"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Запазено"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекратяване на връзката"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активиране"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Автоматично включване отново утре"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Bluetooth се използва от различни функции, като например „Бързо споделяне“, „Намиране на устройството ми“ и местоположението на устройството"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth ще се включи утре в 5:00 ч."</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батерия: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалки"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Записване на проблем"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Стартиране"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Спиране"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Сигнал за грешка"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"С какво имахте проблем?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Изберете тип проблем"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Запис на екрана"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 36b57ee..ec704b8 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"সব দেখুন"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ব্লুটুথ ব্যবহার করুন"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"কানেক্ট করা আছে"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"সেভ করা আছে"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ডিসকানেক্ট করুন"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"চালু করুন"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"আগামীকাল অটোমেটিক আবার চালু হবে"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"\'দ্রুত শেয়ার\', \'Find My Device\' ও \'ডিভাইস লোকেশনের\' মতো ফিচার ব্লুটুথ ব্যবহার করে"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"আগামীকাল ভোর ৫টায় ব্লুটুথ চালু হবে"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"চার্জ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"অডিও"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"হেডসেট"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"রেকর্ডিংয়ে সমস্যা"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"শুরু করুন"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"বন্ধ করুন"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"সমস্যার রিপোর্ট"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"ডিভাইস ব্যবহার করার সময় কোথায় অসুবিধা হয়েছিল?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"সমস্যার প্রকার বেছে নিন"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"স্ক্রিন রেকর্ড"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 91acfa5..fd77f14 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Prikaži sve"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sačuvano"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekid veze"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviranje"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatski uključi ponovo sutra"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Funkcije kao što su Quick Share, Pronađi moj uređaj i lokacija uređaja koriste Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth će se uključiti sutra u 5:00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> baterije"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string>
@@ -350,7 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Snimite problem"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Pokrenite"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Zaustavite"</string>
- <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Izvješće o pogrešci"</string>
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Izvještaj o grešci"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Koji dio uređaja je imao problem?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Odaberite vrstu problema"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Snimanje ekrana"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 1445623..aeb0ef0 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Mostra-ho tot"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Utilitza\'l"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connectat"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Desat"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconnecta"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activa"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Torna\'l a activar automàticament demà"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Funcions com ara Quick Share, Troba el meu dispositiu i la ubicació del dispositiu utilitzen el Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"El Bluetooth s\'activarà demà a les 5:00 h"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de bateria"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Àudio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculars"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Registra el problema"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Inicia"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Atura"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Informe d\'errors"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"L\'experiència amb el dispositiu s\'ha vist afectada?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Selecciona el tipus de problema"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Gravació de pantalla"</string>
@@ -604,7 +611,7 @@
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Toca per silenciar."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Control de soroll"</string>
<string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Àudio espacial"</string>
- <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Desactiva"</string>
+ <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Desactivat"</string>
<string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Fix"</string>
<string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Seguiment del cap"</string>
<string name="volume_ringer_change" msgid="3574969197796055532">"Toca per canviar el mode de timbre"</string>
@@ -1303,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Retroil·luminació del teclat"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Nivell %1$d de %2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Controls de la llar"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Utilitza controls de la llar com a salvapantalles"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Utilitza controls de la llar com a estalvi de pantalla"</string>
</resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 7b766ab..891f836 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Zobrazit vše"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Použít Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Připojeno"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uloženo"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"odpojit"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivovat"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Zítra znovu automaticky zapnout"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Funkce jako Quick Share, Najdi moje zařízení a vyhledávání zařízení používají Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth se zapne zítra v 5:00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterie: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Sluchátka"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Zaznamenat problém"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Spustit"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Ukončit"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Zpráva o chybě"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Co v zařízení bylo ovlivněno?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Vyberte druh problém"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Záznam obrazovky"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 2100e27..5c721a1 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Se alt"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Brug Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Der er oprettet forbindelse"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gemt"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"afbryd forbindelse"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivér"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Aktivér automatisk igen i morgen"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Funktioner som f.eks. Quick Share, Find min enhed og enhedslokation anvender Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth aktiveres i morgen kl. 5.00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Lyd"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Optag problem"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Start"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Stop"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Fejlrapport"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Hvilken del af din enhedsoplevelse blev påvirket?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Vælg problemtype"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Skærmoptagelse"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 6c4f4b5..510dba9 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -198,7 +198,7 @@
<string name="face_re_enroll_notification_title" msgid="1850838867718410520">"Entsperrung per Gesichtserkennung neu einrichten"</string>
<string name="face_re_enroll_notification_name" msgid="7384545252206120659">"Entsperrung per Gesichtserkennung"</string>
<string name="face_re_enroll_dialog_title" msgid="6392173708176069994">"Entsperrung per Gesichtserkennung einrichten"</string>
- <string name="face_re_enroll_dialog_content" msgid="7353502359464038511">"Wenn du die Entsperrung per Gesichtserkennung neu einrichtest, wird dein aktuelles Gesichtsmodell gelöscht.\n\nDu musst diese Funktion neu einrichten, damit du dein Smartphone weiterhin mit deinem Gesicht entsperren kannst."</string>
+ <string name="face_re_enroll_dialog_content" msgid="7353502359464038511">"Wenn du die Entsperrung per Gesichtserkennung neu einrichtest, wird dein aktuelles Gesichtsmodell gelöscht.\n\nDu musst diese Funktion neu einrichten, damit du dein Smartphone weiterhin mithilfe der Gesichtserkennung entsperren kannst."</string>
<string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"Die Entsperrung per Gesichtserkennung konnte nicht eingerichtet werden. Gehe zu den Einstellungen und versuche es noch einmal."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Berühre den Fingerabdrucksensor"</string>
<string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"Tippe zum Fortfahren auf das Symbol „Entsperren“"</string>
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Alle anzeigen"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth verwenden"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Verbunden"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gespeichert"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"Verknüpfung aufheben"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivieren"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Morgen automatisch wieder aktivieren"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Für Funktionen wie Quick Share, „Mein Gerät finden“ und den Gerätestandort wird Bluetooth verwendet"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth wird morgen um 5:00 Uhr aktiviert"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akkustand: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Problem aufnehmen"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Aufnahme starten"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Aufnahme beenden"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Fehlerbericht"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Welche Bereiche des Geräts waren betroffen?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Art des Problems auswählen"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Bildschirmaufnahme"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 4c964de..a881536 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Εμφάνιση όλων"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Χρήση Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Συνδέθηκε"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Αποθηκεύτηκε"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"αποσύνδεση"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ενεργοποίηση"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Αυτόματη ενεργοποίηση ξανά αύριο"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Λειτουργίες όπως το Quick Share, η Εύρεση συσκευής και η τοποθεσία της συσκευής χρησιμοποιούν Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Το Bluetooth θα ενεργοποιηθεί αύριο στις 5 π.μ."</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Μπαταρία <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ήχος"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ακουστικά"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Εγγραφή προβλήματος"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Έναρξη"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Διακοπή"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Αναφορά σφάλματος"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Ποιο κομμάτι της εμπειρίας συσκευής επηρεάστηκε;"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Επιλογή τύπου προβλήματος"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Εγγραφή οθόνης"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index fc27f16..d2af8a6 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"See all"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatically turn on again tomorrow"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Features like Quick Share, Find My Device and device location use Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth will turn on tomorrow at 5.00 a.m."</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Record issue"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Start"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Stop"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Bug report"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"What part of your device experience was affected?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Select issue type"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Screen record"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 088f751..836eefa 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -270,12 +270,15 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"See all"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio Sharing"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatically turn on again tomorrow"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Features like Quick Share, Find My Device, and device location use Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth will turn on tomorrow at 5 AM"</string>
+ <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Features like Quick Share and Find My Device use Bluetooth"</string>
+ <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth will turn on tomorrow morning"</string>
+ <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Audio Sharing"</string>
+ <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="8626191139359072540">"Sharing Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index fc27f16..d2af8a6 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"See all"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatically turn on again tomorrow"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Features like Quick Share, Find My Device and device location use Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth will turn on tomorrow at 5.00 a.m."</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Record issue"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Start"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Stop"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Bug report"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"What part of your device experience was affected?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Select issue type"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Screen record"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index fc27f16..d2af8a6 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"See all"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatically turn on again tomorrow"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Features like Quick Share, Find My Device and device location use Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth will turn on tomorrow at 5.00 a.m."</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Record issue"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Start"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Stop"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Bug report"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"What part of your device experience was affected?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Select issue type"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Screen record"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 958c645..77ef52e 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -270,12 +270,15 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"See all"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio Sharing"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatically turn on again tomorrow"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Features like Quick Share, Find My Device, and device location use Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth will turn on tomorrow at 5 AM"</string>
+ <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Features like Quick Share and Find My Device use Bluetooth"</string>
+ <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth will turn on tomorrow morning"</string>
+ <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Audio Sharing"</string>
+ <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="8626191139359072540">"Sharing Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> battery"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index b625dc5..c33213a 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Ver todos"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Volver a activar automáticamente mañana"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Las funciones como Quick Share, Encontrar mi dispositivo y la ubicación del dispositivo usan Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Se activará el Bluetooth mañana a las 5 a.m."</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batería"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Grabar error"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Iniciar"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Detener"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Informe de errores"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"¿Qué parte de tu exp. del disp. se vio afectada?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Seleccionar tipo de problema"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Grabadora de pant."</string>
@@ -1303,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Retroiluminación del teclado"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Nivel %1$d de %2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Controles de la casa"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Accede a controles de la casa como prot. de pant."</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Accede rápidamente a controles de la casa como prot. de pantalla"</string>
</resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 583a181..1dc93fde 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Ver todos"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Volver a activar automáticamente mañana"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Las funciones como Quick Share y Encontrar mi dispositivo, y la ubicación del dispositivo usan Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"El Bluetooth se activará mañana a las 5:00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batería"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Problema de grabación"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Iniciar"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Detener"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Informe errores"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"¿Qué parte de tu experiencia se ha visto afectada?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Selecciona el tipo de problema"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Grabar pantalla"</string>
@@ -479,7 +486,7 @@
<string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Cuando compartes, grabas o envías una aplicación, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> puede acceder a todo lo que se muestre o se reproduzca en ella. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
<string name="media_projection_entry_app_permission_dialog_continue" msgid="295463518195075840">"Empezar"</string>
<string name="media_projection_entry_app_permission_dialog_single_app_disabled" msgid="8999903044874669995">"<xliff:g id="APP_NAME">%1$s</xliff:g> ha inhabilitado esta opción"</string>
- <string name="media_projection_entry_cast_permission_dialog_title" msgid="8860150223172993547">"¿Empezar a enviar contenido?"</string>
+ <string name="media_projection_entry_cast_permission_dialog_title" msgid="8860150223172993547">"¿Empezar a enviar?"</string>
<string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Cuando envías contenido, Android puede acceder a todo lo que se muestre en la pantalla o se reproduzca en tu dispositivo. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
<string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Cuando envías una aplicación, Android puede acceder a todo lo que se muestre o se reproduzca en ella. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
<string name="media_projection_entry_cast_permission_dialog_continue" msgid="7209890669948870042">"Empezar a enviar"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 190c002..fea407d 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Kuva kõik"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Kasuta Bluetoothi"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ühendatud"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvestatud"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"katkesta ühendus"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiveeri"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Lülita automaatselt homme uuesti sisse"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Funktsioonid, nagu Kiirjagamine, Leia mu seade ja seadme asukoht, kasutavad Bluetoothi"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth lülitatakse sisse homme kell viis hommikul"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> akut"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Heli"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Peakomplekt"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Probleemi salvestamine"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Alusta"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Peata"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Veaaruanne"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Millist seadme kasutuskogemuse osa see mõjutas?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Valige probleemi tüüp"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Ekraanisalvestus"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index b60df6f..41de1d4 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Ikusi guztiak"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Erabili Bluetootha"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Konektatuta"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gordeta"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"deskonektatu"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktibatu"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Aktibatu automatikoki berriro bihar"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Quick Share, Bilatu nire gailua, gailuaren kokapena eta beste eginbide batzuek Bluetootha darabilte"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetootha bihar 05:00etan aktibatuko da"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audioa"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Entzungailua"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Arazo bat dago grabaketarekin"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Hasi"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Gelditu"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Akatsen txostena"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Gailuaren erabileraren zer alderdiri eragin dio?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Hautatu arazo mota"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Pantaila-grabaketa"</string>
@@ -735,7 +742,7 @@
<string name="keyboard_key_numpad_template" msgid="7316338238459991821">"Zenbaki-teklatuko <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="notif_inline_reply_remove_attachment_description" msgid="7954075334095405429">"Kendu eranskina"</string>
<string name="keyboard_shortcut_group_system" msgid="1583416273777875970">"Sistema"</string>
- <string name="keyboard_shortcut_group_system_home" msgid="7465138628692109907">"Hasierako pantaila"</string>
+ <string name="keyboard_shortcut_group_system_home" msgid="7465138628692109907">"Orri nagusia"</string>
<string name="keyboard_shortcut_group_system_recents" msgid="8628108256824616927">"Azkenaldikoak"</string>
<string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Atzera"</string>
<string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Jakinarazpenak"</string>
@@ -1302,6 +1309,6 @@
<string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) aplikazioak erabili du duela gutxi"</string>
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Teklatuaren hondoko argia"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"%1$d/%2$d maila"</string>
- <string name="home_controls_dream_label" msgid="6567105701292324257">"Etxeko gailuak kontrolatzeko aukerak"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Atzitu etxeko gailuak kontrolatzeko aukerak pantaila-babesletik"</string>
+ <string name="home_controls_dream_label" msgid="6567105701292324257">"Etxeko gailuen kontrola"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Kontrolatu etxeko gailuak pantaila-babesletik"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 0c53fa0..8e72450 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"دیدن همه"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"استفاده از بلوتوث"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"متصل"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ذخیرهشده"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"قطع اتصال"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"فعال کردن"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"فردا دوباره بهطور خودکار روشن شود"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"ویژگیهایی مثل «همرسانی سریع»، «پیدا کردن دستگاهم»، و مکان دستگاه از بلوتوث استفاده میکنند"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"بلوتوث فردا ۵ ق.ظ روشن خواهد شد"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"شارژ باتری <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"صوت"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"هدست"</string>
@@ -1301,6 +1309,6 @@
<string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"اخیراً <xliff:g id="APP_NAME">%1$s</xliff:g> از آن استفاده کرده است (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"نور پسزمینه صفحهکلید"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"سطح %1$d از %2$d"</string>
- <string name="home_controls_dream_label" msgid="6567105701292324257">"کنترلهای لوازم خانگی"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"دسترسی سریع به کنترلهای لوازم خانگی بهعنوان محافظ صفحهنمایش"</string>
+ <string name="home_controls_dream_label" msgid="6567105701292324257">"کنترل خانه هوشمند"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"به کنترل خانه هوشمند بهعنوان محافظ صفحهنمایش دسترسی سریع دارید"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 3c90847..2935d2e 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Näytä kaikki"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Käytä Bluetoothia"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Yhdistetty"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Tallennettu"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"katkaise yhteys"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivoi"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Laita automaattisesti päälle taas huomenna"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Ominaisuudet (esim. Quick Share ja Paikanna laite) ja laitteen sijainti käyttävät Bluetoothia"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth käynnistetään huomenna klo 5.00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akun taso <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ääni"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Tallenna ongelma"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Aloita"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Lopeta"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Virheraportti"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Mitä osaa käyttökokemuksesta ongelma koski?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Valitse ongelman tyyppi"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Näytön tallentaja"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index cb56ba93..bd53d85 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Tout afficher"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Utiliser le Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connecté"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Enregistré"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"Déconnecter"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"Activer"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Activer le Bluetooth automatiquement demain"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Les fonctionnalités comme le Partage rapide, Localiser mon appareil et la position de l\'appareil utilisent le Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Le Bluetooth s\'activera demain à 5 h"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Pile : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Écouteurs"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Rapporter le problème"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Commencer"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Arrêter"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Rapport de bogue"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Quelle composante de l\'appareil a été affectée?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Sélectionner un type"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Enregistrement écran"</string>
@@ -1303,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Rétroéclairage du clavier"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Niveau %1$d de %2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Domotique"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Accès rapide : domot. sous forme d\'Écran de veille"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Accès rapide : domotique sous forme d\'Écran de veille"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 15235de..109e767 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Tout afficher"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Utiliser le Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connecté"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Enregistré"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"dissocier"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activer"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Réactiver automatiquement demain"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Certaines fonctionnalités telles que Quick Share, Localiser mon appareil ou encore la position de l\'appareil utilisent le Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Le Bluetooth sera activé demain à 5h00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batterie"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Casque"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Enregistrer le problème"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Début"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Arrêter"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Rapport de bug"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Quel problème avez-vous rencontré avec votre appareil ?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Sélectionnez un type de problème"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Enregistrement de l\'écran"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 7f4ae81..06c984a 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Ver todo"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Estableceuse a conexión"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gardouse"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Volver activar automaticamente mañá"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"As funcións como Quick Share, Localizar o meu dispositivo ou a de localización do dispositivo utilizan o Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"O Bluetooth activarase mañá ás 05:00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de batería"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Rexistrar problema"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Iniciar"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Deter"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Informe de erros"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Cal foi o problema na experiencia co dispositivo?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Selecciona o tipo de problema"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Gravación de pant."</string>
@@ -1303,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Retroiluminación do teclado"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Nivel %1$d de %2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Controis domóticos"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Controis domóticos como protector de pantalla"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Usa os controis domóticos como protector de pantalla"</string>
</resources>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 9425774..c9a5662 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"તમામ જુઓ"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"બ્લૂટૂથનો ઉપયોગ કરો"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"કનેક્ટેડ છે"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"સાચવેલું"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ડિસ્કનેક્ટ કરો"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"સક્રિય કરો"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"આવતીકાલે ફરીથી ઑટોમૅટિક રીતે ચાલુ કરો"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"ક્વિક શેર, Find My Device અને ડિવાઇસના લોકેશન જેવી સુવિધાઓ બ્લૂટૂથનો ઉપયોગ કરે છે"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"બ્લૂટૂથ આવતીકાલે સવારે 5 વાગ્યે ચાલુ થશે"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> બૅટરી"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ઑડિયો"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"હૅડસેટ"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index e4291ac..b2b2037 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"सभी देखें"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ब्लूटूथ इस्तेमाल करें"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"कनेक्ट है"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"सेव किया गया"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिसकनेक्ट करें"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"चालू करें"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"कल फिर से अपने-आप चालू हो जाएगा"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"क्विक शेयर, Find My Device, और डिवाइस की जगह की जानकारी का पता लगाने जैसी सुविधाएं, ब्लूटूथ का इस्तेमाल करती हैं"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"कल सुबह 5 बजे ब्लूटूथ चालू हो जाएगा"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> बैटरी"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ऑडियो"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
@@ -1191,7 +1199,7 @@
<string name="fgs_manager_footer_label" msgid="8276763570622288231">"{count,plural, =1{# ऐप्लिकेशन चालू है}one{# ऐप्लिकेशन चालू है}other{# ऐप्लिकेशन चालू हैं}}"</string>
<string name="fgs_dot_content_description" msgid="2865071539464777240">"नई जानकारी"</string>
<string name="fgs_manager_dialog_title" msgid="5879184257257718677">"ये ऐप्लिकेशन चालू हैं"</string>
- <string name="fgs_manager_dialog_message" msgid="2670045017200730076">"ये ऐप्लिकेशन चालू हैं और आपके इस्तेमाल न करने पर भी चल रहे हैं. इससे, ये बेहतर तरीके से फ़ंक्शन करते हैं. हालांकि, इससे बैटरी लाइफ़ पर भी असर पड़ सकता है."</string>
+ <string name="fgs_manager_dialog_message" msgid="2670045017200730076">"ये ऐप्लिकेशन चालू हैं और आपके इस्तेमाल न करने पर भी चल रहे हैं. इससे ये बेहतर तरीके से काम कर पाते हैं. हालांकि, बैटरी लाइफ़ पर इसका असर पड़ सकता है."</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"बंद करें"</string>
<string name="fgs_manager_app_item_stop_button_stopped_label" msgid="6950382004441263922">"बंद है"</string>
<string name="clipboard_edit_text_done" msgid="4551887727694022409">"हो गया"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index e0d2478..64c35b8 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -270,19 +270,27 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Pogledajte sve"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Spremljeno"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekini vezu"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviraj"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatski ponovo uključi sutra"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Značajke kao što su brzo dijeljenje, Pronađi moj uređaj i lokacija uređaja upotrebljavaju Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth će se uključiti sutra u 5:00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> baterije"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string>
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Unos"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušna pomagala"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključivanje…"</string>
- <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. zakretanje"</string>
+ <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatsko zakretanje"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko zakretanje zaslona"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Lokacija"</string>
<string name="quick_settings_screensaver_label" msgid="1495003469366524120">"Čuvar zaslona"</string>
@@ -1301,6 +1309,6 @@
<string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nedavno koristila aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Pozadinsko osvjetljenje tipkovnice"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Razina %1$d od %2$d"</string>
- <string name="home_controls_dream_label" msgid="6567105701292324257">"Upr. kuć. uređ."</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Brzo pristupajte Upr. kuć. uređ. kao čuvaru zasl."</string>
+ <string name="home_controls_dream_label" msgid="6567105701292324257">"Upravljanje uređajima"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Brzo upravljajte uređajima putem čuvara zaslona"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 7acfbe6..96c3e83 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Összes megtekintése"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth használata"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Csatlakozva"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Mentve"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"leválasztás"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiválás"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatikus visszakapcsolás holnap"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Az olyan funkciók, mint a Quick Share, a Készülékkereső és az eszköz helyadatai Bluetootht használnak"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"A Bluetooth bekapcsol holnap reggel 5-kor"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akkumulátor: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Hang"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Probléma rögzítése"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Indítás"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Leállítás"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Hibajelentés"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Az eszközhasználati élmény mely része érintett?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Problématípus kiválasztása"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Képernyőrögzítés"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 7dc33b6..00c3318 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Տեսնել բոլորը"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Միացնել Bluetooth-ը"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Միացված է"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Պահված է"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"անջատել"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ակտիվացնել"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Վաղը նորից ավտոմատ միացնել"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Գործառույթները, ինչպիսիք են Quick Share-ը, «Գտնել իմ սարքը» գործառույթը և սարքի տեղորոշումը, օգտագործում են Bluetooth-ը"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth-ը կմիանա վաղը՝ ժամը 05։00-ին"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Մարտկոցի լիցքը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Աուդիո"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ականջակալ"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Ձայնագրել"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Սկսել"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Կանգնեցնել"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Հաղորդում սխալի մասին"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Սարքի ո՞ր մասի հետ է կապված խնդիրը։"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Ընտրեք խնդրի տեսակը"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Էկրանի տեսագրում"</string>
@@ -1303,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Հետին լուսավորությամբ ստեղնաշար"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"%1$d՝ %2$d-ից"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Տան կառավարման տարրեր"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Օգտագործեք տան կառավարման տարրերը որպես էկրանապահ"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Տան կառավարման տարրերը դարձրեք էկրանապահ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 3da9a90..826fc1a4 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Lihat semua"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Gunakan Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Terhubung"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Disimpan"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"putuskan koneksi"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktifkan"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Otomatis aktifkan lagi besok"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Fitur seperti Quick Share, Temukan Perangkat Saya, dan lokasi perangkat menggunakan Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth akan diaktifkan besok pada pukul 05.00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index b400f8c..1a0b3f2 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Sjá allt"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Nota Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Tengt"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Vistað"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"aftengja"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"virkja"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Kveikja sjálfkrafa aftur á morgun"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Eiginleikar á borð við flýtideilingu, „Finna tækið mitt“ og staðsetningu tækis nota Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Kveikt verður á Bluetooth á morgun kl. 05:00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> rafhlöðuhleðsla"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Hljóð"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Höfuðtól"</string>
@@ -1302,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Baklýsing lyklaborðs"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Stig %1$d af %2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Heimastýringar"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Fáðu skjótan aðgang að heimastýringum með því að stilla þær sem skjávara"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Fáðu skjótan aðgang að heimastýringum sem skjávara"</string>
</resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index aa7f152..d7e5cdc 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Visualizza tutti"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Usa Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Dispositivo connesso"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Dispositivo salvato"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnetti"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"attiva"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Riattiva automaticamente domani"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Funzionalità come Quick Share, Trova il mio dispositivo e la posizione del dispositivo usano il Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Il Bluetooth verrà attivato domani alle 05:00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Batteria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auricolare"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Registra problema"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Avvia"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Interrompi"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Segnalazione di bug"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Quali problemi ha l\'esperienza del dispositivo?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Seleziona il tipo di problema"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Regis. dello schermo"</string>
@@ -1303,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Retroilluminazione della tastiera"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Livello %1$d di %2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Controlli della casa"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Accedi ai controlli della casa dal salvaschermo"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Accedi rapidamente ai controlli della casa dal salvaschermo"</string>
</resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index a804271..bed2fc7 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"הצגת הכול"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"מחובר"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"נשמר"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ניתוק"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"הפעלה"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"החיבור יופעל שוב אוטומטית מחר"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"תכונות כמו \'שיתוף מהיר\', \'איפה המכשיר שלי\' ומיקום המכשיר משתמשות בחיבור Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth יופעל מחר בשעה 5 בבוקר"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> סוללה"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"אודיו"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"אוזניות"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"תיעוד הבעיה"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"התחלה"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"עצירה"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"דיווח על באג"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"איזה חלק בחוויית השימוש שלך במכשיר הושפע?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"בחירה בסוג הבעיה"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"הקלטת המסך"</string>
@@ -479,10 +486,10 @@
<string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"בזמן שיתוף, הקלטה או העברה (cast) של אפליקציה, תהיה ל-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> גישה לכל מה שגלוי באפליקציה או מופעל מהאפליקציה. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
<string name="media_projection_entry_app_permission_dialog_continue" msgid="295463518195075840">"התחלה"</string>
<string name="media_projection_entry_app_permission_dialog_single_app_disabled" msgid="8999903044874669995">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> השביתה את האפשרות הזו"</string>
- <string name="media_projection_entry_cast_permission_dialog_title" msgid="8860150223172993547">"להתחיל את ההעברה?"</string>
+ <string name="media_projection_entry_cast_permission_dialog_title" msgid="8860150223172993547">"להפעיל Cast?"</string>
<string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"בזמן העברה (cast), תהיה ל-Android גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"בזמן העברה (cast) של אפליקציה, תהיה ל-Android גישה לכל מה שגלוי באפליקציה או מופעל מהאפליקציה. כדאי להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
- <string name="media_projection_entry_cast_permission_dialog_continue" msgid="7209890669948870042">"התחלת ההעברה (cast)"</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"בזמן Cast מאפליקציה, תהיה ל-Android גישה לכל מה שמופיע באפליקציה ולכל מדיה שפועלת בה. כדאי להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
+ <string name="media_projection_entry_cast_permission_dialog_continue" msgid="7209890669948870042">"הפעלת Cast"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"להתחיל את השיתוף?"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"בזמן שיתוף, הקלטה או העברה (cast) תהיה ל-Android גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"בזמן שיתוף, הקלטה או העברה (cast) של אפליקציה, תהיה ל-Android גישה לכל מה שגלוי באפליקציה או מופעל מהאפליקציה. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
@@ -1245,7 +1252,7 @@
<string name="home_quick_affordance_unavailable_configure_the_app" msgid="604424593994493281">"• יש לפחות מכשיר אחד או פאנל מכשיר אחד זמינים"</string>
<string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"צריך לבחור אפליקציית פתקים שתיפתח כברירת מחדל כשייעשה שימוש במקש הקיצור לכתיבת פתקים"</string>
<string name="keyguard_affordance_enablement_dialog_notes_app_action" msgid="6821710209675089470">"בחירת אפליקציה"</string>
- <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"מקש קיצור ללחיצה ארוכה"</string>
+ <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"צריך ללחוץ לחיצה ארוכה על הלחצן"</string>
<string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"ביטול"</string>
<string name="rear_display_bottom_sheet_confirm" msgid="1507591562761552899">"כן, אני רוצה להחליף בין המסכים"</string>
<string name="rear_display_folded_bottom_sheet_title" msgid="3930008746560711990">"פתיחת הטלפון"</string>
@@ -1302,6 +1309,6 @@
<string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"נעשה שימוש לאחרונה על ידי <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"התאורה האחורית במקלדת"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"רמה %1$d מתוך %2$d"</string>
- <string name="home_controls_dream_label" msgid="6567105701292324257">"ממשק השליטה במכשירים"</string>
+ <string name="home_controls_dream_label" msgid="6567105701292324257">"שליטה במכשירים"</string>
<string name="home_controls_dream_description" msgid="4644150952104035789">"גישה מהירה לממשק השליטה במכשירים כשומר מסך"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 6cd1dcb1..f403308 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -173,7 +173,7 @@
<string name="biometric_dialog_wrong_pin" msgid="1878539073972762803">"PIN が正しくありません"</string>
<string name="biometric_dialog_wrong_pattern" msgid="8954812279840889029">"パターンが正しくありません"</string>
<string name="biometric_dialog_wrong_password" msgid="69477929306843790">"パスワードが正しくありません"</string>
- <string name="biometric_dialog_credential_too_many_attempts" msgid="3083141271737748716">"間違えた回数が上限を超えました。\n<xliff:g id="NUMBER">%d</xliff:g> 秒後にもう一度お試しください。"</string>
+ <string name="biometric_dialog_credential_too_many_attempts" msgid="3083141271737748716">"試行回数が上限に達しました。\n<xliff:g id="NUMBER">%d</xliff:g> 秒後にもう一度お試しください。"</string>
<string name="work_challenge_emergency_button_text" msgid="8946588434515599288">"緊急通報"</string>
<string name="biometric_dialog_credential_attempts_before_wipe" msgid="6751859711975516999">"もう一度お試しください。入力回数: <xliff:g id="ATTEMPTS_0">%1$d</xliff:g>/<xliff:g id="MAX_ATTEMPTS">%2$d</xliff:g> 回"</string>
<string name="biometric_dialog_last_attempt_before_wipe_dialog_title" msgid="2874250099278693477">"データが削除されます"</string>
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"すべて表示"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth を使用"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"接続しました"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"保存しました"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"接続を解除"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"有効化"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"明日自動的に ON に戻す"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"クイック共有、デバイスを探す、デバイスの位置情報などの機能は Bluetooth を使用します"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"午前 5 時に Bluetooth が ON になります"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"バッテリー <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"オーディオ"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ヘッドセット"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"録音に関する問題"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"開始"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"停止"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"バグレポート"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"デバイスのどの部分が影響を受けましたか?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"問題の種類を選択する"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"スクリーン レコード"</string>
@@ -549,7 +556,7 @@
<string name="monitoring_description_parental_controls" msgid="8184693528917051626">"このデバイスは保護者によって管理されています。保護者は、あなたが使用するアプリ、あなたの現在地、デバイスの利用時間などの情報を確認したり、管理したりできます。"</string>
<string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string>
<string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"信頼エージェントがロック解除を管理"</string>
- <string name="kg_prompt_after_adaptive_auth_lock" msgid="2587481497846342760">"認証の試行回数が上限を超えたため、デバイスがロックされました"</string>
+ <string name="kg_prompt_after_adaptive_auth_lock" msgid="2587481497846342760">"認証の試行回数が上限に達したため、デバイスがロックされました"</string>
<string name="keyguard_indication_after_adaptive_auth_lock" msgid="2323400645470712787">"デバイスがロックされました\n認証に失敗しました"</string>
<string name="zen_mode_and_condition" msgid="5043165189511223718">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>。<xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
<string name="accessibility_volume_settings" msgid="1458961116951564784">"音声の設定"</string>
@@ -606,7 +613,7 @@
<string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"空間オーディオ"</string>
<string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"OFF"</string>
<string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"固定"</string>
- <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"ヘッド トラッキング"</string>
+ <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"ヘッド トラッキング"</string>
<string name="volume_ringer_change" msgid="3574969197796055532">"タップすると、着信音のモードを変更できます"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ミュート"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ミュートを解除"</string>
@@ -1303,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"キーボード バックライト"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"レベル %1$d/%2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"ホーム コントロール"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"ホーム コントロールにスクリーンセーバーとしてすばやくアクセス"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"ホーム コントロールにスクリーンセーバーからすばやくアクセス"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 15bb4fc..d9483ec 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"ყველას ნახვა"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-ის გამოყენება"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"დაკავშირებული"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"შენახული"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"კავშირის გაწყვეტა"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"გააქტიურება"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ხელახლა ავტომატურად ჩართვა ხვალ"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"ფუნქციები, როგორებიცაა „სწრაფი გაზიარება“, „ჩემი მოწყობილობის პოვნა“ და „მოწყობილობის მდებარეობა“ იყენებს Bluetooth-ს"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth ჩაირთვება ხვალ დილის 5 საათზე"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ბატარეა"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"აუდიო"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ყურსაცვამი"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"ჩაწერასთან დაკავშირებული პრობლემა"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"დაწყება"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"გაჩერება"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"სისტემის ხარვეზის ანგარიში"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"თქვენი მოწყობილობის გამოცდილების რა ნაწილზე მოხდა ზეგავლენა?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"აირჩიეთ პრობლემის ტიპი"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"ეკრანის ჩანაწერი"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 835a0ca..36ae88a 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Барлығын көру"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-ты пайдалану"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Қосылды"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сақталды"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ажырату"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"іске қосу"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Ертең автоматты түрде қосылсын"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Quick Share, Find My Device сияқты функциялар мен құрылғы локациясы Bluetooth пайдаланады."</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth ертең таңғы сағат 5-те қосылады."</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батарея деңгейі: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Aудио"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Ақауды жазу"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Бастау"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Тоқтату"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Қате туралы есеп"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Құрылғы қызметінің қандай түріне әсер етті?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Мәселе түрін таңдаңыз."</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Экранды жазу"</string>
@@ -604,7 +611,7 @@
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Дыбысын өшіру үшін түртіңіз."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Шуды реттеу"</string>
<string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Кеңістіктік дыбыс"</string>
- <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Өшіру"</string>
+ <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Өшірілген"</string>
<string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Бекітілген"</string>
<string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Бас қимылын қадағалау"</string>
<string name="volume_ringer_change" msgid="3574969197796055532">"Қоңырау режимін өзгерту үшін түртіңіз."</string>
@@ -1303,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Пернетақта жарығы"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Деңгей: %1$d/%2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Үй басқару элементтері"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Үй басқару элементтерін скринсейвер ретінде жылдам қолдану"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Үй басқару элементтерін скринсейверден қолдану"</string>
</resources>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index d56c20d..c7868db 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"មើលទាំងអស់"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ប្រើប៊្លូធូស"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"បានភ្ជាប់"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"បានរក្សាទុក"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ផ្ដាច់"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"បើកដំណើរការ"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"បើកដោយស្វ័យប្រវត្តិម្ដងទៀតនៅថ្ងៃស្អែក"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"មុខងារដូចជា Quick Share, រកឧបករណ៍របស់ខ្ញុំ និងប៊្លូធូសប្រើប្រាស់ទីតាំងឧបករណ៍"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"ប៊្លូធូសនឹងបើកនៅថ្ងៃស្អែកនៅម៉ោង 5 ព្រឹក"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"ថ្ម <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"សំឡេង"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"កាស"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 23bde22..42d655e 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -237,7 +237,7 @@
<string name="accessibility_desc_qs_notification_shade" msgid="8327226953072700376">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್ಗಳು ಮತ್ತು ಅಧಿಸೂಚನೆಯ ಪರದೆ."</string>
<string name="accessibility_desc_lock_screen" msgid="5983125095181194887">"ಲಾಕ್ ಸ್ಕ್ರೀನ್."</string>
<string name="accessibility_desc_work_lock" msgid="4355620395354680575">"ಕೆಲಸದ ಲಾಕ್ ಪರದೆ"</string>
- <string name="accessibility_desc_close" msgid="8293708213442107755">"ಮುಚ್ಚು"</string>
+ <string name="accessibility_desc_close" msgid="8293708213442107755">"ಮುಚ್ಚಿ"</string>
<string name="accessibility_quick_settings_dnd_none_on" msgid="3235552940146035383">"ಸಂಪೂರ್ಣ ನಿಶ್ಯಬ್ಧ"</string>
<string name="accessibility_quick_settings_dnd_alarms_on" msgid="3375848309132140014">"ಅಲಾರಮ್ಗಳು ಮಾತ್ರ"</string>
<string name="accessibility_quick_settings_dnd" msgid="2415967452264206047">"ಅಡಚಣೆ ಮಾಡಬೇಡಿ."</string>
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"ಎಲ್ಲವನ್ನೂ ನೋಡಿ"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ಬ್ಲೂಟೂತ್ ಬಳಸಿ"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ಡಿಸ್ಕನೆಕ್ಟ್ ಮಾಡಿ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ನಾಳೆ ಪುನಃ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಆನ್ ಮಾಡಿ"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"ಕ್ವಿಕ್ ಶೇರ್, Find My Device ನಂತಹ ಫೀಚರ್ಗಳು ಹಾಗೂ ಸಾಧನದ ಸ್ಥಳವು ಬ್ಲೂಟೂತ್ ಅನ್ನು ಬಳಸುತ್ತವೆ"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"ಬ್ಲೂಟೂತ್ ನಾಳೆ ಬೆಳಗ್ಗೆ 5 ಗಂಟೆಗೆ ಆನ್ ಆಗುತ್ತದೆ"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ಬ್ಯಾಟರಿ"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ಆಡಿಯೋ"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ಹೆಡ್ಸೆಟ್"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"ರೆಕಾರ್ಡ್ ದೋಷ"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"ಪ್ರಾರಂಭಿಸಿ"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"ನಿಲ್ಲಿಸಿ"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"ಬಗ್ ವರದಿ ಮಾಡುವಿಕೆ"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"ಸಾಧನ ಬಳಸುವಾಗ ನೀವು ಯಾವ ರೀತಿಯ ಸಮಸ್ಯೆ ಎದುರಿಸುತ್ತೀರಿ?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"ಸಮಸ್ಯೆಯ ಪ್ರಕಾರವನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡ್"</string>
@@ -367,7 +374,7 @@
<string name="quick_settings_contrast_high" msgid="656049259587494499">"ಹೆಚ್ಚು"</string>
<string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"ಹಿಯರಿಂಗ್ ಸಾಧನಗಳು"</string>
<string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"ಹಿಯರಿಂಗ್ ಸಾಧನಗಳು"</string>
- <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"ಹೊಸ ಸಾಧನವನ್ನು ಜೋಡಿಸಿ"</string>
+ <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"ಹೊಸ ಸಾಧನವನ್ನು ಪೇರ್ ಮಾಡಿ"</string>
<string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"ಹೊಸ ಸಾಧನವನ್ನು ಜೋಡಿಸಲು ಕ್ಲಿಕ್ ಮಾಡಿ"</string>
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ಸಾಧನದ ಮೈಕ್ರೋಫೋನ್ ನಿರ್ಬಂಧವನ್ನು ತೆಗೆಯಬೇಕೆ?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"ಸಾಧನದ ಕ್ಯಾಮರಾ ನಿರ್ಬಂಧವನ್ನು ತೆಗೆಯಬೇಕೆ?"</string>
@@ -1302,6 +1309,6 @@
<string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"ಇತ್ತೀಚೆಗೆ <xliff:g id="APP_NAME">%1$s</xliff:g> ಇದನ್ನು ಬಳಸಿದೆ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"ಕೀಬೋರ್ಡ್ ಬ್ಯಾಕ್ಲೈಟ್"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$d ರಲ್ಲಿ %1$d ಮಟ್ಟ"</string>
- <string name="home_controls_dream_label" msgid="6567105701292324257">"ಹೋಮ್ ನಿಯಂತ್ರಣಗಳು"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"ಹೋಮ್ ನಿಯಂತ್ರಣವನ್ನು ಸ್ಕ್ರೀನ್ಸೇವರ್ನಂತೆ ತ್ವರಿತವಾಗಿ ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಿ"</string>
+ <string name="home_controls_dream_label" msgid="6567105701292324257">"ಮನೆ ನಿಯಂತ್ರಣಗಳು"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"ಮನೆ ನಿಯಂತ್ರಣವನ್ನು ಸ್ಕ್ರೀನ್ಸೇವರ್ನಂತೆ ಬೇಗ ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಿ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 8f82465..3c34f8d 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"모두 보기"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"블루투스 사용"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"연결됨"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"저장됨"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"연결 해제"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"실행"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"내일 다시 자동으로 사용 설정"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Quick Share, 내 기기 찾기, 기기 위치 등의 기능에서 블루투스를 사용합니다."</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"블루투스가 내일 오전 5시에 켜집니다."</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"배터리 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"오디오"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"헤드셋"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"문제 기록"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"시작"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"중지"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"버그 신고"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"기기 경험의 어떤 부분에 영향이 있었나요?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"문제 유형 선택"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"화면 녹화"</string>
@@ -1303,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"키보드 백라이트"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$d단계 중 %1$d단계"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"홈 컨트롤"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"화면 보호기로 홈 컨트롤에 빠르게 액세스합니다."</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"화면 보호기로 홈 컨트롤에 빠르게 액세스하기"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 40a427c..ad9d2f7 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Баарын көрүү"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Иштетүү"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Туташты"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сакталды"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ажыратуу"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"иштетүү"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Эртең автоматтык түрдө кайра күйгүзүү"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Тез Бөлүшүү, \"Түзмөгүм кайда?\" жана түзмөктүн турган жерин аныктоо сыяктуу функциялар Bluetooth\'ду колдонот"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth эртең саат 05:00 күйөт"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батареянын деңгээли <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index f7dfc7e..eb9e1eb 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"ເບິ່ງທັງໝົດ"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ໃຊ້ Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ເຊື່ອມຕໍ່ແລ້ວ"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ບັນທຶກແລ້ວ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ຕັດການເຊື່ອມຕໍ່"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ເປີດນຳໃຊ້"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ເປີດໃຊ້ໂດຍອັດຕະໂນມັດອີກຄັ້ງມື້ອື່ນ"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"ຄຸນສົມບັດຕ່າງໆ ເຊັ່ນ: ການແຊຣ໌ດ່ວນ, ຊອກຫາອຸປະກອນຂອງຂ້ອຍ ແລະ ສະຖານທີ່ຂອງອຸປະກອນແມ່ນໃຊ້ Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth ຈະເປີດມື້ອື່ນເວລາ 05:00 ໂມງ"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"ແບັດເຕີຣີ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ສຽງ"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ຊຸດຫູຟັງ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 734daf8..63971ab 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Žiūrėti viską"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"„Bluetooth“ naudojimas"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Prisijungta"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Išsaugota"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"atjungti"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"suaktyvinti"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatiškai vėl įjungti rytoj"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Tokioms funkcijoms kaip „Spartusis bendrinimas“, „Rasti įrenginį“ ir įrenginio vietovė naudojamas „Bluetooth“ ryšys"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"„Bluetooth“ bus įjungtas rytoj, 5 val."</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akumuliatorius: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Garsas"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Virtualiosios realybės įrenginys"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Įrašyti problemą"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Pradėti"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Stabdyti"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Pranešimas apie riktą"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Kuri įrenginio funkcija buvo paveikta?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Pasirinkite problemos tipą"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Ekrano įrašas"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 74a9239..4b7507d 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Skatīt visas"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Izmantot Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Savienojums izveidots"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saglabāta"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"atvienot"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivizēt"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automātiski atkal ieslēgt rīt"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Tādas funkcijas kā “Ātrā kopīgošana”, “Atrast ierīci” un ierīces atrašanās vietas noteikšana izmanto tehnoloģiju Bluetooth."</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth tiks ieslēgts rīt plkst. 5:00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Akumulators: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Austiņas"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Problēmas ierakstīšana"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Sākt"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Apturēt"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Kļūdas pārskats"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Kuras ierīces funkcijas tika ietekmētas?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Atlasiet problēmas veidu"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Ekrāna ierakstīšana"</string>
@@ -604,7 +611,7 @@
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Pieskarieties, lai izslēgtu skaņu."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Trokšņu kontrole"</string>
<string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Telpiskais audio"</string>
- <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Izslēgts"</string>
+ <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Izslēgta"</string>
<string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Fiksēts"</string>
<string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Seko galvai"</string>
<string name="volume_ringer_change" msgid="3574969197796055532">"Pieskarieties, lai mainītu zvanītāja režīmu."</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 36d49e4..ba29ee6 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Прикажи ги сите"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Користи Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Поврзано"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Зачувано"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекини врска"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активирај"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Автоматски вклучи повторно утре"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Функциите како „Брзо споделување“, „Најди го мојот уред“ и локација на уредот користат Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth ќе се вклучи утре во 5:00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Батерија: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалки"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Евидентирајте проблем"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Започнете"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Сопрете"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Извештај за грешка"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Кој дел од доживувањето на уредот беше засегнат?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Изберете тип проблем"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Снимање екран"</string>
@@ -1303,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Осветлување на тастатура"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Ниво %1$d од %2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Контроли за домот"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Брзо прист. до контр. за дом. како штедач на екран"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Контролите за домот како штедач на екран"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index b20faa9..8e9392c 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"എല്ലാം കാണുക"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth ഉപയോഗിക്കുക"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"കണക്റ്റ് ചെയ്തു"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"സംരക്ഷിച്ചു"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"വിച്ഛേദിക്കുക"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"സജീവമാക്കുക"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"നാളെ വീണ്ടും സ്വയമേവ ഓണാക്കുക"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"ക്വിക്ക് ഷെയർ, Find My Device, ഉപകരണ ലൊക്കേഷൻ എന്നിവ പോലുള്ള ഫീച്ചറുകൾ Bluetooth ഉപയോഗിക്കുന്നു"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth നാളെ 5 AM-ന് ഓണാക്കും"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ബാറ്ററി"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ഓഡിയോ"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ഹെഡ്സെറ്റ്"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 333ab55..aab22b3 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Бүгдийг харах"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-г ашиглах"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Холбогдсон"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Хадгалсан"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"салгах"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"идэвхжүүлэх"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Маргааш автоматаар дахин асаах"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Түргэн хуваалцах, Миний төхөөрөмжийг олох зэрэг онцлогууд болон төхөөрөмжийн байршил Bluetooth-г ашигладаг"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth маргааш ҮӨ 5 цагт асна"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> батарей"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Чихэвч"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Асуудлыг бичих"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Эхлүүлэх"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Зогсоох"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Алдааны мэдээ"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Таны төхөөрөмжийн хэрэглээний аль хэсэгт нөлөөлсөн бэ?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Асуудлын төрөл сонгоно уу"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Дэлгэцийн бичлэг"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 5bed1b4..071935a 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"सर्व पहा"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ब्लूटूथ वापरा"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"कनेक्ट केले"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"सेव्ह केले"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिस्कनेक्ट करा"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ॲक्टिव्हेट करा"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"उद्या पुन्हा आपोआप सुरू करा"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"क्विक शेअर, Find My Device आणि डिव्हाइसचे स्थान यांसारखी वैशिष्ट्ये ब्लूटूथ वापरतात"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"ब्लूटूथ उद्या सकाळी ५ वाजता सुरू होईल"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> बॅटरी"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ऑडिओ"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"समस्या रेकॉर्ड करा"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"सुरुवात करा"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"थांबवा"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"बग रिपोर्ट"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"तुमच्या डिव्हाइसबाबत कोणत्या अनुभवावर परिणाम झाला?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"समस्येचा प्रकार निवडा"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"स्क्रीन रेकॉर्ड"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 4df6540..95d4237 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Lihat semua"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Gunakan Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Disambungkan"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Disimpan"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"putuskan sambungan"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktifkan"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Dihidupkan sekali lagi esok secara automatik"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Ciri seperti Quick Share, Find My Device dan lokasi peranti menggunakan Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth akan dihidupkan esok pada pukul 5 PG"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> bateri"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Set Kepala"</string>
@@ -1302,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Cahaya latar papan kekunci"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Tahap %1$d daripada %2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Kawalan Rumah"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Akses kawalan rumah anda sebagai penyelamat skrin dengan cepat"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Jadikan kawalan rumah anda sebagai penyelamat skrin"</string>
</resources>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index a1894b0..7941935 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"အားလုံးကြည့်ရန်"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ဘလူးတုသ်သုံးရန်"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ချိတ်ဆက်ထားသည်"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"သိမ်းထားသည်"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ချိတ်ဆက်မှုဖြုတ်ရန်"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"စသုံးရန်"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"မနက်ဖြန် အလိုအလျောက် ထပ်ဖွင့်ရန်"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"‘အမြန် မျှဝေပါ’၊ Find My Device နှင့် စက်ပစ္စည်းတည်နေရာကဲ့သို့ တူးလ်များသည် ဘလူးတုသ်သုံးသည်"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"မနက်ဖြန် မနက် ၅ နာရီတွင် ဘလူးတုသ်ကို ဖွင့်ပါမည်"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ဘက်ထရီ"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"အသံ"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"မိုက်ခွက်ပါနားကြပ်"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"ပြဿနာကို မှတ်တမ်းတင်ခြင်း"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"စတင်ပါ"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"ရပ်ပါ"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"ချွတ်ယွင်းမှု အစီရင်ခံစာ"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"စက်အသုံးပြုမှု၏ မည်သည့်အပိုင်းကို သက်ရောက်သလဲ။"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"ပြဿနာအမျိုးအစား ရွေးရန်"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"ဖန်သားပြင်ရိုက်ကူးရန်"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 5039150d..59afb3e 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Se alle"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bruk Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Tilkoblet"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Lagret"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"koble fra"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiver"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Slå på igjen i morgen automatisk"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Funksjoner som Quick Share, Finn enheten min og enhetsposisjon bruker Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth slås på i morgen kl. 05.00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Lyd"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Hodetelefoner"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Registrer problem"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Start"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Stopp"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Feilrapport"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Hvilken del av enhetsopplevelsen din ble påvirket?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Velg problemtype"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Skjermopptak"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 28c7b05..3ab647f 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"सबै डिभाइसहरू हेर्नुहोस्"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ब्लुटुथ प्रयोग गर्नुहोस्"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"कनेक्ट गरिएको छ"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"सेभ गरिएको छ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिस्कनेक्ट गर्नुहोस्"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"एक्टिभेट गर्नुहोस्"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"भोलि फेरि स्वतः अन गर्नुहोस्"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"क्विक सेयर, Find My Device र डिभाइसको लोकेसन जस्ता सुविधाहरूले ब्लुटुथ प्रयोग गर्छन्"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"ब्लुटुथ भोलि बिहान ५ बजे अन हुने छ"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ब्याट्री"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"अडियो"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index d0500c0..e85a1da 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Alles tonen"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth gebruiken"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Verbonden"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Opgeslagen"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"loskoppelen"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activeren"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Morgen weer automatisch aanzetten"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Functies zoals Quick Share, Vind mijn apparaat en apparaatlocatie maken gebruik van bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth gaat morgen om 05:00 uur aan"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batterijniveau"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Probleem vastleggen"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Starten"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Stoppen"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Bugrapport"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Op welk onderdeel van de apparaatfunctionaliteit had dit effect?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Probleemtype selecteren"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Schermopname"</string>
@@ -606,7 +613,7 @@
<string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Ruimtelijke audio"</string>
<string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Uit"</string>
<string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Vast"</string>
- <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Hoofdbeweging volgen"</string>
+ <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Hoofdbeweging volgen"</string>
<string name="volume_ringer_change" msgid="3574969197796055532">"Tik om de beltoonmodus te wijzigen"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"geluid uit"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"geluid aanzetten"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index eb6865e..77ed062 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"ସବୁ ଦେଖନ୍ତୁ"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ବ୍ଲୁଟୁଥ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"କନେକ୍ଟ କରାଯାଇଛି"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ସେଭ କରାଯାଇଛି"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ଡିସକନେକ୍ଟ କରନ୍ତୁ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ଚାଲୁ କରନ୍ତୁ"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ଆସନ୍ତାକାଲି ସ୍ୱତଃ ପୁଣି ଚାଲୁ ହେବ"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Quick Share, Find My Device ଏବଂ ଡିଭାଇସ ଲୋକେସନ ପରି ଫିଚରଗୁଡ଼ିକ ବ୍ଲୁଟୁଥ ବ୍ୟବହାର କରେ"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"ବ୍ଲୁଟୁଥ ଆସନ୍ତାକାଲି 5 AMରେ ଚାଲୁ ହେବ"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ବ୍ୟାଟେରୀ"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ଅଡିଓ"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ହେଡସେଟ୍"</string>
@@ -1302,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"କୀବୋର୍ଡ ବେକଲାଇଟ"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$dରୁ %1$d ନମ୍ବର ଲେଭେଲ"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"ହୋମ କଣ୍ଟ୍ରୋଲ୍ସ"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"ସ୍କ୍ରିନସେଭର ଭାବେ ହୋମ କଣ୍ଟ୍ରୋଲ୍ସକୁ ଶୀଘ୍ର ଆକ୍ସେସ କର"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"ସ୍କ୍ରିନସେଭର ଭାବେ ହୋମ କଣ୍ଟ୍ରୋଲ୍ସକୁ ଶୀଘ୍ର ଆକ୍ସେସ କରନ୍ତୁ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 3066c1d..0cc753b 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"ਸਭ ਦੇਖੋ"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ਬਲੂਟੁੱਥ ਵਰਤੋ"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ਕਨੈਕਟ ਹੈ"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ਡਿਸਕਨੈਕਟ ਕਰੋ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ਕਿਰਿਆਸ਼ੀਲ ਕਰੋ"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ਕੱਲ੍ਹ ਨੂੰ ਆਪਣੇ ਆਪ ਚਾਲੂ ਹੋ ਜਾਵੇਗਾ"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"ਕਵਿੱਕ ਸ਼ੇਅਰ, Find My Device ਅਤੇ ਡੀਵਾਈਸ ਦਾ ਟਿਕਾਣਾ ਵਰਗੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਬਲੂਟੁੱਥ ਦੀ ਵਰਤੋਂ ਕਰਦੀਆਂ ਹਨ"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"ਬਲੂਟੁੱਥ ਕੱਲ੍ਹ ਸਵੇਰੇ 5 ਵਜੇ ਚਾਲੂ ਹੋਵੇਗਾ"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ਬੈਟਰੀ"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ਆਡੀਓ"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ਹੈੱਡਸੈੱਟ"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"ਸਮੱਸਿਆ ਰਿਕਾਰਡ ਕਰੋ"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"ਸ਼ੁਰੂ ਕਰੋ"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"ਬੰਦ ਕਰੋ"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"ਬੱਗ ਰਿਪੋਰਟ"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀ ਕਿਹੜੀ ਸੁਵਿਧਾ ਪ੍ਰਭਾਵਿਤ ਹੋਈ ਸੀ?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"ਸਮੱਸਿਆ ਦੀ ਕਿਸਮ ਚੁਣੋ"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 6ff18b9..814d321 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -270,12 +270,15 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Pokaż wszystkie"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Używaj Bluetootha"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Połączone"</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Udostępnianie dźwięku"</string>
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Zapisane"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"rozłącz"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktywuj"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatycznie włącz ponownie jutro"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Funkcje takie jak szybkie udostępnianie, Znajdź moje urządzenie czy lokalizacja urządzenia używają Bluetootha"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth włączy się jutro o 5 rano"</string>
+ <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Funkcje takie jak szybkie udostępnianie czy Znajdź moje urządzenie korzystają z Bluetootha"</string>
+ <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth włączy się jutro rano"</string>
+ <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Udostępnianie dźwięku"</string>
+ <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="8626191139359072540">"Udostępniam dźwięk"</string>
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> naładowania baterii"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Dźwięk"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Zestaw słuchawkowy"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 4d8156b..17fde25 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Mostrar tudo"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvo"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Ativar automaticamente de novo amanhã"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Recursos como o Quick Share, o Encontre Meu Dispositivo e a localização do dispositivo usam o Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"O Bluetooth será ativado amanhã às 5h"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Fone de ouvido"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Problema na gravação"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Iniciar"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Parar"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Relatório do bug"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Que parte da sua experiência no dispositivo foi afetada?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Selecionar tipo de problema"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Gravação de tela"</string>
@@ -1303,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Luz de fundo do teclado"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Nível %1$d de %2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Automação residencial"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Acesse rapidamente a automação residencial como um protetor de tela"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Controles de automação residencial no protetor de tela"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 3c7578f..dfb7695 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Ver tudo"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ligado"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desassociar"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Reativar amanhã automaticamente"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"As funcionalidades como Partilha rápida, Localizar o meu dispositivo e localização do dispositivo usam o Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"O Bluetooth vai ser ativado amanhã às 05:00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> de bateria"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ausc. c/ mic. integ."</string>
@@ -1302,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Luz do teclado"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Nível %1$d de %2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Controlos domésticos"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Aceda rapid. aos contr. domést. como prot. de ecrã"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Use controlos domésticos como proteção de ecrã"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 4d8156b..17fde25 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Mostrar tudo"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvo"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Ativar automaticamente de novo amanhã"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Recursos como o Quick Share, o Encontre Meu Dispositivo e a localização do dispositivo usam o Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"O Bluetooth será ativado amanhã às 5h"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Fone de ouvido"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Problema na gravação"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Iniciar"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Parar"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Relatório do bug"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Que parte da sua experiência no dispositivo foi afetada?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Selecionar tipo de problema"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Gravação de tela"</string>
@@ -1303,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Luz de fundo do teclado"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Nível %1$d de %2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Automação residencial"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Acesse rapidamente a automação residencial como um protetor de tela"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Controles de automação residencial no protetor de tela"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 172ee12..2af8013 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Afișează tot"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Folosește Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectat"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvat"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"deconectează"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activează"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Activează din nou automat mâine"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Funcții precum Quick Share, Găsește-mi dispozitivul și locația dispozitivului folosesc Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth se va activa mâine la 5 dimineața"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Nivelul bateriei: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Căști"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Problemă legată de înregistrare"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Începe"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Oprește"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Raport de eroare"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Ce parte a experienței pe dispozitiv a fost afectată?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Selectează tipul problemei"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Înregistrarea ecranului"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 249be7fe..16fe331 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Все"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Использовать"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Подключено"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сохранено"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"отключить"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активировать"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Включить завтра автоматически"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Bluetooth используется в сервисе \"Найти устройство\", таких функциях, как Быстрая отправка, и при определении местоположения устройства"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth будет включен завтра в 05:00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудиоустройство"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
@@ -1302,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Подсветка клавиатуры"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Уровень %1$d из %2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Управление домом"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Добавьте настройки умного дома на заставку"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Быстрый доступ к управлению домом через заставку"</string>
</resources>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 90c87f0..458d9ee 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"සියල්ල බලන්න"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"බ්ලූටූත් භාවිතා කරන්න"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"සම්බන්ධිතයි"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"සුරැකිණි"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"විසන්ධි කරන්න"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"සක්රිය කරන්න"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"හෙට ස්වයංක්රීයව නැවත ක්රියාත්මක කරන්න"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"ඉක්මන් බෙදා ගැනීම, මගේ උපාංගය සෙවීම, සහ උපාංග ස්ථානය වැනි විශේෂාංග බ්ලූටූත් භාවිත කරයි"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"බ්ලූටූත් හෙට පෙ.ව. 5ට ක්රියාත්මක වනු ඇත"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"බැටරිය <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ශ්රව්ය"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"හෙඩ්සෙටය"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"පටිගත කිරීමේ ගැටලුව"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"අරඹන්න"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"නවත්වන්න"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"දෝෂ වර්තාව"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"ඔබේ උපාංග අත්දැකීමේ කුමන කොටසට බලපෑවේ ද?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"ගැටලු වර්ගය තෝරන්න"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"තිර පටිගත කිරීම"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 7751c6d..c8c2ee5 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Zobraziť všetko"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Použiť Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Pripojené"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uložené"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"odpojiť"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivovať"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automaticky zajtra znova zapnúť"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Funkcie, ako sú Quick Share, Nájdi moje zariadenie a poloha zariadenia, používajú Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth sa zapne zajtra o 5:00."</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Batéria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Náhlavná súprava"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 53be1bd..0302199 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Pokaži vse"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Uporabi Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Shranjeno"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekinitev povezave"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviranje"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Samodejno znova vklopi jutri"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Funkcije, kot so Hitro deljenje, Poišči mojo napravo in zaznavanje lokacije naprave, uporabljajo Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth se bo vklopil jutri ob 5. uri"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterija na <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvok"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalke z mikrofonom"</string>
@@ -1302,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Osvetlitev tipkovnice"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Stopnja %1$d od %2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Kontrolniki za dom"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Hiter dostop do kontrol. za dom na ohranj. zaslona"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Hiter dostop do kontrolnikov za dom na ohranjevalniku zaslona"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 7e75984..8e621ce 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Shiko të gjitha"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Përdor Bluetooth-in"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Lidhur"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Ruajtur"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"shkëput"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivizo"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Aktivizoje automatikisht nesër"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Veçoritë si \"Ndarja e shpejtë\", \"Gjej pajisjen time\" dhe vendndodhja e pajisjes përdorin Bluetooth-in"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth-i do të aktivizohet nesër në 5:00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> bateri"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Kufje me mikrofon"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Regjistro problemin"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Nis"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Ndalo"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Raporti i defekteve në kod"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Cila pjesë e përvojës me pajisjen është prekur?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Zgjidh llojin e problemit"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Regjistrim i ekranit"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 2b1882d..a1825bf 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Прикажи све"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Користи Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Повезано"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сачувано"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекините везу"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активирајте"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Аутоматски поново укључи сутра"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Функције као што су Quick Share, Пронађи мој уређај и локација уређаја користе Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth ће се укључити сутра у 5:00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Ниво батерије је <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалице"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index f075c8c..abb5d09 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Se alla"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Använd Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ansluten"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sparad"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"koppla från"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivera"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Aktivera automatiskt igen i morgon"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Funktioner som Snabbdelning, Hitta min enhet och enhetens plats använder Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth aktiveras i morgon kl. 5.00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> batteri"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ljud"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Registrera problem"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Starta"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Stoppa"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Felrapport"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Vilken enhetsupplevelse påverkades?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Välj problemtyp"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Skärminspelning"</string>
@@ -1303,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Bakgrundsbelysning för tangentbord"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Nivå %1$d av %2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Hemstyrning"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Kom snabbt åt hemstyrningen som en skärmsläckare"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Kom snabbt åt hemstyrningen via skärmsläckaren"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 102926b..392a74d 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Angalia vyote"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Tumia Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Imeunganishwa"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Imehifadhiwa"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ondoa"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"anza kutumia"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Iwashe tena kesho kiotomatiki"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Vipengele kama vile Kutuma Haraka, Tafuta Kifaa Changu na mahali kifaa kilipo hutumia Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth itawaka kesho saa 11 alfajiri"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Chaji ya betri ni <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Sauti"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Vifaa vya sauti"</string>
@@ -1301,6 +1309,6 @@
<string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Ilitumiwa hivi majuzi na <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Mwanga chini ya kibodi"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Kiwango cha %1$d kati ya %2$d"</string>
- <string name="home_controls_dream_label" msgid="6567105701292324257">"Vidhibiti vya Vifaa Nyumbani"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Fikia haraka vidhibiti vya vifaa nyumbani kama taswira ya skrini"</string>
+ <string name="home_controls_dream_label" msgid="6567105701292324257">"Dhibiti Vifaa Nyumbani"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Fikia haraka vidhibiti vya vifaa nyumbani vikiwa taswira ya skrini"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 9afa0d1..dfc00df 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"அனைத்தையும் காட்டு"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"புளூடூத்தைப் பயன்படுத்துதல்"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"இணைக்கப்பட்டது"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"சேமிக்கப்பட்டது"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"இணைப்பு நீக்கும்"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"செயல்படுத்தும்"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"நாளைக்குத் தானாகவே மீண்டும் இயக்கப்படும்"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"விரைவுப் பகிர்தல், Find My Device போன்ற அம்சங்களும் சாதன இருப்பிடமும் புளூடூத்தைப் பயன்படுத்துகின்றன"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"நாளை 5 AMக்கு புளூடூத் ஆன் ஆகும்"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> பேட்டரி"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ஆடியோ"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ஹெட்செட்"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"சிக்கலை ரெக்கார்டு செய்தல்"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"தொடங்குங்கள்"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"நிறுத்துங்கள்"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"பிழை அறிக்கை"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"சாதன அனுபவத்தின் எந்தப் பகுதி பாதிக்கப்பட்டது?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"சிக்கல் வகையைத் தேர்வுசெய்க"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"ஸ்கிரீன் ரெக்கார்டு"</string>
@@ -1303,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"கீபோர்டு பேக்லைட்"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"நிலை, %2$d இல் %1$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"ஹோம் கன்ட்ரோல்கள்"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"ஹோம் கன்ட்ரோல்களை ஸ்கிரீன் சேவராக விரைவாக அணுகலாம்"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"ஹோம் கன்ட்ரோல்களை ஸ்கிரீன் சேவராக அணுகலாம்"</string>
</resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 7f82ebf..518883f 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"అన్నింటినీ చూడండి"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"బ్లూటూత్ వాడండి"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"కనెక్ట్ అయింది"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"సేవ్ చేయబడింది"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"డిస్కనెక్ట్ చేయండి"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"యాక్టివేట్ చేయండి"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"రేపు మళ్లీ ఆటోమేటిక్గా ఆన్ చేస్తుంది"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"క్విక్ షేర్, Find My Device, పరికర లొకేషన్ వంటి ఫీచర్లు బ్లూటూత్ను ఉపయోగిస్తాయి"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"బ్లూటూత్ రేపు ఉదయం 5 గంటలకు ఆన్ అవుతుంది"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> బ్యాటరీ"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ఆడియో"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"హెడ్సెట్"</string>
@@ -1302,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"కీబోర్డ్ బ్యాక్లైట్"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"%2$dలో %1$dవ స్థాయి"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"హోమ్ కంట్రోల్స్"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"స్క్రీన్ సేవర్గా మీ హోమ్ కంట్రోల్స్ను త్వరగా యాక్సెస్ చేయండి"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"హోమ్ కంట్రోల్స్ను స్క్రీన్ సేవర్గా చేసి వేగంగా యాక్సెస్ పొందండి"</string>
</resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index dece9c1..77f506c 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -260,7 +260,7 @@
<string name="accessibility_rotation_lock_on_landscape" msgid="936972553861524360">"ขณะนี้หน้าจอถูกล็อกให้วางในแนวนอน"</string>
<string name="accessibility_rotation_lock_on_portrait" msgid="2356633398683813837">"ขณะนี้หน้าจอถูกล็อกให้วางในแนวตั้ง"</string>
<string name="dessert_case" msgid="9104973640704357717">"ชั้นแสดงของหวาน"</string>
- <string name="start_dreams" msgid="9131802557946276718">"โปรแกรมรักษาหน้าจอ"</string>
+ <string name="start_dreams" msgid="9131802557946276718">"ภาพพักหน้าจอ"</string>
<string name="ethernet_label" msgid="2203544727007463351">"อีเทอร์เน็ต"</string>
<string name="quick_settings_dnd_label" msgid="7728690179108024338">"ห้ามรบกวน"</string>
<string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"บลูทูธ"</string>
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"ดูทั้งหมด"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"ใช้บลูทูธ"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"เชื่อมต่อแล้ว"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"บันทึกแล้ว"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ยกเลิกการเชื่อมต่อ"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"เปิดใช้งาน"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"เปิดอีกครั้งโดยอัตโนมัติในวันพรุ่งนี้"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"ฟีเจอร์ต่างๆ เช่น Quick Share, หาอุปกรณ์ของฉัน และตำแหน่งของอุปกรณ์ ใช้บลูทูธ"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"บลูทูธจะเปิดพรุ่งนี้เวลา 05:00 น."</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"แบตเตอรี่ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"เสียง"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ชุดหูฟัง"</string>
@@ -747,7 +755,7 @@
<string name="keyboard_shortcut_search_list_no_result" msgid="6819302191660875501">"ไม่พบแป้นพิมพ์ลัด"</string>
<string name="keyboard_shortcut_search_category_system" msgid="1151182120757052669">"ระบบ"</string>
<string name="keyboard_shortcut_search_category_input" msgid="5440558509904296233">"อินพุต"</string>
- <string name="keyboard_shortcut_search_category_open_apps" msgid="1450959949739257562">"แอปที่เปิดอยู่"</string>
+ <string name="keyboard_shortcut_search_category_open_apps" msgid="1450959949739257562">"เปิดแอป"</string>
<string name="keyboard_shortcut_search_category_current_app" msgid="2011953559133734491">"แอปปัจจุบัน"</string>
<string name="keyboard_shortcut_a11y_show_search_results" msgid="2865241062981833705">"แสดงผลการค้นหา"</string>
<string name="keyboard_shortcut_a11y_filter_system" msgid="7744143131119370483">"แสดงแป้นพิมพ์ลัดสำหรับระบบ"</string>
@@ -1302,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"ไฟแบ็กไลต์ของแป้นพิมพ์"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"ระดับที่ %1$d จาก %2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"ระบบควบคุมอุปกรณ์สมาร์ทโฮม"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"เข้าถึงระบบควบคุมอุปกรณ์สมาร์ทโฮมได้อย่างรวดเร็วที่การพักหน้าจอ"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"เข้าถึงระบบควบคุมอุปกรณ์สมาร์ทโฮมได้อย่างรวดเร็วผ่านภาพพักหน้าจอ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 9f408f4..922bf88 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Tingnan lahat"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Gumamit ng Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Nakakonekta"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Na-save"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"idiskonekta"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"i-activate"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Awtomatikong i-on ulit bukas"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Guamgamit ng Bluetooth ang mga feature tulad ng Quick Share, Hanapin ang Aking Device, at lokasyon ng device"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Mag-o-on ang Bluetooth bukas nang 5 AM"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> na baterya"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index c07c350..35f13de 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Tümünü göster"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth\'u kullan"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Bağlandı"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Kaydedildi"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"bağlantıyı kes"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"etkinleştir"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Yarın otomatik olarak tekrar aç"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Quick Share, Cihazımı Bul ve cihaz konumu gibi özellikler Bluetooth\'u kullanır"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth yarın saat 05:00\'te açılacak"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Pil düzeyi <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ses"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Mikrofonlu kulaklık"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Sorunu Kaydedin"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Başlayın"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Durdurun"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Hata Raporu"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Cihaz deneyiminiz ne şekilde etkilendi?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Sorun türünü seçin"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Ekran kaydedicisi"</string>
@@ -1303,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"Klavye aydınlatması"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"Seviye %1$d / %2$d"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"Ev Kontrolleri"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"Ekran koruyucu olan ev kontrollerinize hızlıca erişin"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"Ekran koruyucu olarak ev kontrollerinize hızla erişin"</string>
</resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 6563034..0d97121 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Показати всі"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Увімкнути Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Підключено"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Збережено"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"від’єднати"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активувати"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Автоматично ввімкнути знову завтра"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Такі функції, як швидкий обмін, \"Знайти пристрій\" і визначення місцезнаходження пристрою, використовують Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth увімкнеться завтра о 5:00"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> заряду акумулятора"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудіопристрій"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнітура"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Запис помилки"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Почати"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Зупинити"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Звіт про помилку"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"На який аспект роботи пристрою вплинула проблема?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Виберіть тип проблеми"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Запис відео з екрана"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 452beaf..888be9c 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"سبھی دیکھیں"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"بلوٹوتھ استعمال کریں"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"منسلک ہے"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"محفوظ ہے"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"غیر منسلک کریں"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"فعال کریں"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"کل دوبارہ خودکار طور پر آن ہوگا"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"فوری اشتراک، میرا آلہ ڈھونڈیں، اور آلہ کے مقام جیسی خصوصیات بلوٹوتھ کا استعمال کرتی ہیں"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"بلوٹوتھ کل صبح 5 بجے آن ہو جائے گا"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> بیٹری"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"آڈیو"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ہیڈ سیٹ"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 7a5cbad..6581db5 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Hammasi"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth ishlatish"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ulangan"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saqlangan"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"uzish"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"faollashtirish"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Ertaga yana avtomatik yoqilsin"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Tezkor ulashuv, Qurilmamni top va qurilma geolokatsiyasi kabi funksiyalar Bluetooth ishlatadi"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth ertaga soat 5 da yoqiladi"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Batareya quvvati: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Garnitura"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Yozib olishda xato"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Boshlash"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Toʻxtatish"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Xatoliklar hisoboti"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Qurilma ishlashining qaysi qismiga taʼsir qildi?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Muammo turini tanlang"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Ekran yozuvi"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 85421ee..cce810e 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Xem tất cả"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Bật Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Đã kết nối"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Đã lưu"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ngắt kết nối"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"kích hoạt"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Tự động bật lại vào ngày mai"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Các tính năng như Chia sẻ nhanh, Tìm thiết bị của tôi và dịch vụ vị trí trên thiết bị có sử dụng Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"Bluetooth sẽ bật vào ngày mai lúc 5 giờ sáng"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> pin"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Âm thanh"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Tai nghe"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Ghi lại vấn đề"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Bắt đầu"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Dừng"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Báo cáo lỗi"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Bạn gặp loại vấn đề gì khi dùng thiết bị?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Chọn loại vấn đề"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Ghi màn hình"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index d23a0fd..f683133 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"查看全部"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"使用蓝牙"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"已连接"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"已保存"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"断开连接"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"启用"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"明天自动重新开启"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"“快速分享”“查找我的设备”“设备位置信息”等功能会使用蓝牙"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"蓝牙将于明天早晨 5 点开启"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> 的电量"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音频"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳机"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"录制问题"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"开始"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"停止"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"错误报告"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"设备体验的哪个方面受到影响?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"选择问题类型"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"屏幕录制"</string>
@@ -748,7 +755,7 @@
<string name="keyboard_shortcut_search_list_no_result" msgid="6819302191660875501">"未找到任何快捷键"</string>
<string name="keyboard_shortcut_search_category_system" msgid="1151182120757052669">"系统"</string>
<string name="keyboard_shortcut_search_category_input" msgid="5440558509904296233">"输入"</string>
- <string name="keyboard_shortcut_search_category_open_apps" msgid="1450959949739257562">"已打开的应用"</string>
+ <string name="keyboard_shortcut_search_category_open_apps" msgid="1450959949739257562">"打开应用"</string>
<string name="keyboard_shortcut_search_category_current_app" msgid="2011953559133734491">"当前应用"</string>
<string name="keyboard_shortcut_a11y_show_search_results" msgid="2865241062981833705">"目前显示的是搜索结果"</string>
<string name="keyboard_shortcut_a11y_filter_system" msgid="7744143131119370483">"目前显示的是系统快捷键"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 04565e5..e6fcb7f 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"查看全部"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"使用藍牙"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"已連接"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"已儲存"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"解除連結"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"啟動"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"明天自動重新開啟"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"「快速共享」、「尋找我的裝置」和裝置位置等功能都會使用藍牙"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"藍牙將於明天上午 5 時開啟"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音訊"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳機"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"錄製問題"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"開始"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"停止"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"錯誤報告"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"哪些裝置使用體驗受影響?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"選取問題類型"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"螢幕錄影"</string>
@@ -1303,5 +1310,5 @@
<string name="keyboard_backlight_dialog_title" msgid="8273102932345564724">"鍵盤背光"</string>
<string name="keyboard_backlight_value" msgid="7336398765584393538">"第 %1$d 級,共 %2$d 級"</string>
<string name="home_controls_dream_label" msgid="6567105701292324257">"智能家居"</string>
- <string name="home_controls_dream_description" msgid="4644150952104035789">"在螢幕保護程式畫面上快速存取家居控制功能"</string>
+ <string name="home_controls_dream_description" msgid="4644150952104035789">"在螢幕保護程式畫面上控制智能家居"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 11d2c3e..b02bf81 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"查看全部"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"使用藍牙"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"已連線"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"已儲存"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"取消連結"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"啟用"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"明天自動重新開啟"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"藍牙會用於快速分享、「尋找我的裝置」,以及裝置位置資訊等功能"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"藍牙將在明天早上 5 點開啟"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音訊"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳機"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"記錄問題"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"開始"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"停止"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"錯誤報告"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"哪些裝置使用體驗受到影響?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"選取問題類型"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"螢幕錄影"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 6f87762..a4d66a4 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -270,12 +270,20 @@
<string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Buka konke"</string>
<string name="turn_on_bluetooth" msgid="5681370462180289071">"Sebenzisa i-Bluetooth"</string>
<string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ixhunyiwe"</string>
+ <!-- no translation found for quick_settings_bluetooth_device_audio_sharing (1496358082943301670) -->
+ <skip />
<string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Ilondoloziwe"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"nqamula"</string>
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"yenza kusebenze"</string>
<string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Vula ngokuzenzekela futhi kusasa"</string>
- <string name="turn_on_bluetooth_auto_info_disabled" msgid="8267380591344023327">"Izakhi ezifana Nokwabelana Ngokushesha, okuthi Thola Idivayisi Yami, kanye nendawo yedivayisi zisebenzisa i-Bluetooth"</string>
- <string name="turn_on_bluetooth_auto_info_enabled" msgid="4802071533678400330">"I-Bluetooth izovulwa kusasa ngo-5 AM"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (682984290339848844) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (7440944034584560279) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button (4499275822759907822) -->
+ <skip />
+ <!-- no translation found for quick_settings_bluetooth_audio_sharing_button_sharing (8626191139359072540) -->
+ <skip />
<string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g> ibhethri"</string>
<string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Umsindo"</string>
<string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ihedisethi"</string>
@@ -350,8 +358,7 @@
<string name="qs_record_issue_label" msgid="8166290137285529059">"Rekhoda Inkinga"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Qala"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Misa"</string>
- <!-- no translation found for qs_record_issue_bug_report (8229031766918650079) -->
- <skip />
+ <string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Umbiko Wesiphazamisi"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Kuthinteke yiphi ingxenye yokusebenzisa idivayisi?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Khetha uhlobo lwenkinga"</string>
<string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Irekhodi lesikrini"</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 26fa2b1..82395e4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1762,6 +1762,14 @@
<!-- The height of the main scroll view in bluetooth dialog with auto on toggle. -->
<dimen name="bluetooth_dialog_scroll_view_min_height_with_auto_on">350dp</dimen>
+ <!-- Hearing devices dialog related dimensions -->
+ <dimen name="hearing_devices_preset_spinner_height">72dp</dimen>
+ <dimen name="hearing_devices_preset_spinner_margin">24dp</dimen>
+ <dimen name="hearing_devices_preset_spinner_text_padding_start">20dp</dimen>
+ <dimen name="hearing_devices_preset_spinner_text_padding_end">80dp</dimen>
+ <dimen name="hearing_devices_preset_spinner_arrow_size">24dp</dimen>
+ <dimen name="hearing_devices_preset_spinner_background_radius">28dp</dimen>
+
<!-- Height percentage of the parent container occupied by the communal view -->
<item name="communal_source_height_percentage" format="float" type="dimen">0.80</item>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 035cfdc..b993a5a 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -186,6 +186,8 @@
<item type="id" name="action_remove_menu"/>
<item type="id" name="action_edit"/>
+ <item type="id" name="accessibility_action_open_communal_hub"/>
+
<!-- rounded corner view id -->
<item type="id" name="rounded_corner_top_left"/>
<item type="id" name="rounded_corner_top_right"/>
@@ -231,6 +233,7 @@
<item type="id" name="smart_space_barrier_bottom" />
<item type="id" name="small_clock_guideline_top" />
<item type="id" name="weather_clock_date_and_icons_barrier_bottom" />
+ <item type="id" name="accessibility_actions_view" />
<!-- Privacy dialog -->
<item type="id" name="privacy_dialog_close_app_button" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2fed457..3e13043 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -568,7 +568,7 @@
<!-- Content description for the split notification shade that also includes QS (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_desc_qs_notification_shade">Quick settings and Notification shade.</string>
<!-- Content description for the lock screen (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_desc_lock_screen">Lock screen.</string>
+ <string name="accessibility_desc_lock_screen">Lock screen</string>
<!-- Content description for the work profile lock screen. This prevents work profile apps from being used, but personal apps can be used as normal (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_desc_work_lock">Work lock screen</string>
<!-- Content description for the close button in the zen mode panel introduction message. [CHAR LIMIT=NONE] -->
@@ -678,6 +678,8 @@
<string name="turn_on_bluetooth">Use Bluetooth</string>
<!-- QuickSettings: Bluetooth dialog device connected default summary [CHAR LIMIT=NONE]-->
<string name="quick_settings_bluetooth_device_connected">Connected</string>
+ <!-- QuickSettings: Bluetooth dialog device in audio sharing default summary [CHAR LIMIT=50]-->
+ <string name="quick_settings_bluetooth_device_audio_sharing">Audio Sharing</string>
<!-- QuickSettings: Bluetooth dialog device saved default summary [CHAR LIMIT=NONE]-->
<string name="quick_settings_bluetooth_device_saved">Saved</string>
<!-- QuickSettings: Accessibility label to disconnect a device [CHAR LIMIT=NONE]-->
@@ -690,6 +692,10 @@
<string name="turn_on_bluetooth_auto_info_disabled">Features like Quick Share and Find My Device use Bluetooth</string>
<!-- QuickSettings: Bluetooth auto on info text when enabled [CHAR LIMIT=NONE]-->
<string name="turn_on_bluetooth_auto_info_enabled">Bluetooth will turn on tomorrow morning</string>
+ <!-- QuickSettings: Bluetooth dialog audio sharing button text [CHAR LIMIT=50]-->
+ <string name="quick_settings_bluetooth_audio_sharing_button">Audio Sharing</string>
+ <!-- QuickSettings: Bluetooth dialog audio sharing button text when sharing audio [CHAR LIMIT=50]-->
+ <string name="quick_settings_bluetooth_audio_sharing_button_sharing">Sharing Audio</string>
<!-- QuickSettings: Bluetooth secondary label for the battery level of a connected device [CHAR LIMIT=20]-->
<string name="quick_settings_bluetooth_secondary_label_battery_level"><xliff:g id="battery_level_as_percentage">%s</xliff:g> battery</string>
@@ -910,6 +916,8 @@
<string name="quick_settings_pair_hearing_devices">Pair new device</string>
<!-- QuickSettings: Content description of the hearing devices dialog pair new device [CHAR LIMIT=NONE] -->
<string name="accessibility_hearing_device_pair_new_device">Click to pair new device</string>
+ <!-- Message when selecting hearing aids presets failed. [CHAR LIMIT=NONE] -->
+ <string name="hearing_devices_presets_error">Couldn\'t update preset</string>
<!--- Title of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=150] -->
<string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string>
@@ -1128,6 +1136,9 @@
<!-- Indication on the keyguard that is shown when the device is dock charging. [CHAR LIMIT=80]-->
<string name="keyguard_indication_charging_time_dock"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
+ <!-- Label for accessibility action that shows widgets on lock screen on click. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_open_communal_hub">Widgets on lock screen</string>
+
<!-- Indicator on keyguard to start the communal tutorial. [CHAR LIMIT=100] -->
<string name="communal_tutorial_indicator_text">Swipe left to start the communal tutorial</string>
@@ -1165,6 +1176,10 @@
<string name="work_mode_off_title">Unpause work apps?</string>
<!-- Title for button to unpause on work profile. [CHAR LIMIT=NONE] -->
<string name="work_mode_turn_on">Unpause</string>
+ <!-- Label for accessibility action that navigates to lock screen. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_label_close_communal_hub">Close widgets on lock screen</string>
+ <!-- Accessibility content description for communal hub. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_content_description_for_communal_hub">Widgets on lock screen</string>
<!-- Related to user switcher --><skip/>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 458a21c5..75d925d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -107,7 +107,10 @@
}
}
- private void updateMessageAreaVisibility() {
+ /**
+ * Determines whether to show the message area controlled by MessageAreaController.
+ */
+ public void updateMessageAreaVisibility() {
if (mMessageAreaController == null) return;
if (Flags.revampedBouncerMessages()) {
mMessageAreaController.disable();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index c509356..e8e1cab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -90,7 +90,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -134,7 +134,6 @@
private final UserSwitcherController mUserSwitcherController;
private final GlobalSettings mGlobalSettings;
private final FeatureFlags mFeatureFlags;
- private final SceneContainerFlags mSceneContainerFlags;
private final SessionTracker mSessionTracker;
private final Optional<SideFpsController> mSideFpsController;
private final FalsingA11yDelegate mFalsingA11yDelegate;
@@ -456,7 +455,6 @@
FalsingManager falsingManager,
UserSwitcherController userSwitcherController,
FeatureFlags featureFlags,
- SceneContainerFlags sceneContainerFlags,
GlobalSettings globalSettings,
SessionTracker sessionTracker,
Optional<SideFpsController> sideFpsController,
@@ -491,7 +489,6 @@
mFalsingManager = falsingManager;
mUserSwitcherController = userSwitcherController;
mFeatureFlags = featureFlags;
- mSceneContainerFlags = sceneContainerFlags;
mGlobalSettings = globalSettings;
mSessionTracker = sessionTracker;
if (SideFpsControllerRefactor.isEnabled()) {
@@ -534,7 +531,7 @@
showPrimarySecurityScreen(false);
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
// 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(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 558679e..3ef3418 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -118,6 +118,12 @@
}
@Override
+ public void updateMessageAreaVisibility() {
+ if (mMessageAreaController == null) return;
+ mMessageAreaController.setIsVisible(true);
+ }
+
+ @Override
void resetState() {
super.resetState();
if (DEBUG) Log.v(TAG, "Resetting state");
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index cb1c4b30..46225c7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -115,6 +115,12 @@
}
@Override
+ public void updateMessageAreaVisibility() {
+ if (mMessageAreaController == null) return;
+ mMessageAreaController.setIsVisible(true);
+ }
+
+ @Override
public void onResume(int reason) {
super.onResume(reason);
if (mShowDefaultMessage) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 4987724..8c51a4e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -225,7 +225,7 @@
protected static final int BIOMETRIC_STATE_STOPPED = 0;
/** Biometric authentication state: Listening. */
- protected static final int BIOMETRIC_STATE_RUNNING = 1;
+ private static final int BIOMETRIC_STATE_RUNNING = 1;
/**
* Biometric authentication: Cancelling and waiting for the relevant biometric service to
@@ -1145,6 +1145,7 @@
if (getUserCanSkipBouncer(userId)) {
mTrustManager.unlockedByBiometricForUser(userId, FACE);
}
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
mLogger.d("onFaceAuthenticated");
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1155,12 +1156,6 @@
}
}
- // Intentionally update the fingerprint running state after sending the
- // onBiometricAuthenticated callback to listeners. Updating the fingerprint listening state
- // can update the state of the device which listeners to the callback may rely on.
- // For example, the alternate bouncer visibility state or udfps finger down state.
- updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
-
// Only authenticate face once when assistant is visible
mAssistantVisible = false;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 9b09265..afeb0f8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -189,6 +189,5 @@
ShadeLockscreenInteractor shadeLockscreenInteractor,
@Nullable ShadeExpansionStateManager shadeExpansionStateManager,
BiometricUnlockController biometricUnlockController,
- View notificationContainer,
- KeyguardBypassController bypassController);
+ View notificationContainer);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java
index 4e5df35..cf2675b 100644
--- a/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java
@@ -74,7 +74,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -131,7 +131,6 @@
@NonNull private final KeyguardInteractor mKeyguardInteractor;
@NonNull private final View.AccessibilityDelegate mAccessibilityDelegate;
@NonNull private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractor;
- @NonNull private final SceneContainerFlags mSceneContainerFlags;
// Tracks the velocity of a touch to help filter out the touches that move too fast.
private VelocityTracker mVelocityTracker;
@@ -208,8 +207,7 @@
@NonNull FeatureFlags featureFlags,
PrimaryBouncerInteractor primaryBouncerInteractor,
Context context,
- Lazy<DeviceEntryInteractor> deviceEntryInteractor,
- SceneContainerFlags sceneContainerFlags
+ Lazy<DeviceEntryInteractor> deviceEntryInteractor
) {
mStatusBarStateController = statusBarStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -236,7 +234,6 @@
mResources = resources;
mContext = context;
mDeviceEntryInteractor = deviceEntryInteractor;
- mSceneContainerFlags = sceneContainerFlags;
mAccessibilityDelegate = new View.AccessibilityDelegate() {
private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityAuthenticateHint =
@@ -746,7 +743,7 @@
// play device entry haptic (consistent with UDFPS controller longpress)
vibrateOnLongPress();
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
mDeviceEntryInteractor.get().attemptDeviceEntry();
} else {
mKeyguardViewController.showPrimaryBouncer(/* scrim */ true);
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/DeviceEntryIconLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/DeviceEntryIconLogger.kt
new file mode 100644
index 0000000..349964b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/DeviceEntryIconLogger.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.logging
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.dagger.DeviceEntryIconLog
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val GENERIC_TAG = "DeviceEntryIconLogger"
+
+/** Helper class for logging for the DeviceEntryIcon */
+@SysUISingleton
+class DeviceEntryIconLogger
+@Inject
+constructor(@DeviceEntryIconLog private val logBuffer: LogBuffer) {
+ fun i(@CompileTimeConstant msg: String) = log(msg, INFO)
+ fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+ fun log(@CompileTimeConstant msg: String, level: LogLevel) =
+ logBuffer.log(GENERIC_TAG, level, msg)
+
+ fun logDeviceEntryUdfpsTouchOverlayShouldHandleTouches(
+ shouldHandleTouches: Boolean,
+ canTouchDeviceEntryViewAlpha: Boolean,
+ alternateBouncerVisible: Boolean,
+ hideAffordancesRequest: Boolean,
+ ) {
+ logBuffer.log(
+ "DeviceEntryUdfpsTouchOverlay",
+ DEBUG,
+ {
+ bool1 = canTouchDeviceEntryViewAlpha
+ bool2 = alternateBouncerVisible
+ bool3 = hideAffordancesRequest
+ bool4 = shouldHandleTouches
+ },
+ {
+ "shouldHandleTouches=$bool4 canTouchDeviceEntryViewAlpha=$bool1 " +
+ "alternateBouncerVisible=$bool2 hideAffordancesRequest=$bool3"
+ }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
index 5bd85a7..429d3f0 100644
--- a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
@@ -14,6 +14,8 @@
package com.android.systemui;
+import static com.android.systemui.Flags.sliceBroadcastRelayInBackground;
+
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProvider;
@@ -23,13 +25,19 @@
import android.net.Uri;
import android.os.UserHandle;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.GuardedBy;
+import androidx.annotation.WorkerThread;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.SliceBroadcastRelay;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -42,14 +50,18 @@
private static final String TAG = "SliceBroadcastRelay";
private static final boolean DEBUG = false;
+ @GuardedBy("mRelays")
private final ArrayMap<Uri, BroadcastRelay> mRelays = new ArrayMap<>();
private final Context mContext;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final Executor mBackgroundExecutor;
@Inject
- public SliceBroadcastRelayHandler(Context context, BroadcastDispatcher broadcastDispatcher) {
+ public SliceBroadcastRelayHandler(Context context, BroadcastDispatcher broadcastDispatcher,
+ @Background Executor backgroundExecutor) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
+ mBackgroundExecutor = backgroundExecutor;
}
@Override
@@ -57,21 +69,29 @@
if (DEBUG) Log.d(TAG, "Start");
IntentFilter filter = new IntentFilter(SliceBroadcastRelay.ACTION_REGISTER);
filter.addAction(SliceBroadcastRelay.ACTION_UNREGISTER);
- mBroadcastDispatcher.registerReceiver(mReceiver, filter);
+
+ if (sliceBroadcastRelayInBackground()) {
+ mBroadcastDispatcher.registerReceiver(mReceiver, filter, mBackgroundExecutor);
+ } else {
+ mBroadcastDispatcher.registerReceiver(mReceiver, filter);
+ }
}
// This does not use BroadcastDispatcher as the filter may have schemas or mime types.
+ @WorkerThread
@VisibleForTesting
void handleIntent(Intent intent) {
if (SliceBroadcastRelay.ACTION_REGISTER.equals(intent.getAction())) {
- Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI);
+ Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI, Uri.class);
ComponentName receiverClass =
- intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_RECEIVER);
- IntentFilter filter = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_FILTER);
+ intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_RECEIVER,
+ ComponentName.class);
+ IntentFilter filter = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_FILTER,
+ IntentFilter.class);
if (DEBUG) Log.d(TAG, "Register " + uri + " " + receiverClass + " " + filter);
getOrCreateRelay(uri).register(mContext, receiverClass, filter);
} else if (SliceBroadcastRelay.ACTION_UNREGISTER.equals(intent.getAction())) {
- Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI);
+ Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI, Uri.class);
if (DEBUG) Log.d(TAG, "Unregister " + uri);
BroadcastRelay relay = getAndRemoveRelay(uri);
if (relay != null) {
@@ -80,17 +100,23 @@
}
}
+ @WorkerThread
private BroadcastRelay getOrCreateRelay(Uri uri) {
- BroadcastRelay ret = mRelays.get(uri);
- if (ret == null) {
- ret = new BroadcastRelay(uri);
- mRelays.put(uri, ret);
+ synchronized (mRelays) {
+ BroadcastRelay ret = mRelays.get(uri);
+ if (ret == null) {
+ ret = new BroadcastRelay(uri);
+ mRelays.put(uri, ret);
+ }
+ return ret;
}
- return ret;
}
+ @WorkerThread
private BroadcastRelay getAndRemoveRelay(Uri uri) {
- return mRelays.remove(uri);
+ synchronized (mRelays) {
+ return mRelays.remove(uri);
+ }
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -102,7 +128,7 @@
private static class BroadcastRelay extends BroadcastReceiver {
- private final ArraySet<ComponentName> mReceivers = new ArraySet<>();
+ private final CopyOnWriteArraySet<ComponentName> mReceivers = new CopyOnWriteArraySet<>();
private final UserHandle mUserId;
private final Uri mUri;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
index af8149f..0bd6d6e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -18,8 +18,15 @@
import static android.view.WindowManager.LayoutParams;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.annotation.UiContext;
+import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
@@ -30,56 +37,123 @@
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.res.R;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
-class FullscreenMagnificationController {
+class FullscreenMagnificationController implements ComponentCallbacks {
private final Context mContext;
private final AccessibilityManager mAccessibilityManager;
private final WindowManager mWindowManager;
private Supplier<SurfaceControlViewHost> mScvhSupplier;
- private SurfaceControlViewHost mSurfaceControlViewHost;
+ private SurfaceControlViewHost mSurfaceControlViewHost = null;
+ private SurfaceControl mBorderSurfaceControl = null;
private Rect mWindowBounds;
private SurfaceControl.Transaction mTransaction;
private View mFullscreenBorder = null;
private int mBorderOffset;
private final int mDisplayId;
private static final Region sEmptyRegion = new Region();
+ private ValueAnimator mShowHideBorderAnimator;
+ private Executor mExecutor;
+ private boolean mFullscreenMagnificationActivated = false;
+ private final Configuration mConfiguration;
FullscreenMagnificationController(
@UiContext Context context,
+ Executor executor,
AccessibilityManager accessibilityManager,
WindowManager windowManager,
Supplier<SurfaceControlViewHost> scvhSupplier) {
+ this(context, executor, accessibilityManager, windowManager, scvhSupplier,
+ new SurfaceControl.Transaction(), createNullTargetObjectAnimator(context));
+ }
+
+ @VisibleForTesting
+ FullscreenMagnificationController(
+ @UiContext Context context,
+ @Main Executor executor,
+ AccessibilityManager accessibilityManager,
+ WindowManager windowManager,
+ Supplier<SurfaceControlViewHost> scvhSupplier,
+ SurfaceControl.Transaction transaction,
+ ValueAnimator valueAnimator) {
mContext = context;
+ mExecutor = executor;
mAccessibilityManager = accessibilityManager;
mWindowManager = windowManager;
mWindowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
- mTransaction = new SurfaceControl.Transaction();
+ mTransaction = transaction;
mScvhSupplier = scvhSupplier;
mBorderOffset = mContext.getResources().getDimensionPixelSize(
R.dimen.magnifier_border_width_fullscreen_with_offset)
- mContext.getResources().getDimensionPixelSize(
R.dimen.magnifier_border_width_fullscreen);
mDisplayId = mContext.getDisplayId();
+ mConfiguration = new Configuration(context.getResources().getConfiguration());
+ mShowHideBorderAnimator = valueAnimator;
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ if (isReverse) {
+ // The animation was played in reverse, which means we are hiding the border.
+ // We would like to perform clean up after the border is fully hidden.
+ cleanUpBorder();
+ }
+ }
+ });
}
+ private static ValueAnimator createNullTargetObjectAnimator(Context context) {
+ final ValueAnimator valueAnimator =
+ ObjectAnimator.ofFloat(/* target= */ null, View.ALPHA, 0f, 1f);
+ Interpolator interpolator = new AccelerateDecelerateInterpolator();
+ final long longAnimationDuration = context.getResources().getInteger(
+ com.android.internal.R.integer.config_longAnimTime);
+
+ valueAnimator.setInterpolator(interpolator);
+ valueAnimator.setDuration(longAnimationDuration);
+ return valueAnimator;
+ }
+
+ /**
+ * Check the fullscreen magnification activation status, and proceed corresponding actions when
+ * there is an activation change.
+ */
@UiThread
void onFullscreenMagnificationActivationChanged(boolean activated) {
- if (activated) {
- createFullscreenMagnificationBorder();
- } else {
- removeFullscreenMagnificationBorder();
+ final boolean changed = (mFullscreenMagnificationActivated != activated);
+ if (changed) {
+ mFullscreenMagnificationActivated = activated;
+ if (activated) {
+ createFullscreenMagnificationBorder();
+ } else {
+ removeFullscreenMagnificationBorder();
+ }
}
}
+ /**
+ * This method should only be called when fullscreen magnification is changed from activated
+ * to inactivated.
+ */
@UiThread
private void removeFullscreenMagnificationBorder() {
+ mContext.unregisterComponentCallbacks(this);
+ mShowHideBorderAnimator.reverse();
+ }
+
+ private void cleanUpBorder() {
if (mSurfaceControlViewHost != null) {
mSurfaceControlViewHost.release();
mSurfaceControlViewHost = null;
@@ -91,31 +165,57 @@
}
/**
- * Since the device corners are not perfectly rounded, we would like to create a thick stroke,
- * and set negative offset to the border view to fill up the spaces between the border and the
- * device corners.
+ * This method should only be called when fullscreen magnification is changed from inactivated
+ * to activated.
*/
@UiThread
private void createFullscreenMagnificationBorder() {
- mFullscreenBorder = LayoutInflater.from(mContext)
- .inflate(R.layout.fullscreen_magnification_border, null);
- mSurfaceControlViewHost = mScvhSupplier.get();
- mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());
+ onConfigurationChanged(mContext.getResources().getConfiguration());
+ mContext.registerComponentCallbacks(this);
- SurfaceControl surfaceControl = mSurfaceControlViewHost
- .getSurfacePackage().getSurfaceControl();
+ if (mSurfaceControlViewHost == null) {
+ // Create the view only if it does not exist yet. If we are trying to enable fullscreen
+ // magnification before it was fully disabled, we use the previous view instead of
+ // creating a new one.
+ mFullscreenBorder = LayoutInflater.from(mContext)
+ .inflate(R.layout.fullscreen_magnification_border, null);
+ // Set the initial border view alpha manually so we won't show the border accidentally
+ // after we apply show() to the SurfaceControl and before the animation starts to run.
+ mFullscreenBorder.setAlpha(0f);
+ mShowHideBorderAnimator.setTarget(mFullscreenBorder);
+ mSurfaceControlViewHost = mScvhSupplier.get();
+ mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());
+ mBorderSurfaceControl = mSurfaceControlViewHost.getSurfacePackage().getSurfaceControl();
+ }
mTransaction
- .setPosition(surfaceControl, -mBorderOffset, -mBorderOffset)
- .setLayer(surfaceControl, Integer.MAX_VALUE)
- .show(surfaceControl)
+ .addTransactionCommittedListener(
+ mExecutor,
+ () -> {
+ if (mShowHideBorderAnimator.isRunning()) {
+ // Since the method is only called when there is an activation
+ // status change, the running animator is hiding the border.
+ mShowHideBorderAnimator.reverse();
+ } else {
+ mShowHideBorderAnimator.start();
+ }
+ })
+ .setPosition(mBorderSurfaceControl, -mBorderOffset, -mBorderOffset)
+ .setLayer(mBorderSurfaceControl, Integer.MAX_VALUE)
+ .show(mBorderSurfaceControl)
.apply();
- mAccessibilityManager.attachAccessibilityOverlayToDisplay(mDisplayId, surfaceControl);
+ mAccessibilityManager.attachAccessibilityOverlayToDisplay(
+ mDisplayId, mBorderSurfaceControl);
applyTouchableRegion();
}
+ /**
+ * Since the device corners are not perfectly rounded, we would like to create a thick stroke,
+ * and set negative offset to the border view to fill up the spaces between the border and the
+ * device corners.
+ */
private LayoutParams getBorderLayoutParams() {
LayoutParams params = new LayoutParams(
mWindowBounds.width() + 2 * mBorderOffset,
@@ -137,4 +237,41 @@
// all touch events to go through this view.
surfaceControl.setTouchableRegion(sEmptyRegion);
}
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ final int configDiff = newConfig.diff(mConfiguration);
+ mConfiguration.setTo(newConfig);
+ onConfigurationChanged(configDiff);
+ }
+
+ @VisibleForTesting
+ void onConfigurationChanged(int configDiff) {
+ boolean reCreateWindow = false;
+ if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0
+ || (configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0
+ || (configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+ updateDimensions();
+ mWindowBounds.set(mWindowManager.getCurrentWindowMetrics().getBounds());
+ reCreateWindow = true;
+ }
+
+ if (mFullscreenBorder != null && reCreateWindow) {
+ final int newWidth = mWindowBounds.width() + 2 * mBorderOffset;
+ final int newHeight = mWindowBounds.height() + 2 * mBorderOffset;
+ mSurfaceControlViewHost.relayout(newWidth, newHeight);
+ }
+ }
+
+ private void updateDimensions() {
+ mBorderOffset = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen_with_offset)
+ - mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen);
+ }
+
+ @Override
+ public void onLowMemory() {
+
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 70165f3..177d933 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -54,6 +54,7 @@
import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
import javax.inject.Inject;
@@ -71,6 +72,7 @@
private final ModeSwitchesController mModeSwitchesController;
private final Context mContext;
private final Handler mHandler;
+ private final Executor mExecutor;
private final AccessibilityManager mAccessibilityManager;
private final CommandQueue mCommandQueue;
private final OverviewProxyService mOverviewProxyService;
@@ -139,12 +141,13 @@
DisplayIdIndexSupplier<FullscreenMagnificationController> {
private final Context mContext;
+ private final Executor mExecutor;
- FullscreenMagnificationControllerSupplier(Context context, Handler handler,
- DisplayManager displayManager, SysUiState sysUiState,
- SecureSettings secureSettings) {
+ FullscreenMagnificationControllerSupplier(Context context, DisplayManager displayManager,
+ Executor executor) {
super(displayManager);
mContext = context;
+ mExecutor = executor;
}
@Override
@@ -156,6 +159,7 @@
windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
return new FullscreenMagnificationController(
windowContext,
+ mExecutor,
windowContext.getSystemService(AccessibilityManager.class),
windowContext.getSystemService(WindowManager.class),
scvhSupplier);
@@ -200,13 +204,14 @@
DisplayIdIndexSupplier<MagnificationSettingsController> mMagnificationSettingsSupplier;
@Inject
- public Magnification(Context context, @Main Handler mainHandler,
+ public Magnification(Context context, @Main Handler mainHandler, @Main Executor executor,
CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
SysUiState sysUiState, OverviewProxyService overviewProxyService,
SecureSettings secureSettings, DisplayTracker displayTracker,
DisplayManager displayManager, AccessibilityLogger a11yLogger) {
mContext = context;
mHandler = mainHandler;
+ mExecutor = executor;
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mCommandQueue = commandQueue;
mModeSwitchesController = modeSwitchesController;
@@ -218,7 +223,7 @@
mHandler, mWindowMagnifierCallback,
displayManager, sysUiState, secureSettings);
mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
- context, mHandler, displayManager, sysUiState, secureSettings);
+ context, displayManager, mExecutor);
mMagnificationSettingsSupplier = new SettingsSupplier(context,
mMagnificationSettingsControllerCallback, displayManager, secureSettings);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
index 7e96e48..615363d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
@@ -72,6 +72,7 @@
private boolean mEndAnimationCanceled = false;
@MagnificationState
private int mState = STATE_DISABLED;
+ private Runnable mOnAnimationEndRunnable;
WindowMagnificationAnimationController(@UiContext Context context) {
this(context, newValueAnimator(context.getResources()));
@@ -303,12 +304,7 @@
return;
}
- // If the animation is playing backwards, mStartSpec will be the final spec we would
- // like to reach.
- AnimationSpec spec = isReverse ? mStartSpec : mEndSpec;
- mController.updateWindowMagnificationInternal(
- spec.mScale, spec.mCenterX, spec.mCenterY,
- mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
+ mOnAnimationEndRunnable.run();
if (mState == STATE_DISABLING) {
mController.deleteWindowMagnification();
@@ -333,6 +329,10 @@
public void onAnimationRepeat(Animator animation) {
}
+ void setOnAnimationEndRunnable(Runnable runnable) {
+ mOnAnimationEndRunnable = runnable;
+ }
+
private void sendAnimationCallback(boolean success) {
if (mAnimationCallback != null) {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index a847c3d..9837e36 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -260,6 +260,11 @@
mContext = context;
mHandler = handler;
mAnimationController = animationController;
+ mAnimationController.setOnAnimationEndRunnable(() -> {
+ if (Flags.createWindowlessWindowMagnifier()) {
+ notifySourceBoundsChanged();
+ }
+ });
mAnimationController.setWindowMagnificationController(this);
mWindowMagnifierCallback = callback;
mSysUiState = sysUiState;
@@ -1051,11 +1056,15 @@
// Notify source bounds change when the magnifier is not animating.
if (!mAnimationController.isAnimating()) {
- mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, mSourceBounds);
+ notifySourceBoundsChanged();
}
}
}
+ private void notifySourceBoundsChanged() {
+ mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, mSourceBounds);
+ }
+
/**
* Updates the position of {@link mSurfaceControlViewHost} and layout params of MirrorView based
* on the position and size of {@link #mMagnificationFrame}.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 475bb2c..7b5a09cb 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -21,6 +21,8 @@
import static java.util.Collections.emptyList;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothHapPresetInfo;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
@@ -30,7 +32,11 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.Visibility;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
import android.widget.Button;
+import android.widget.Spinner;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -40,7 +46,9 @@
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.accessibility.hearingaid.HearingDevicesListAdapter.HearingDeviceItemCallback;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.bluetooth.qsdialog.ActiveHearingDeviceItemFactory;
@@ -48,7 +56,9 @@
import com.android.systemui.bluetooth.qsdialog.ConnectedDeviceItemFactory;
import com.android.systemui.bluetooth.qsdialog.DeviceItem;
import com.android.systemui.bluetooth.qsdialog.DeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.DeviceItemType;
import com.android.systemui.bluetooth.qsdialog.SavedHearingDeviceItemFactory;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.res.R;
@@ -80,11 +90,37 @@
private final LocalBluetoothManager mLocalBluetoothManager;
private final Handler mMainHandler;
private final AudioManager mAudioManager;
-
+ private final LocalBluetoothProfileManager mProfileManager;
+ private final HapClientProfile mHapClientProfile;
private HearingDevicesListAdapter mDeviceListAdapter;
+ private HearingDevicesPresetsController mPresetsController;
+ private Context mApplicationContext;
private SystemUIDialog mDialog;
private RecyclerView mDeviceList;
+ private List<DeviceItem> mHearingDeviceItemList;
+ private Spinner mPresetSpinner;
+ private ArrayAdapter<String> mPresetInfoAdapter;
private Button mPairButton;
+ private final HearingDevicesPresetsController.PresetCallback mPresetCallback =
+ new HearingDevicesPresetsController.PresetCallback() {
+ @Override
+ public void onPresetInfoUpdated(List<BluetoothHapPresetInfo> presetInfos,
+ int activePresetIndex) {
+ mMainHandler.post(
+ () -> refreshPresetInfoAdapter(presetInfos, activePresetIndex));
+ }
+
+ @Override
+ public void onPresetCommandFailed(int reason) {
+ final List<BluetoothHapPresetInfo> presetInfos =
+ mPresetsController.getAllPresetInfo();
+ final int activePresetIndex = mPresetsController.getActivePresetIndex();
+ mMainHandler.post(() -> {
+ refreshPresetInfoAdapter(presetInfos, activePresetIndex);
+ showPresetErrorToast(mApplicationContext);
+ });
+ }
+ };
private final List<DeviceItemFactory> mHearingDeviceItemFactoryList = List.of(
new ActiveHearingDeviceItemFactory(),
new AvailableHearingDeviceItemFactory(),
@@ -107,6 +143,7 @@
@AssistedInject
public HearingDevicesDialogDelegate(
+ @Application Context applicationContext,
@Assisted boolean showPairNewDevice,
SystemUIDialog.Factory systemUIDialogFactory,
ActivityStarter activityStarter,
@@ -114,6 +151,7 @@
@Nullable LocalBluetoothManager localBluetoothManager,
@Main Handler handler,
AudioManager audioManager) {
+ mApplicationContext = applicationContext;
mShowPairNewDevice = showPairNewDevice;
mSystemUIDialogFactory = systemUIDialogFactory;
mActivityStarter = activityStarter;
@@ -121,6 +159,8 @@
mLocalBluetoothManager = localBluetoothManager;
mMainHandler = handler;
mAudioManager = audioManager;
+ mProfileManager = localBluetoothManager.getProfileManager();
+ mHapClientProfile = mProfileManager.getHapClientProfile();
}
@Override
@@ -158,19 +198,34 @@
@Override
public void onActiveDeviceChanged(@Nullable CachedBluetoothDevice activeDevice,
int bluetoothProfile) {
- mMainHandler.post(() -> mDeviceListAdapter.refreshDeviceItemList(getHearingDevicesList()));
+ CachedBluetoothDevice activeHearingDevice;
+ mHearingDeviceItemList = getHearingDevicesList();
+ if (mPresetsController != null) {
+ activeHearingDevice = getActiveHearingDevice(mHearingDeviceItemList);
+ mPresetsController.setActiveHearingDevice(activeHearingDevice);
+ } else {
+ activeHearingDevice = null;
+ }
+ mMainHandler.post(() -> {
+ mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList);
+ mPresetSpinner.setVisibility(
+ (activeHearingDevice != null && !mPresetInfoAdapter.isEmpty()) ? VISIBLE
+ : GONE);
+ });
}
@Override
public void onProfileConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
int state, int bluetoothProfile) {
- mMainHandler.post(() -> mDeviceListAdapter.refreshDeviceItemList(getHearingDevicesList()));
+ mHearingDeviceItemList = getHearingDevicesList();
+ mMainHandler.post(() -> mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList));
}
@Override
public void onAclConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
int state) {
- mMainHandler.post(() -> mDeviceListAdapter.refreshDeviceItemList(getHearingDevicesList()));
+ mHearingDeviceItemList = getHearingDevicesList();
+ mMainHandler.post(() -> mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList));
}
@Override
@@ -187,10 +242,15 @@
@Override
public void onCreate(@NonNull SystemUIDialog dialog, @Nullable Bundle savedInstanceState) {
+ if (mLocalBluetoothManager == null) {
+ return;
+ }
mPairButton = dialog.requireViewById(R.id.pair_new_device_button);
mDeviceList = dialog.requireViewById(R.id.device_list);
+ mPresetSpinner = dialog.requireViewById(R.id.preset_spinner);
setupDeviceListView(dialog);
+ setupPresetSpinner(dialog);
setupPairNewDeviceButton(dialog, mShowPairNewDevice ? VISIBLE : GONE);
}
@@ -199,7 +259,14 @@
if (mLocalBluetoothManager == null) {
return;
}
+
mLocalBluetoothManager.getEventManager().registerCallback(this);
+ if (mPresetsController != null) {
+ mPresetsController.registerHapCallback();
+ if (mHapClientProfile != null && !mHapClientProfile.isProfileReady()) {
+ mProfileManager.addServiceListener(mPresetsController);
+ }
+ }
}
@Override
@@ -207,15 +274,51 @@
if (mLocalBluetoothManager == null) {
return;
}
+
+ if (mPresetsController != null) {
+ mPresetsController.unregisterHapCallback();
+ mProfileManager.removeServiceListener(mPresetsController);
+ }
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
}
private void setupDeviceListView(SystemUIDialog dialog) {
mDeviceList.setLayoutManager(new LinearLayoutManager(dialog.getContext()));
- mDeviceListAdapter = new HearingDevicesListAdapter(getHearingDevicesList(), this);
+ mHearingDeviceItemList = getHearingDevicesList();
+ mDeviceListAdapter = new HearingDevicesListAdapter(mHearingDeviceItemList, this);
mDeviceList.setAdapter(mDeviceListAdapter);
}
+ private void setupPresetSpinner(SystemUIDialog dialog) {
+ mPresetsController = new HearingDevicesPresetsController(mProfileManager, mPresetCallback);
+ final CachedBluetoothDevice activeHearingDevice = getActiveHearingDevice(
+ mHearingDeviceItemList);
+ mPresetsController.setActiveHearingDevice(activeHearingDevice);
+
+ mPresetInfoAdapter = new ArrayAdapter<>(dialog.getContext(),
+ android.R.layout.simple_spinner_dropdown_item);
+ mPresetInfoAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mPresetSpinner.setAdapter(mPresetInfoAdapter);
+ mPresetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ mPresetsController.selectPreset(
+ mPresetsController.getAllPresetInfo().get(position).getIndex());
+ mPresetSpinner.setSelection(position);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Do nothing
+ }
+ });
+ final List<BluetoothHapPresetInfo> presetInfos = mPresetsController.getAllPresetInfo();
+ final int activePresetIndex = mPresetsController.getActivePresetIndex();
+ refreshPresetInfoAdapter(presetInfos, activePresetIndex);
+ mPresetSpinner.setVisibility(
+ (activeHearingDevice != null && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
+ }
+
private void setupPairNewDeviceButton(SystemUIDialog dialog, @Visibility int visibility) {
if (visibility == VISIBLE) {
mPairButton.setOnClickListener(v -> {
@@ -230,6 +333,21 @@
}
}
+ private void refreshPresetInfoAdapter(List<BluetoothHapPresetInfo> presetInfos,
+ int activePresetIndex) {
+ mPresetInfoAdapter.clear();
+ mPresetInfoAdapter.addAll(
+ presetInfos.stream().map(BluetoothHapPresetInfo::getName).toList());
+ if (activePresetIndex != BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) {
+ final int size = mPresetInfoAdapter.getCount();
+ for (int position = 0; position < size; position++) {
+ if (presetInfos.get(position).getIndex() == activePresetIndex) {
+ mPresetSpinner.setSelection(position);
+ }
+ }
+ }
+ }
+
private List<DeviceItem> getHearingDevicesList() {
if (mLocalBluetoothManager == null
|| !mLocalBluetoothManager.getBluetoothAdapter().isEnabled()) {
@@ -242,6 +360,15 @@
.collect(Collectors.toList());
}
+ @Nullable
+ private CachedBluetoothDevice getActiveHearingDevice(List<DeviceItem> hearingDeviceItemList) {
+ return hearingDeviceItemList.stream()
+ .filter(item -> item.getType() == DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
+ .map(DeviceItem::getCachedBluetoothDevice)
+ .findFirst()
+ .orElse(null);
+ }
+
private DeviceItem createHearingDeviceItem(CachedBluetoothDevice cachedDevice) {
final Context context = mDialog.getContext();
if (cachedDevice == null) {
@@ -260,4 +387,8 @@
mDialog.dismiss();
}
}
+
+ private void showPresetErrorToast(Context context) {
+ Toast.makeText(context, R.string.hearing_devices_presets_error, Toast.LENGTH_SHORT).show();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
new file mode 100644
index 0000000..02fa003
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.hearingaid;
+
+import static java.util.Collections.emptyList;
+
+import android.bluetooth.BluetoothCsipSetCoordinator;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothHapPresetInfo;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.HapClientProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.List;
+
+/**
+ * The controller of the hearing devices presets of the bluetooth Hearing Access Profile.
+ */
+public class HearingDevicesPresetsController implements
+ LocalBluetoothProfileManager.ServiceListener, BluetoothHapClient.Callback {
+
+ private static final String TAG = "HearingDevicesPresetsController";
+ private static final boolean DEBUG = true;
+
+ private final LocalBluetoothProfileManager mProfileManager;
+ private final HapClientProfile mHapClientProfile;
+ private final PresetCallback mPresetCallback;
+
+ private CachedBluetoothDevice mActiveHearingDevice;
+ private int mSelectedPresetIndex;
+
+ public HearingDevicesPresetsController(LocalBluetoothProfileManager profileManager,
+ PresetCallback presetCallback) {
+ mProfileManager = profileManager;
+ mHapClientProfile = mProfileManager.getHapClientProfile();
+ mPresetCallback = presetCallback;
+ }
+
+ @Override
+ public void onServiceConnected() {
+ if (mHapClientProfile != null && mHapClientProfile.isProfileReady()) {
+ mProfileManager.removeServiceListener(this);
+ registerHapCallback();
+ mPresetCallback.onPresetInfoUpdated(getAllPresetInfo(), getActivePresetIndex());
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected() {
+ // Do nothing
+ }
+
+ @Override
+ public void onPresetSelected(@NonNull BluetoothDevice device, int presetIndex, int reason) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ if (device.equals(mActiveHearingDevice.getDevice())) {
+ if (DEBUG) {
+ Log.d(TAG, "onPresetSelected, device: " + device.getAddress()
+ + ", presetIndex: " + presetIndex + ", reason: " + reason);
+ }
+ mPresetCallback.onPresetInfoUpdated(getAllPresetInfo(), getActivePresetIndex());
+ }
+ }
+
+ @Override
+ public void onPresetInfoChanged(@NonNull BluetoothDevice device,
+ @NonNull List<BluetoothHapPresetInfo> presetInfoList, int reason) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ if (device.equals(mActiveHearingDevice.getDevice())) {
+ if (DEBUG) {
+ Log.d(TAG, "onPresetInfoChanged, device: " + device.getAddress()
+ + ", reason: " + reason + ", infoList: " + presetInfoList);
+ }
+ mPresetCallback.onPresetInfoUpdated(getAllPresetInfo(), getActivePresetIndex());
+ }
+ }
+
+ @Override
+ public void onPresetSelectionFailed(@NonNull BluetoothDevice device, int reason) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ if (device.equals(mActiveHearingDevice.getDevice())) {
+ Log.w(TAG, "onPresetSelectionFailed, device: " + device.getAddress()
+ + ", reason: " + reason);
+ mPresetCallback.onPresetCommandFailed(reason);
+ }
+ }
+
+ @Override
+ public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
+ Log.w(TAG, "onPresetSelectionForGroupFailed, group: " + hapGroupId
+ + ", reason: " + reason);
+ selectPresetIndependently(mSelectedPresetIndex);
+ }
+ }
+
+ @Override
+ public void onSetPresetNameFailed(@NonNull BluetoothDevice device, int reason) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ if (device.equals(mActiveHearingDevice.getDevice())) {
+ Log.w(TAG, "onSetPresetNameFailed, device: " + device.getAddress()
+ + ", reason: " + reason);
+ mPresetCallback.onPresetCommandFailed(reason);
+ }
+ }
+
+ @Override
+ public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
+ Log.w(TAG, "onSetPresetNameForGroupFailed, group: " + hapGroupId
+ + ", reason: " + reason);
+ }
+ mPresetCallback.onPresetCommandFailed(reason);
+ }
+
+ /**
+ * Registers a callback to be notified about operation changed for {@link HapClientProfile}.
+ */
+ public void registerHapCallback() {
+ if (mHapClientProfile != null) {
+ try {
+ mHapClientProfile.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
+ } catch (IllegalArgumentException e) {
+ // The callback was already registered
+ Log.w(TAG, "Cannot register callback: " + e.getMessage());
+ }
+
+ }
+ }
+
+ /**
+ * Removes a previously-added {@link HapClientProfile} callback.
+ */
+ public void unregisterHapCallback() {
+ if (mHapClientProfile != null) {
+ try {
+ mHapClientProfile.unregisterCallback(this);
+ } catch (IllegalArgumentException e) {
+ // The callback was never registered or was already unregistered
+ Log.w(TAG, "Cannot unregister callback: " + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Sets the hearing device for this controller to control the preset.
+ *
+ * @param activeHearingDevice the {@link CachedBluetoothDevice} need to be hearing aid device
+ */
+ public void setActiveHearingDevice(CachedBluetoothDevice activeHearingDevice) {
+ mActiveHearingDevice = activeHearingDevice;
+ }
+
+ /**
+ * Selects the currently active preset for {@code mActiveHearingDevice} individual device or
+ * the device group accoridng to whether it supports synchronized presets or not.
+ *
+ * @param presetIndex an index of one of the available presets
+ */
+ public void selectPreset(int presetIndex) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ mSelectedPresetIndex = presetIndex;
+ boolean supportSynchronizedPresets = mHapClientProfile.supportsSynchronizedPresets(
+ mActiveHearingDevice.getDevice());
+ int hapGroupId = mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice());
+ if (supportSynchronizedPresets) {
+ if (hapGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ selectPresetSynchronously(hapGroupId, presetIndex);
+ } else {
+ Log.w(TAG, "supportSynchronizedPresets but hapGroupId is invalid.");
+ selectPresetIndependently(presetIndex);
+ }
+ } else {
+ selectPresetIndependently(presetIndex);
+ }
+ }
+
+ /**
+ * Gets all preset info for {@code mActiveHearingDevice} device.
+ *
+ * @return a list of all known preset info
+ */
+ public List<BluetoothHapPresetInfo> getAllPresetInfo() {
+ if (mActiveHearingDevice == null) {
+ return emptyList();
+ }
+ return mHapClientProfile.getAllPresetInfo(mActiveHearingDevice.getDevice());
+ }
+
+ /**
+ * Gets the currently active preset for {@code mActiveHearingDevice} device.
+ *
+ * @return active preset index
+ */
+ public int getActivePresetIndex() {
+ if (mActiveHearingDevice == null) {
+ return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
+ }
+ return mHapClientProfile.getActivePresetIndex(mActiveHearingDevice.getDevice());
+ }
+
+ private void selectPresetSynchronously(int groupId, int presetIndex) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "selectPresetSynchronously"
+ + ", presetIndex: " + presetIndex
+ + ", groupId: " + groupId
+ + ", device: " + mActiveHearingDevice.getAddress());
+ }
+ mHapClientProfile.selectPresetForGroup(groupId, presetIndex);
+ }
+
+ private void selectPresetIndependently(int presetIndex) {
+ if (mActiveHearingDevice == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "selectPresetIndependently"
+ + ", presetIndex: " + presetIndex
+ + ", device: " + mActiveHearingDevice.getAddress());
+ }
+ mHapClientProfile.selectPreset(mActiveHearingDevice.getDevice(), presetIndex);
+ final CachedBluetoothDevice subDevice = mActiveHearingDevice.getSubDevice();
+ if (subDevice != null) {
+ if (DEBUG) {
+ Log.d(TAG, "selectPreset for subDevice, device: " + subDevice);
+ }
+ mHapClientProfile.selectPreset(subDevice.getDevice(), presetIndex);
+ }
+ for (final CachedBluetoothDevice memberDevice :
+ mActiveHearingDevice.getMemberDevice()) {
+ if (DEBUG) {
+ Log.d(TAG, "selectPreset for memberDevice, device: " + memberDevice);
+ }
+ mHapClientProfile.selectPreset(memberDevice.getDevice(), presetIndex);
+ }
+ }
+
+ /**
+ * Interface to provide callbacks when preset command result from {@link HapClientProfile}
+ * changed.
+ */
+ public interface PresetCallback {
+ /**
+ * Called when preset info from {@link HapClientProfile} operation get updated.
+ *
+ * @param presetInfos all preset info for {@code mActiveHearingDevice} device
+ * @param activePresetIndex currently active preset index for {@code mActiveHearingDevice}
+ * device
+ */
+ void onPresetInfoUpdated(List<BluetoothHapPresetInfo> presetInfos, int activePresetIndex);
+
+ /**
+ * Called when preset operation from {@link HapClientProfile} failed to handle.
+ *
+ * @param reason failure reason
+ */
+ void onPresetCommandFailed(int reason);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 7cb028a..b8ff0c0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.accessibility.qs
+import com.android.systemui.Flags
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
@@ -40,9 +41,14 @@
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor.ReduceBrightColorsTileDataInteractor
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor.ReduceBrightColorsTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.qs.tiles.impl.reducebrightness.ui.ReduceBrightColorsTileMapper
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel
import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
@@ -105,6 +111,7 @@
const val COLOR_CORRECTION_TILE_SPEC = "color_correction"
const val COLOR_INVERSION_TILE_SPEC = "inversion"
const val FONT_SCALING_TILE_SPEC = "font_scaling"
+ const val REDUCE_BRIGHTNESS_TILE_SPEC = "reduce_brightness"
@Provides
@IntoMap
@@ -198,5 +205,41 @@
stateInteractor,
mapper,
)
+
+ @Provides
+ @IntoMap
+ @StringKey(REDUCE_BRIGHTNESS_TILE_SPEC)
+ fun provideReduceBrightColorsTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(REDUCE_BRIGHTNESS_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_extra_dim_icon_on,
+ labelRes = com.android.internal.R.string.reduce_bright_colors_feature_name,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /**
+ * Inject Reduce Bright Colors Tile into tileViewModelMap in QSModule. The tile is hidden
+ * behind a flag.
+ */
+ @Provides
+ @IntoMap
+ @StringKey(REDUCE_BRIGHTNESS_TILE_SPEC)
+ fun provideReduceBrightColorsTileViewModel(
+ factory: QSTileViewModelFactory.Static<ReduceBrightColorsTileModel>,
+ mapper: ReduceBrightColorsTileMapper,
+ stateInteractor: ReduceBrightColorsTileDataInteractor,
+ userActionInteractor: ReduceBrightColorsTileUserActionInteractor
+ ): QSTileViewModel =
+ if (Flags.qsNewTilesFuture())
+ factory.create(
+ TileSpec.create(REDUCE_BRIGHTNESS_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
+ else StubQSTileViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt b/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt
new file mode 100644
index 0000000..ea00398
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.ambient.dagger
+
+import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
+import com.android.systemui.ambient.touch.dagger.InputSessionComponent
+import dagger.Module
+
+@Module(subcomponents = [AmbientTouchComponent::class, InputSessionComponent::class])
+interface AmbientModule {
+ companion object {
+ const val TOUCH_HANDLERS = "touch_handlers"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
index 66d413a..d0f08f5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
-
-import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING;
-import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING;
-import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION;
-import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE;
+package com.android.systemui.ambient.touch;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -38,9 +33,10 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Flags;
+import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule;
+import com.android.systemui.ambient.touch.scrim.ScrimController;
+import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
-import com.android.systemui.dreams.touch.scrim.ScrimController;
-import com.android.systemui.dreams.touch.scrim.ScrimManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -55,7 +51,7 @@
/**
* Monitor for tracking touches on the DreamOverlay to bring up the bouncer.
*/
-public class BouncerSwipeTouchHandler implements DreamTouchHandler {
+public class BouncerSwipeTouchHandler implements TouchHandler {
/**
* An interface for creating ValueAnimators.
*/
@@ -226,12 +222,12 @@
VelocityTrackerFactory velocityTrackerFactory,
LockPatternUtils lockPatternUtils,
UserTracker userTracker,
- @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
+ @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
FlingAnimationUtils flingAnimationUtils,
- @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
+ @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
FlingAnimationUtils flingAnimationUtilsClosing,
- @Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage,
- @Named(MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage,
+ @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage,
+ @Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage,
UiEventLogger uiEventLogger) {
mCentralSurfaces = centralSurfaces;
mScrimManager = scrimManager;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/InputSession.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/InputSession.java
index cddba04..6a76c87 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/InputSession.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
-import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.PILFER_ON_GESTURE_CONSUME;
+import static com.android.systemui.ambient.touch.dagger.AmbientTouchModule.PILFER_ON_GESTURE_CONSUME;
import android.os.Looper;
import android.view.Choreographer;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
index e0bf52e..9ef9938 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
-import static com.android.systemui.dreams.touch.dagger.ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT;
+import static com.android.systemui.ambient.touch.dagger.ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT;
import android.graphics.Rect;
import android.graphics.Region;
@@ -35,7 +35,7 @@
* {@link ShadeTouchHandler} is responsible for handling swipe down gestures over dream
* to bring down the shade.
*/
-public class ShadeTouchHandler implements DreamTouchHandler {
+public class ShadeTouchHandler implements TouchHandler {
private final Optional<CentralSurfaces> mSurfaces;
private final ShadeViewController mShadeViewController;
private final int mInitiationHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
index 1ec0008..190bc15 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
import android.graphics.Rect;
import android.graphics.Region;
@@ -25,24 +25,30 @@
import com.google.common.util.concurrent.ListenableFuture;
/**
- * The {@link DreamTouchHandler} interface provides a way for dream overlay components to observe
+ * The {@link TouchHandler} interface provides a way for dream overlay components to observe
* touch events and gestures with the ability to intercept the latter. Touch interaction sequences
* are abstracted as sessions. A session represents the time of first
- * {@code android.view.MotionEvent.ACTION_DOWN} event to the last {@link DreamTouchHandler}
+ * {@code android.view.MotionEvent.ACTION_DOWN} event to the last {@link TouchHandler}
* stopping interception of gestures. If no gesture is intercepted, the session continues
- * indefinitely. {@link DreamTouchHandler} have the ability to create a stack of sessions, which
+ * indefinitely. {@link TouchHandler} have the ability to create a stack of sessions, which
* allows for motion logic to be captured in modal states.
*/
-public interface DreamTouchHandler {
+public interface TouchHandler {
/**
- * A touch session captures the interaction surface of a {@link DreamTouchHandler}. Clients
+ * A touch session captures the interaction surface of a {@link TouchHandler}. Clients
* register listeners as desired to participate in motion/gesture callbacks.
*/
interface TouchSession {
interface Callback {
+ /**
+ * Invoked when the session has been removed.
+ */
void onRemoved();
}
+ /**
+ * Registers a callback to be notified when there are updates to the {@link TouchSession}.
+ */
void registerCallback(Callback callback);
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index 3b22b31..e7e12ba 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -37,10 +37,10 @@
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
+import com.android.systemui.ambient.touch.dagger.InputSessionComponent;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.touch.dagger.InputSessionComponent;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.util.display.DisplayHelper;
@@ -58,12 +58,12 @@
import javax.inject.Inject;
/**
- * {@link DreamOverlayTouchMonitor} is responsible for monitoring touches and gestures over the
+ * {@link TouchMonitor} is responsible for monitoring touches and gestures over the
* dream overlay and redirecting them to a set of listeners. This monitor is in charge of figuring
* out when listeners are eligible for receiving touches and filtering the listener pool if
* touches are consumed.
*/
-public class DreamOverlayTouchMonitor {
+public class TouchMonitor {
// This executor is used to protect {@code mActiveTouchSessions} from being modified
// concurrently. Any operation that adds or removes values should use this executor.
public String TAG = "DreamOverlayTouchMonitor";
@@ -83,11 +83,10 @@
};
-
/**
* Adds a new {@link TouchSessionImpl} to participate in receiving future touches and gestures.
*/
- private ListenableFuture<DreamTouchHandler.TouchSession> push(
+ private ListenableFuture<TouchHandler.TouchSession> push(
TouchSessionImpl touchSessionImpl) {
return CallbackToFutureAdapter.getFuture(completer -> {
mMainExecutor.execute(() -> {
@@ -110,7 +109,7 @@
/**
* Removes a {@link TouchSessionImpl} from receiving further updates.
*/
- private ListenableFuture<DreamTouchHandler.TouchSession> pop(
+ private ListenableFuture<TouchHandler.TouchSession> pop(
TouchSessionImpl touchSessionImpl) {
return CallbackToFutureAdapter.getFuture(completer -> {
mMainExecutor.execute(() -> {
@@ -140,11 +139,11 @@
}
/**
- * {@link TouchSessionImpl} implements {@link DreamTouchHandler.TouchSession} for
- * {@link DreamOverlayTouchMonitor}. It enables the monitor to access the associated listeners
+ * {@link TouchSessionImpl} implements {@link TouchHandler.TouchSession} for
+ * {@link TouchMonitor}. It enables the monitor to access the associated listeners
* and provides the associated client with access to the monitor.
*/
- private static class TouchSessionImpl implements DreamTouchHandler.TouchSession {
+ private static class TouchSessionImpl implements TouchHandler.TouchSession {
private final HashSet<InputChannelCompat.InputEventListener> mEventListeners =
new HashSet<>();
private final HashSet<GestureDetector.OnGestureListener> mGestureListeners =
@@ -152,10 +151,10 @@
private final HashSet<Callback> mCallbacks = new HashSet<>();
private final TouchSessionImpl mPredecessor;
- private final DreamOverlayTouchMonitor mTouchMonitor;
+ private final TouchMonitor mTouchMonitor;
private final Rect mBounds;
- TouchSessionImpl(DreamOverlayTouchMonitor touchMonitor, Rect bounds,
+ TouchSessionImpl(TouchMonitor touchMonitor, Rect bounds,
TouchSessionImpl predecessor) {
mPredecessor = predecessor;
mTouchMonitor = touchMonitor;
@@ -179,12 +178,12 @@
}
@Override
- public ListenableFuture<DreamTouchHandler.TouchSession> push() {
+ public ListenableFuture<TouchHandler.TouchSession> push() {
return mTouchMonitor.push(this);
}
@Override
- public ListenableFuture<DreamTouchHandler.TouchSession> pop() {
+ public ListenableFuture<TouchHandler.TouchSession> pop() {
return mTouchMonitor.pop(this);
}
@@ -275,10 +274,10 @@
});
}
mCurrentInputSession = mInputSessionFactory.create(
- "dreamOverlay",
- mInputEventListener,
- mOnGestureListener,
- true)
+ "dreamOverlay",
+ mInputEventListener,
+ mOnGestureListener,
+ true)
.getInputSession();
}
@@ -323,21 +322,21 @@
private final HashSet<TouchSessionImpl> mActiveTouchSessions = new HashSet<>();
- private final Collection<DreamTouchHandler> mHandlers;
+ private final Collection<TouchHandler> mHandlers;
private final DisplayHelper mDisplayHelper;
private boolean mStopMonitoringPending;
private InputChannelCompat.InputEventListener mInputEventListener =
new InputChannelCompat.InputEventListener() {
- @Override
- public void onInputEvent(InputEvent ev) {
- // No Active sessions are receiving touches. Create sessions for each listener
- if (mActiveTouchSessions.isEmpty()) {
- final HashMap<DreamTouchHandler, DreamTouchHandler.TouchSession> sessionMap =
- new HashMap<>();
+ @Override
+ public void onInputEvent(InputEvent ev) {
+ // No Active sessions are receiving touches. Create sessions for each listener
+ if (mActiveTouchSessions.isEmpty()) {
+ final HashMap<TouchHandler, TouchHandler.TouchSession> sessionMap =
+ new HashMap<>();
- for (DreamTouchHandler handler : mHandlers) {
+ for (TouchHandler handler : mHandlers) {
if (!handler.isEnabled()) {
continue;
}
@@ -349,46 +348,49 @@
exclusionRect = getCurrentExclusionRect();
}
handler.getTouchInitiationRegion(
- maxBounds, initiationRegion, exclusionRect);
+ maxBounds, initiationRegion, exclusionRect);
- if (!initiationRegion.isEmpty()) {
- // Initiation regions require a motion event to determine pointer location
- // within the region.
- if (!(ev instanceof MotionEvent)) {
- continue;
+ if (!initiationRegion.isEmpty()) {
+ // Initiation regions require a motion event to determine pointer
+ // location
+ // within the region.
+ if (!(ev instanceof MotionEvent)) {
+ continue;
+ }
+
+ final MotionEvent motionEvent = (MotionEvent) ev;
+
+ // If the touch event is outside the region, then ignore.
+ if (!initiationRegion.contains(Math.round(motionEvent.getX()),
+ Math.round(motionEvent.getY()))) {
+ continue;
+ }
+ }
+
+ final TouchSessionImpl sessionStack = new TouchSessionImpl(
+ TouchMonitor.this, maxBounds, null);
+ mActiveTouchSessions.add(sessionStack);
+ sessionMap.put(handler, sessionStack);
}
- final MotionEvent motionEvent = (MotionEvent) ev;
-
- // If the touch event is outside the region, then ignore.
- if (!initiationRegion.contains(Math.round(motionEvent.getX()),
- Math.round(motionEvent.getY()))) {
- continue;
- }
+ // Informing handlers of new sessions is delayed until we have all
+ // created so the
+ // final session is correct.
+ sessionMap.forEach((dreamTouchHandler, touchSession)
+ -> dreamTouchHandler.onSessionStart(touchSession));
}
- final TouchSessionImpl sessionStack = new TouchSessionImpl(
- DreamOverlayTouchMonitor.this, maxBounds, null);
- mActiveTouchSessions.add(sessionStack);
- sessionMap.put(handler, sessionStack);
+ // Find active sessions and invoke on InputEvent.
+ mActiveTouchSessions.stream()
+ .map(touchSessionStack -> touchSessionStack.getEventListeners())
+ .flatMap(Collection::stream)
+ .forEach(inputEventListener -> inputEventListener.onInputEvent(ev));
}
- // Informing handlers of new sessions is delayed until we have all created so the
- // final session is correct.
- sessionMap.forEach((dreamTouchHandler, touchSession)
- -> dreamTouchHandler.onSessionStart(touchSession));
- }
-
- // Find active sessions and invoke on InputEvent.
- mActiveTouchSessions.stream()
- .map(touchSessionStack -> touchSessionStack.getEventListeners())
- .flatMap(Collection::stream)
- .forEach(inputEventListener -> inputEventListener.onInputEvent(ev));
- }
- private Rect getCurrentExclusionRect() {
- return mExclusionRect;
- }
- };
+ private Rect getCurrentExclusionRect() {
+ return mExclusionRect;
+ }
+ };
/**
* The {@link Evaluator} interface allows for callers to inspect a listener from the
@@ -401,71 +403,75 @@
private GestureDetector.OnGestureListener mOnGestureListener =
new GestureDetector.OnGestureListener() {
- private boolean evaluate(Evaluator evaluator) {
- final Set<TouchSessionImpl> consumingSessions = new HashSet<>();
+ private boolean evaluate(Evaluator evaluator) {
+ final Set<TouchSessionImpl> consumingSessions = new HashSet<>();
- // When a gesture is consumed, it is assumed that all touches for the current session
- // should be directed only to those TouchSessions until those sessions are popped. All
- // non-participating sessions are removed from receiving further updates with
- // {@link DreamOverlayTouchMonitor#isolate}.
- final boolean eventConsumed = mActiveTouchSessions.stream()
- .map(touchSession -> {
- boolean consume = touchSession.getGestureListeners()
- .stream()
- .map(listener -> evaluator.evaluate(listener))
- .anyMatch(consumed -> consumed);
+ // When a gesture is consumed, it is assumed that all touches for the current
+ // session
+ // should be directed only to those TouchSessions until those sessions are
+ // popped. All
+ // non-participating sessions are removed from receiving further updates with
+ // {@link DreamOverlayTouchMonitor#isolate}.
+ final boolean eventConsumed = mActiveTouchSessions.stream()
+ .map(touchSession -> {
+ boolean consume = touchSession.getGestureListeners()
+ .stream()
+ .map(listener -> evaluator.evaluate(listener))
+ .anyMatch(consumed -> consumed);
- if (consume) {
- consumingSessions.add(touchSession);
- }
- return consume;
- }).anyMatch(consumed -> consumed);
+ if (consume) {
+ consumingSessions.add(touchSession);
+ }
+ return consume;
+ }).anyMatch(consumed -> consumed);
- if (eventConsumed) {
- DreamOverlayTouchMonitor.this.isolate(consumingSessions);
- }
+ if (eventConsumed) {
+ TouchMonitor.this.isolate(consumingSessions);
+ }
- return eventConsumed;
- }
+ return eventConsumed;
+ }
- // This method is called for gesture events that cannot be consumed.
- private void observe(Consumer<GestureDetector.OnGestureListener> consumer) {
- mActiveTouchSessions.stream()
- .map(touchSession -> touchSession.getGestureListeners())
- .flatMap(Collection::stream)
- .forEach(listener -> consumer.accept(listener));
- }
+ // This method is called for gesture events that cannot be consumed.
+ private void observe(Consumer<GestureDetector.OnGestureListener> consumer) {
+ mActiveTouchSessions.stream()
+ .map(touchSession -> touchSession.getGestureListeners())
+ .flatMap(Collection::stream)
+ .forEach(listener -> consumer.accept(listener));
+ }
- @Override
- public boolean onDown(MotionEvent e) {
- return evaluate(listener -> listener.onDown(e));
- }
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return evaluate(listener -> listener.onDown(e));
+ }
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- return evaluate(listener -> listener.onFling(e1, e2, velocityX, velocityY));
- }
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ return evaluate(listener -> listener.onFling(e1, e2, velocityX, velocityY));
+ }
- @Override
- public void onLongPress(MotionEvent e) {
- observe(listener -> listener.onLongPress(e));
- }
+ @Override
+ public void onLongPress(MotionEvent e) {
+ observe(listener -> listener.onLongPress(e));
+ }
- @Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
- return evaluate(listener -> listener.onScroll(e1, e2, distanceX, distanceY));
- }
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+ float distanceY) {
+ return evaluate(listener -> listener.onScroll(e1, e2, distanceX, distanceY));
+ }
- @Override
- public void onShowPress(MotionEvent e) {
- observe(listener -> listener.onShowPress(e));
- }
+ @Override
+ public void onShowPress(MotionEvent e) {
+ observe(listener -> listener.onShowPress(e));
+ }
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- return evaluate(listener -> listener.onSingleTapUp(e));
- }
- };
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return evaluate(listener -> listener.onSingleTapUp(e));
+ }
+ };
private InputSessionComponent.Factory mInputSessionFactory;
private InputSession mCurrentInputSession;
@@ -474,25 +480,27 @@
/**
- * Designated constructor for {@link DreamOverlayTouchMonitor}
- * @param executor This executor will be used for maintaining the active listener list to avoid
- * concurrent modification.
- * @param lifecycle {@link DreamOverlayTouchMonitor} will listen to this lifecycle to determine
- * whether touch monitoring should be active.
+ * Designated constructor for {@link TouchMonitor}
+ *
+ * @param executor This executor will be used for maintaining the active listener
+ * list to avoid
+ * concurrent modification.
+ * @param lifecycle {@link TouchMonitor} will listen to this lifecycle to determine
+ * whether touch monitoring should be active.
* @param inputSessionFactory This factory will generate the {@link InputSession} requested by
* the monitor. Each session should be unique and valid when
* returned.
- * @param handlers This set represents the {@link DreamTouchHandler} instances that will
- * participate in touch handling.
+ * @param handlers This set represents the {@link TouchHandler} instances that will
+ * participate in touch handling.
*/
@Inject
- public DreamOverlayTouchMonitor(
+ public TouchMonitor(
@Main Executor executor,
@Background Executor backgroundExecutor,
Lifecycle lifecycle,
InputSessionComponent.Factory inputSessionFactory,
DisplayHelper displayHelper,
- Set<DreamTouchHandler> handlers,
+ Set<TouchHandler> handlers,
IWindowManager windowManagerService,
@DisplayId int displayId) {
mDisplayId = displayId;
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchComponent.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchComponent.kt
new file mode 100644
index 0000000..390e53b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchComponent.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.ambient.touch.dagger
+
+import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.ambient.dagger.AmbientModule.Companion.TOUCH_HANDLERS
+import com.android.systemui.ambient.touch.TouchHandler
+import com.android.systemui.ambient.touch.TouchMonitor
+import dagger.BindsInstance
+import dagger.Subcomponent
+import javax.inject.Named
+
+/**
+ * {@link AmbientTouchComponent} can be used for setting up a touch environment over the entire
+ * display surface. This allows for implementing behaviors such as swiping up to bring up the
+ * bouncer.
+ */
+@Subcomponent(modules = [AmbientTouchModule::class, ShadeModule::class, BouncerSwipeModule::class])
+interface AmbientTouchComponent {
+ @Subcomponent.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance lifecycleOwner: LifecycleOwner,
+ @BindsInstance
+ @Named(TOUCH_HANDLERS)
+ touchHandlers: Set<@JvmSuppressWildcards TouchHandler>
+ ): AmbientTouchComponent
+ }
+
+ /** Builds a [TouchMonitor] */
+ fun getTouchMonitor(): TouchMonitor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
new file mode 100644
index 0000000..a4924d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.ambient.touch.dagger
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.ambient.dagger.AmbientModule
+import com.android.systemui.ambient.touch.TouchHandler
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+import javax.inject.Named
+
+@Module
+interface AmbientTouchModule {
+ companion object {
+ @JvmStatic
+ @Provides
+ fun providesLifecycle(lifecycleOwner: LifecycleOwner): Lifecycle {
+ return lifecycleOwner.lifecycle
+ }
+
+ @Provides
+ @ElementsIntoSet
+ fun providesDreamTouchHandlers(
+ @Named(AmbientModule.TOUCH_HANDLERS)
+ touchHandlers: Set<@JvmSuppressWildcards TouchHandler>
+ ): Set<@JvmSuppressWildcards TouchHandler> {
+ return touchHandlers
+ }
+
+ const val INPUT_SESSION_NAME = "INPUT_SESSION_NAME"
+ const val PILFER_ON_GESTURE_CONSUME = "PILFER_ON_GESTURE_CONSUME"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/BouncerSwipeModule.java
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/BouncerSwipeModule.java
index a5db2ff..dac2d8e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/BouncerSwipeModule.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.dagger;
+package com.android.systemui.ambient.touch.dagger;
import android.animation.ValueAnimator;
import android.content.res.Resources;
import android.util.TypedValue;
import android.view.VelocityTracker;
+import com.android.systemui.ambient.touch.BouncerSwipeTouchHandler;
+import com.android.systemui.ambient.touch.TouchHandler;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeViewController;
import com.android.wm.shell.animation.FlingAnimationUtils;
@@ -66,7 +66,7 @@
*/
@Provides
@IntoSet
- public static DreamTouchHandler providesBouncerSwipeTouchHandler(
+ public static TouchHandler providesBouncerSwipeTouchHandler(
BouncerSwipeTouchHandler touchHandler) {
return touchHandler;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionComponent.java
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionComponent.java
index 0b14521..203fb64 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionComponent.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.dagger;
+package com.android.systemui.ambient.touch.dagger;
-import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.INPUT_SESSION_NAME;
-import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.PILFER_ON_GESTURE_CONSUME;
+import static com.android.systemui.ambient.touch.dagger.AmbientTouchModule.INPUT_SESSION_NAME;
+import static com.android.systemui.ambient.touch.dagger.AmbientTouchModule.PILFER_ON_GESTURE_CONSUME;
import android.view.GestureDetector;
-import com.android.systemui.dreams.touch.InputSession;
+import com.android.systemui.ambient.touch.InputSession;
import com.android.systemui.shared.system.InputChannelCompat;
import dagger.BindsInstance;
@@ -42,6 +42,7 @@
*/
@Subcomponent.Factory
interface Factory {
+ /** */
InputSessionComponent create(@Named(INPUT_SESSION_NAME) @BindsInstance String name,
@BindsInstance InputChannelCompat.InputEventListener inputEventListener,
@BindsInstance GestureDetector.OnGestureListener gestureListener,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionModule.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionModule.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionModule.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionModule.java
index dfab666..99dbdee 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionModule.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionModule.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.dagger;
+package com.android.systemui.ambient.touch.dagger;
-import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.INPUT_SESSION_NAME;
+import static com.android.systemui.ambient.touch.dagger.AmbientTouchModule.INPUT_SESSION_NAME;
import android.view.GestureDetector;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java
similarity index 62%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java
index 0f08d37..bc2f354 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.dagger;
+package com.android.systemui.ambient.touch.dagger;
import android.content.res.Resources;
+import com.android.systemui.ambient.touch.ShadeTouchHandler;
+import com.android.systemui.ambient.touch.TouchHandler;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.touch.CommunalTouchHandler;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
-import com.android.systemui.dreams.touch.ShadeTouchHandler;
import com.android.systemui.res.R;
import dagger.Binds;
@@ -43,23 +42,14 @@
public static final String NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT =
"notification_shade_gesture_initiation_height";
- /** Width of swipe gesture edge to show communal hub. */
- public static final String COMMUNAL_GESTURE_INITIATION_WIDTH =
- "communal_gesture_initiation_width";
-
/**
* Provides {@link ShadeTouchHandler} to handle notification swipe down over dream.
*/
@Binds
@IntoSet
- public abstract DreamTouchHandler providesNotificationShadeTouchHandler(
+ public abstract TouchHandler providesNotificationShadeTouchHandler(
ShadeTouchHandler touchHandler);
- /** Provides {@link CommunalTouchHandler}. */
- @Binds
- @IntoSet
- public abstract DreamTouchHandler bindCommunalTouchHandler(CommunalTouchHandler touchHandler);
-
/**
* Provides the height of the gesture area for notification swipe down.
*/
@@ -69,12 +59,4 @@
return resources.getDimensionPixelSize(R.dimen.dream_overlay_status_bar_height);
}
- /**
- * Provides the width of the gesture area for swiping open communal hub.
- */
- @Provides
- @Named(COMMUNAL_GESTURE_INITIATION_WIDTH)
- public static int providesCommunalGestureInitiationWidth(@Main Resources resources) {
- return resources.getDimensionPixelSize(R.dimen.communal_gesture_initiation_width);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
index 776b7bd..94c9982 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimController.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimController.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimController.java
index 01e4d04..c453ddb 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
import android.os.PowerManager;
import android.os.SystemClock;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimController.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
index 61629ef..0054352 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimManager.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimManager.java
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimManager.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimManager.java
index 0d0dff6..676221d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimManager.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
-import static com.android.systemui.dreams.touch.scrim.dagger.ScrimModule.BOUNCERLESS_SCRIM_CONTROLLER;
-import static com.android.systemui.dreams.touch.scrim.dagger.ScrimModule.BOUNCER_SCRIM_CONTROLLER;
+import static com.android.systemui.ambient.touch.scrim.dagger.ScrimModule.BOUNCERLESS_SCRIM_CONTROLLER;
+import static com.android.systemui.ambient.touch.scrim.dagger.ScrimModule.BOUNCER_SCRIM_CONTROLLER;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.policy.KeyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/dagger/ScrimModule.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/dagger/ScrimModule.java
similarity index 79%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/dagger/ScrimModule.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/dagger/ScrimModule.java
index 4ad5161..b07029b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/dagger/ScrimModule.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/dagger/ScrimModule.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.scrim.dagger;
+package com.android.systemui.ambient.touch.scrim.dagger;
-import com.android.systemui.dreams.touch.scrim.BouncerScrimController;
-import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
-import com.android.systemui.dreams.touch.scrim.ScrimController;
+import com.android.systemui.ambient.touch.scrim.BouncerScrimController;
+import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
+import com.android.systemui.ambient.touch.scrim.ScrimController;
import dagger.Module;
import dagger.Provides;
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 454ed27..a9f985f 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -36,7 +36,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.onSubscriberAdded
@@ -186,7 +186,6 @@
constructor(
@Application private val applicationScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
- flags: SceneContainerFlags,
private val clock: SystemClock,
private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
private val userRepository: UserRepository,
@@ -255,7 +254,7 @@
override val hasLockoutOccurred: StateFlow<Boolean> = _hasLockoutOccurred.asStateFlow()
init {
- if (flags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
// Hydrate failedAuthenticationAttempts initially and whenever the selected user
// changes.
applicationScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 61d1c71..4a60d19 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -323,7 +323,7 @@
overlayParams = updatedOverlayParams
sensorBounds = updatedOverlayParams.sensorBounds
getTouchOverlay()?.let {
- if (addViewRunnable != null) {
+ if (addViewRunnable == null) {
// Only updateViewLayout if there's no pending view to add to WM.
// If there is a pending view, that means the view hasn't been added yet so there's
// no need to update any layouts. Instead the correct params will be used when the
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
index 66b7d7a..d9d3715 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -26,6 +26,7 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieOnCompositionLoadedListener
import com.android.settingslib.widget.LottieColorUtils
import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
@@ -77,6 +78,8 @@
}
launch {
+ var lottieOnCompositionLoadedListener: LottieOnCompositionLoadedListener? = null
+
combine(viewModel.activeAuthType, viewModel.iconSize, ::Pair).collect {
(activeAuthType, iconSize) ->
// Every time after bp shows, [isIconViewLoaded] is set to false in
@@ -94,10 +97,18 @@
* TODO(b/288175072): May be able to remove this once constraint
* layout is implemented
*/
- iconView.removeAllLottieOnCompositionLoadedListener()
- iconView.addLottieOnCompositionLoadedListener {
- promptViewModel.setIsIconViewLoaded(true)
+ if (lottieOnCompositionLoadedListener != null) {
+ iconView.removeLottieOnCompositionLoadedListener(
+ lottieOnCompositionLoadedListener!!
+ )
}
+ lottieOnCompositionLoadedListener =
+ LottieOnCompositionLoadedListener {
+ promptViewModel.setIsIconViewLoaded(true)
+ }
+ iconView.addLottieOnCompositionLoadedListener(
+ lottieOnCompositionLoadedListener!!
+ )
}
AuthType.Face -> {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
index 2797b7b..07e30ce 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics.ui.viewmodel
+import com.android.keyguard.logging.DeviceEntryIconLogger
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -24,6 +25,8 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
/**
* View model for the UdfpsTouchOverlay for when UDFPS is being requested for device entry. Handles
@@ -37,16 +40,30 @@
deviceEntryIconViewModel: DeviceEntryIconViewModel,
alternateBouncerInteractor: AlternateBouncerInteractor,
systemUIDialogManager: SystemUIDialogManager,
+ logger: DeviceEntryIconLogger,
) : UdfpsTouchOverlayViewModel {
+ private val deviceEntryViewAlphaIsMostlyVisible: Flow<Boolean> =
+ deviceEntryIconViewModel.deviceEntryViewAlpha
+ .map { it > ALLOW_TOUCH_ALPHA_THRESHOLD }
+ .distinctUntilChanged()
override val shouldHandleTouches: Flow<Boolean> =
combine(
- deviceEntryIconViewModel.deviceEntryViewAlpha,
- alternateBouncerInteractor.isVisible,
- systemUIDialogManager.hideAffordancesRequest
- ) { deviceEntryViewAlpha, alternateBouncerVisible, hideAffordancesRequest ->
- (deviceEntryViewAlpha > ALLOW_TOUCH_ALPHA_THRESHOLD && !hideAffordancesRequest) ||
- alternateBouncerVisible
- }
+ deviceEntryViewAlphaIsMostlyVisible,
+ alternateBouncerInteractor.isVisible,
+ systemUIDialogManager.hideAffordancesRequest,
+ ) { canTouchDeviceEntryViewAlpha, alternateBouncerVisible, hideAffordancesRequest ->
+ val shouldHandleTouches =
+ (canTouchDeviceEntryViewAlpha && !hideAffordancesRequest) ||
+ alternateBouncerVisible
+ logger.logDeviceEntryUdfpsTouchOverlayShouldHandleTouches(
+ shouldHandleTouches,
+ canTouchDeviceEntryViewAlpha,
+ alternateBouncerVisible,
+ hideAffordancesRequest
+ )
+ shouldHandleTouches
+ }
+ .distinctUntilChanged()
companion object {
// only allow touches if the view is still mostly visible
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
index 66aeda6..207f7db 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
@@ -217,10 +217,13 @@
mSwitchBroadcast.setText(mContext.getString(
R.string.bt_le_audio_broadcast_dialog_switch_app, switchBroadcastApp), null);
mSwitchBroadcast.setOnClickListener((view) -> startSwitchBroadcast());
- changeOutput.setOnClickListener((view) -> {
- mMediaOutputDialogManager.createAndShow(mOutputPackageName, true, null);
- dialog.dismiss();
- });
+ changeOutput.setOnClickListener(
+ (view) -> {
+ // TODO: b/321969740 - Take the userHandle as a parameter and pass it through.
+ // The package name is not sufficient to unambiguously identify an app.
+ mMediaOutputDialogManager.createAndShow(mOutputPackageName, true, null, null);
+ dialog.dismiss();
+ });
cancelBtn.setOnClickListener((view) -> {
if (DEBUG) {
Log.d(TAG, "BroadcastDialog dismiss.");
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
new file mode 100644
index 0000000..e44f054
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.bluetooth.qsdialog
+
+import androidx.annotation.StringRes
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
+
+internal sealed class AudioSharingButtonState {
+ object Gone : AudioSharingButtonState()
+ data class Visible(@StringRes val resId: Int) : AudioSharingButtonState()
+}
+
+/** Holds business logic for the audio sharing state. */
+@SysUISingleton
+internal class AudioSharingInteractor
+@Inject
+constructor(
+ private val localBluetoothManager: LocalBluetoothManager?,
+ bluetoothStateInteractor: BluetoothStateInteractor,
+ deviceItemInteractor: DeviceItemInteractor,
+ @Application private val coroutineScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+ /** Flow representing the update of AudioSharingButtonState. */
+ internal val audioSharingButtonStateUpdate: Flow<AudioSharingButtonState> =
+ combine(
+ bluetoothStateInteractor.bluetoothStateUpdate,
+ deviceItemInteractor.deviceItemUpdate
+ ) { bluetoothState, deviceItem ->
+ getButtonState(bluetoothState, deviceItem)
+ }
+ .flowOn(backgroundDispatcher)
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
+ initialValue = AudioSharingButtonState.Gone
+ )
+
+ private fun getButtonState(
+ bluetoothState: Boolean,
+ deviceItem: List<DeviceItem>
+ ): AudioSharingButtonState {
+ return when {
+ // Don't show button when bluetooth is off
+ !bluetoothState -> AudioSharingButtonState.Gone
+ // Show sharing audio when broadcasting
+ BluetoothUtils.isBroadcasting(localBluetoothManager) ->
+ AudioSharingButtonState.Visible(
+ R.string.quick_settings_bluetooth_audio_sharing_button_sharing
+ )
+ // When not broadcasting, don't show button if there's connected source in any device
+ deviceItem.any {
+ BluetoothUtils.hasConnectedBroadcastSource(
+ it.cachedBluetoothDevice,
+ localBluetoothManager
+ )
+ } -> AudioSharingButtonState.Gone
+ // Show audio sharing when there's a connected LE audio device
+ deviceItem.any { BluetoothUtils.isActiveLeAudioDevice(it.cachedBluetoothDevice) } ->
+ AudioSharingButtonState.Visible(
+ R.string.quick_settings_bluetooth_audio_sharing_button
+ )
+ else -> AudioSharingButtonState.Gone
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
index 94d7af7..17f9e63 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
@@ -25,12 +25,17 @@
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
/** Holds business logic for the Bluetooth Dialog's bluetooth and device connection state */
@SysUISingleton
@@ -40,9 +45,10 @@
private val localBluetoothManager: LocalBluetoothManager?,
private val logger: BluetoothTileDialogLogger,
@Application private val coroutineScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) {
- internal val bluetoothStateUpdate: StateFlow<Boolean?> =
+ internal val bluetoothStateUpdate: StateFlow<Boolean> =
conflatedCallbackFlow {
val listener =
object : BluetoothCallback {
@@ -64,16 +70,22 @@
localBluetoothManager?.eventManager?.registerCallback(listener)
awaitClose { localBluetoothManager?.eventManager?.unregisterCallback(listener) }
}
+ .onStart { emit(isBluetoothEnabled()) }
+ .flowOn(backgroundDispatcher)
.stateIn(
coroutineScope,
SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
- initialValue = null
+ initialValue = false
)
- internal var isBluetoothEnabled: Boolean
- get() = localBluetoothManager?.bluetoothAdapter?.isEnabled == true
- set(value) {
- if (isBluetoothEnabled != value) {
+ suspend fun isBluetoothEnabled(): Boolean =
+ withContext(backgroundDispatcher) {
+ localBluetoothManager?.bluetoothAdapter?.isEnabled == true
+ }
+
+ suspend fun setBluetoothEnabled(value: Boolean) {
+ withContext(backgroundDispatcher) {
+ if (isBluetoothEnabled() != value) {
localBluetoothManager?.bluetoothAdapter?.apply {
if (value) enable() else disable()
logger.logBluetoothState(
@@ -83,6 +95,7 @@
}
}
}
+ }
companion object {
private const val TAG = "BtStateInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index c7d171d..dd8c0df 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -27,6 +27,7 @@
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
+import android.widget.Button
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.Switch
@@ -59,7 +60,6 @@
internal constructor(
@Assisted private val initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
@Assisted private val cachedContentHeight: Int,
- @Assisted private val bluetoothToggleInitialValue: Boolean,
@Assisted private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
@Assisted private val dismissListener: Runnable,
@Main private val mainDispatcher: CoroutineDispatcher,
@@ -69,8 +69,7 @@
private val systemuiDialogFactory: SystemUIDialog.Factory,
) : SystemUIDialog.Delegate {
- private val mutableBluetoothStateToggle: MutableStateFlow<Boolean> =
- MutableStateFlow(bluetoothToggleInitialValue)
+ private val mutableBluetoothStateToggle: MutableStateFlow<Boolean?> = MutableStateFlow(null)
internal val bluetoothStateToggle
get() = mutableBluetoothStateToggle.asStateFlow()
@@ -99,7 +98,6 @@
fun create(
initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
cachedContentHeight: Int,
- bluetoothEnabled: Boolean,
dialogCallback: BluetoothTileDialogCallback,
dimissListener: Runnable
): BluetoothTileDialogDelegate
@@ -130,6 +128,9 @@
getPairNewDeviceButton(dialog).setOnClickListener {
bluetoothTileDialogCallback.onPairNewDeviceClicked(it)
}
+ getAudioSharingButtonView(dialog).setOnClickListener {
+ bluetoothTileDialogCallback.onAudioSharingButtonClicked(it)
+ }
getScrollViewContent(dialog).apply {
minimumHeight =
resources.getDimensionPixelSize(initialUiProperties.scrollViewMinHeightResId)
@@ -211,9 +212,19 @@
getAutoOnToggleInfoTextView(dialog).text = dialog.context.getString(infoResId)
}
+ internal fun onAudioSharingButtonUpdated(
+ dialog: SystemUIDialog,
+ visibility: Int,
+ label: String?
+ ) {
+ getAudioSharingButtonView(dialog).apply {
+ this.visibility = visibility
+ label?.let { text = it }
+ }
+ }
+
private fun setupToggle(dialog: SystemUIDialog) {
val toggleView = getToggleView(dialog)
- toggleView.isChecked = bluetoothToggleInitialValue
toggleView.setOnCheckedChangeListener { view, isChecked ->
mutableBluetoothStateToggle.value = isChecked
view.apply {
@@ -259,6 +270,10 @@
return dialog.requireViewById(R.id.bluetooth_auto_on_toggle)
}
+ private fun getAudioSharingButtonView(dialog: SystemUIDialog): Button {
+ return dialog.requireViewById(R.id.audio_sharing_button)
+ }
+
private fun getAutoOnToggleView(dialog: SystemUIDialog): View {
return dialog.requireViewById(R.id.bluetooth_auto_on_toggle_layout)
}
@@ -412,6 +427,8 @@
const val ACTION_PREVIOUSLY_CONNECTED_DEVICE =
"com.android.settings.PREVIOUSLY_CONNECTED_DEVICE"
const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
+ const val ACTION_AUDIO_SHARING =
+ "com.google.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"
const val DISABLED_ALPHA = 0.3f
const val ENABLED_ALPHA = 1f
const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
index add1647..b592b8ed 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
@@ -30,9 +30,12 @@
@UiEvent(doc = "Connected device clicked to active") CONNECTED_DEVICE_SET_ACTIVE(1499),
@UiEvent(doc = "Saved clicked to connect") SAVED_DEVICE_CONNECT(1500),
@UiEvent(doc = "Active device clicked to disconnect") ACTIVE_DEVICE_DISCONNECT(1507),
+ @UiEvent(doc = "Audio sharing device clicked, do nothing") AUDIO_SHARING_DEVICE_CLICKED(1699),
@UiEvent(doc = "Connected other device clicked to disconnect")
CONNECTED_OTHER_DEVICE_DISCONNECT(1508),
- @UiEvent(doc = "The auto on toggle is clicked") BLUETOOTH_AUTO_ON_TOGGLE_CLICKED(1617);
+ @UiEvent(doc = "The auto on toggle is clicked") BLUETOOTH_AUTO_ON_TOGGLE_CLICKED(1617),
+ @UiEvent(doc = "The audio sharing button is clicked")
+ BLUETOOTH_AUDIO_SHARING_BUTTON_CLICKED(1700);
override fun getId() = metricId
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index e65b657..eb919e3 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -28,9 +28,11 @@
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.systemui.Prefs
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_AUDIO_SHARING
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
@@ -61,6 +63,7 @@
private val deviceItemInteractor: DeviceItemInteractor,
private val bluetoothStateInteractor: BluetoothStateInteractor,
private val bluetoothAutoOnInteractor: BluetoothAutoOnInteractor,
+ private val audioSharingInteractor: AudioSharingInteractor,
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val activityStarter: ActivityStarter,
private val uiEventLogger: UiEventLogger,
@@ -119,7 +122,8 @@
dialog,
it.take(MAX_DEVICE_ITEM_ENTRY),
showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
- showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
+ showPairNewDevice =
+ bluetoothStateInteractor.isBluetoothEnabled()
)
animateProgressBar(dialog, false)
}
@@ -142,10 +146,25 @@
}
.launchIn(this)
+ if (BluetoothUtils.isAudioSharingEnabled()) {
+ audioSharingInteractor.audioSharingButtonStateUpdate
+ .onEach {
+ if (it is AudioSharingButtonState.Visible) {
+ dialogDelegate.onAudioSharingButtonUpdated(
+ dialog,
+ VISIBLE,
+ context.getString(it.resId)
+ )
+ } else {
+ dialogDelegate.onAudioSharingButtonUpdated(dialog, GONE, null)
+ }
+ }
+ .launchIn(this)
+ }
+
// bluetoothStateUpdate is emitted when bluetooth on/off state is changed, re-fetch
// the device item list.
bluetoothStateInteractor.bluetoothStateUpdate
- .filterNotNull()
.onEach {
dialogDelegate.onBluetoothStateUpdated(
dialog,
@@ -165,9 +184,10 @@
// bluetoothStateToggle is emitted when user toggles the bluetooth state switch,
// send the new value to the bluetoothStateInteractor and animate the progress bar.
dialogDelegate.bluetoothStateToggle
+ .filterNotNull()
.onEach {
dialogDelegate.animateProgressBar(dialog, true)
- bluetoothStateInteractor.isBluetoothEnabled = it
+ bluetoothStateInteractor.setBluetoothEnabled(it)
}
.launchIn(this)
@@ -222,11 +242,10 @@
return bluetoothDialogDelegateFactory.create(
UiProperties.build(
- bluetoothStateInteractor.isBluetoothEnabled,
+ bluetoothStateInteractor.isBluetoothEnabled(),
isAutoOnToggleFeatureAvailable()
),
cachedContentHeight,
- bluetoothStateInteractor.isBluetoothEnabled,
this@BluetoothTileDialogViewModel,
{ cancelJob() }
)
@@ -256,6 +275,11 @@
startSettingsActivity(Intent(ACTION_PAIR_NEW_DEVICE), view)
}
+ override fun onAudioSharingButtonClicked(view: View) {
+ uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_AUDIO_SHARING_BUTTON_CLICKED)
+ startSettingsActivity(Intent(ACTION_AUDIO_SHARING), view)
+ }
+
private fun cancelJob() {
job?.cancel()
job = null
@@ -312,4 +336,5 @@
fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View)
fun onSeeAllClicked(view: View)
fun onPairNewDeviceClicked(view: View)
+ fun onAudioSharingButtonClicked(view: View)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
index dc5efef..0ea98d1 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
@@ -37,6 +37,7 @@
enum class DeviceItemType {
ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+ AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
CONNECTED_BLUETOOTH_DEVICE,
SAVED_BLUETOOTH_DEVICE,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index f04ba75..49d0847 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -21,13 +21,16 @@
import android.media.AudioManager
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.flags.Flags
+import com.android.settingslib.flags.Flags.enableLeAudioSharing
import com.android.systemui.res.R
private val backgroundOn = R.drawable.settingslib_switch_bar_bg_on
private val backgroundOff = R.drawable.bluetooth_tile_dialog_bg_off
private val backgroundOffBusy = R.drawable.bluetooth_tile_dialog_bg_off_busy
private val connected = R.string.quick_settings_bluetooth_device_connected
+private val audioSharing = R.string.quick_settings_bluetooth_device_audio_sharing
private val saved = R.string.quick_settings_bluetooth_device_saved
private val actionAccessibilityLabelActivate =
R.string.accessibility_quick_settings_bluetooth_device_tap_to_activate
@@ -39,35 +42,81 @@
abstract fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager,
): Boolean
abstract fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem
+
+ companion object {
+ @JvmStatic
+ fun createDeviceItem(
+ context: Context,
+ cachedDevice: CachedBluetoothDevice,
+ type: DeviceItemType,
+ connectionSummary: String,
+ background: Int,
+ actionAccessibilityLabel: String
+ ): DeviceItem {
+ return DeviceItem(
+ type = type,
+ cachedBluetoothDevice = cachedDevice,
+ deviceName = cachedDevice.name,
+ connectionSummary = connectionSummary,
+ iconWithDescription =
+ BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let {
+ Pair(it.first, it.second)
+ },
+ background = background,
+ isEnabled = !cachedDevice.isBusy,
+ actionAccessibilityLabel = actionAccessibilityLabel
+ )
+ }
+ }
}
internal open class ActiveMediaDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
- return DeviceItem(
- type = DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedDevice,
- deviceName = cachedDevice.name,
- connectionSummary = cachedDevice.connectionSummary ?: "",
- iconWithDescription =
- BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
- Pair(p.first, p.second)
- },
- background = backgroundOn,
- isEnabled = !cachedDevice.isBusy,
- actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect),
+ return createDeviceItem(
+ context,
+ cachedDevice,
+ DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+ cachedDevice.connectionSummary ?: "",
+ backgroundOn,
+ context.getString(actionAccessibilityLabelDisconnect)
+ )
+ }
+}
+
+internal class AudioSharingMediaDeviceItemFactory(
+ private val localBluetoothManager: LocalBluetoothManager?
+) : DeviceItemFactory() {
+ override fun isFilterMatched(
+ context: Context,
+ cachedDevice: CachedBluetoothDevice,
+ audioManager: AudioManager
+ ): Boolean {
+ return enableLeAudioSharing() &&
+ BluetoothUtils.hasConnectedBroadcastSource(cachedDevice, localBluetoothManager)
+ }
+
+ override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
+ return createDeviceItem(
+ context,
+ cachedDevice,
+ DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+ cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+ ?: context.getString(audioSharing),
+ if (cachedDevice.isBusy) backgroundOffBusy else backgroundOn,
+ ""
)
}
}
@@ -76,7 +125,7 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
BluetoothUtils.isAvailableHearingDevice(cachedDevice)
@@ -87,27 +136,21 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
}
- // TODO(b/298124674): move create() to the abstract class to reduce duplicate code
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
- return DeviceItem(
- type = DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedDevice,
- deviceName = cachedDevice.name,
- connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
- ?: context.getString(connected),
- iconWithDescription =
- BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
- Pair(p.first, p.second)
- },
- background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
- isEnabled = !cachedDevice.isBusy,
- actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate),
+ return createDeviceItem(
+ context,
+ cachedDevice,
+ DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
+ cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+ ?: context.getString(connected),
+ if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+ context.getString(actionAccessibilityLabelActivate)
)
}
}
@@ -116,7 +159,7 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
BluetoothUtils.isAvailableHearingDevice(cachedDevice)
@@ -127,32 +170,25 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
- !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
- context,
- cachedDevice.getDevice()
- ) && BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
+ !BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
+ BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
} else {
BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
}
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
- return DeviceItem(
- type = DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedDevice,
- deviceName = cachedDevice.name,
- connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
- ?: context.getString(connected),
- iconWithDescription =
- BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
- Pair(p.first, p.second)
- },
- background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
- isEnabled = !cachedDevice.isBusy,
- actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect),
+ return createDeviceItem(
+ context,
+ cachedDevice,
+ DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
+ cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+ ?: context.getString(connected),
+ if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+ context.getString(actionAccessibilityLabelDisconnect)
)
}
}
@@ -161,32 +197,26 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
- !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
- context,
- cachedDevice.getDevice()
- ) && cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
+ !BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
+ cachedDevice.bondState == BluetoothDevice.BOND_BONDED &&
+ !cachedDevice.isConnected
} else {
cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
}
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
- return DeviceItem(
- type = DeviceItemType.SAVED_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedDevice,
- deviceName = cachedDevice.name,
- connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
- ?: context.getString(saved),
- iconWithDescription =
- BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
- Pair(p.first, p.second)
- },
- background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
- isEnabled = !cachedDevice.isBusy,
- actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate),
+ return createDeviceItem(
+ context,
+ cachedDevice,
+ DeviceItemType.SAVED_BLUETOOTH_DEVICE,
+ cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+ ?: context.getString(saved),
+ if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+ context.getString(actionAccessibilityLabelActivate)
)
}
}
@@ -195,7 +225,7 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
!BluetoothUtils.isExclusivelyManagedBluetoothDevice(
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index 4e28caf..66e593b 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -113,6 +113,7 @@
private var deviceItemFactoryList: List<DeviceItemFactory> =
listOf(
ActiveMediaDeviceItemFactory(),
+ AudioSharingMediaDeviceItemFactory(localBluetoothManager),
AvailableMediaDeviceItemFactory(),
ConnectedDeviceItemFactory(),
SavedDeviceItemFactory()
@@ -121,6 +122,7 @@
private var displayPriority: List<DeviceItemType> =
listOf(
DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+ DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
DeviceItemType.SAVED_BLUETOOTH_DEVICE,
@@ -177,6 +179,9 @@
disconnect()
uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT)
}
+ DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+ uiEventLogger.log(BluetoothTileDialogUiEvent.AUDIO_SHARING_DEVICE_CLICKED)
+ }
DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
setActive()
uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 02a40d9..dd71bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -16,16 +16,23 @@
package com.android.systemui.bouncer.domain.interactor
+import android.app.StatusBarManager.SESSION_KEYGUARD
import com.android.compose.animation.scene.SceneKey
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim
import com.android.systemui.bouncer.data.repository.BouncerRepository
+import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
import com.android.systemui.classifier.FalsingClassifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.log.SessionTracker
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -50,6 +57,8 @@
private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
private val falsingInteractor: FalsingInteractor,
private val powerInteractor: PowerInteractor,
+ private val uiEventLogger: UiEventLogger,
+ private val sessionTracker: SessionTracker,
sceneInteractor: SceneInteractor,
) {
private val _onIncorrectBouncerInput = MutableSharedFlow<Unit>()
@@ -162,6 +171,18 @@
) {
_onIncorrectBouncerInput.emit(Unit)
}
+
+ if (authenticationInteractor.getAuthenticationMethod() in setOf(Pin, Password, Pattern)) {
+ if (authResult == AuthenticationResult.SUCCEEDED) {
+ uiEventLogger.log(BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS)
+ } else if (authResult == AuthenticationResult.FAILED) {
+ uiEventLogger.log(
+ BouncerUiEvent.BOUNCER_PASSWORD_FAILURE,
+ sessionTracker.getSessionId(SESSION_KEYGUARD)
+ )
+ }
+ }
+
return authResult
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
index e789475..62ef365 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
@@ -18,7 +18,7 @@
import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import dagger.Module
import dagger.Provides
@@ -42,11 +42,10 @@
fun isOnlyComposeBouncerEnabled(): Boolean
}
-class ComposeBouncerFlagsImpl(private val sceneContainerFlags: SceneContainerFlags) :
- ComposeBouncerFlags {
+class ComposeBouncerFlagsImpl() : ComposeBouncerFlags {
override fun isComposeBouncerOrSceneContainerEnabled(): Boolean {
- return sceneContainerFlags.isEnabled() || Flags.composeBouncer()
+ return SceneContainerFlag.isEnabled || Flags.composeBouncer()
}
@Deprecated(
@@ -55,7 +54,7 @@
replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()")
)
override fun isOnlyComposeBouncerEnabled(): Boolean {
- return !sceneContainerFlags.isEnabled() && Flags.composeBouncer()
+ return !SceneContainerFlag.isEnabled && Flags.composeBouncer()
}
}
@@ -63,7 +62,7 @@
object ComposeBouncerFlagsModule {
@Provides
@SysUISingleton
- fun impl(sceneContainerFlags: SceneContainerFlags): ComposeBouncerFlags {
- return ComposeBouncerFlagsImpl(sceneContainerFlags)
+ fun impl(): ComposeBouncerFlags {
+ return ComposeBouncerFlagsImpl()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/logging/BouncerUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/logging/BouncerUiEvent.kt
new file mode 100644
index 0000000..3be5499
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/logging/BouncerUiEvent.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.shared.logging
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+/**
+ * Legacy bouncer UI events {@link com.android.keyguard.KeyguardSecurityContainer.BouncerUiEvent}.
+ * Only contains that used by metrics.
+ */
+enum class BouncerUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Bouncer is dismissed using extended security access.")
+ BOUNCER_DISMISS_EXTENDED_ACCESS(413),
+
+ // PASSWORD here includes password, pattern, and pin.
+ @UiEvent(doc = "Bouncer is successfully unlocked using password.")
+ BOUNCER_PASSWORD_SUCCESS(418),
+ @UiEvent(doc = "An attempt to unlock bouncer using password has failed.")
+ BOUNCER_PASSWORD_FAILURE(419);
+
+ override fun getId() = _id
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
index 179fa87..eaca276 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
@@ -1,6 +1,9 @@
package com.android.systemui.bouncer.ui.binder
import android.view.ViewGroup
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.compose.ui.platform.ComposeView
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
@@ -30,7 +33,24 @@
) {
view.addView(
ComposeView(view.context).apply {
- setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+ repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ setViewTreeOnBackPressedDispatcherOwner(
+ object : OnBackPressedDispatcherOwner {
+ override val onBackPressedDispatcher =
+ OnBackPressedDispatcher().apply {
+ setOnBackInvokedDispatcher(
+ view.viewRootImpl.onBackInvokedDispatcher
+ )
+ }
+
+ override val lifecycle: Lifecycle =
+ [email protected]
+ }
+ )
+ setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+ }
+ }
}
)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 12cac92..4c2380c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -135,8 +135,11 @@
onIntentionalUserInput()
- mutablePinInput.value = pinInput.append(input)
- tryAuthenticate(useAutoConfirm = true)
+ val maxInputLength = hintedPinLength.value ?: Int.MAX_VALUE
+ if (pinInput.getPin().size < maxInputLength) {
+ mutablePinInput.value = pinInput.append(input)
+ tryAuthenticate(useAutoConfirm = true)
+ }
}
/** Notifies that the user clicked the backspace button. */
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
index af467ef..613280c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
@@ -22,7 +22,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.phone.NotificationTapHelper;
import dagger.Binds;
@@ -51,9 +51,8 @@
@SysUISingleton
static FalsingCollector providesFalsingCollectorLegacy(
FalsingCollectorImpl impl,
- FalsingCollectorNoOp noOp,
- SceneContainerFlags flags) {
- return flags.isEnabled() ? noOp : impl;
+ FalsingCollectorNoOp noOp) {
+ return SceneContainerFlag.isEnabled() ? noOp : impl;
}
/** Provides the actual {@link FalsingCollector}. */
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
index d4a1f74..0c181e9 100644
--- a/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
@@ -16,14 +16,14 @@
package com.android.systemui.common.coroutine
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow as wrapped
import kotlin.experimental.ExperimentalTypeInference
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.callbackFlow
+@Deprecated("Use com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow instead")
object ConflatedCallbackFlow {
/**
@@ -32,9 +32,15 @@
* consumer(s) of the values in the flow), the values are buffered and, if the buffer fills up,
* we drop the oldest values automatically instead of suspending the producer.
*/
- @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
- @OptIn(ExperimentalTypeInference::class, ExperimentalCoroutinesApi::class)
+ @Deprecated(
+ "Use com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow instead",
+ ReplaceWith(
+ "conflatedCallbackFlow",
+ "com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow"
+ )
+ )
+ @OptIn(ExperimentalTypeInference::class)
fun <T> conflatedCallbackFlow(
@BuilderInference block: suspend ProducerScope<T>.() -> Unit,
- ): Flow<T> = callbackFlow(block).buffer(capacity = Channel.CONFLATED)
+ ): Flow<T> = wrapped(block)
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index 5f6ff82..638af58 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -56,6 +56,13 @@
val naturalMaxBounds: Flow<Rect> =
repository.configurationValues.map { it.naturalScreenBounds }.distinctUntilChanged()
+ /**
+ * The layout direction. Will be either `View#LAYOUT_DIRECTION_LTR` or
+ * `View#LAYOUT_DIRECTION_RTL`.
+ */
+ val layoutDirection: Flow<Int> =
+ repository.configurationValues.map { it.layoutDirection }.distinctUntilChanged()
+
/** Given [resourceId], emit the dimension pixel size on config change */
fun dimensionPixelSize(resourceId: Int): Flow<Int> {
return onAnyConfigurationChange.mapLatest { repository.getDimensionPixelSize(resourceId) }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 72dcb26..27af99e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -24,6 +24,8 @@
import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.util.CommunalColors
+import com.android.systemui.communal.util.CommunalColorsImpl
import com.android.systemui.communal.widgets.CommunalWidgetModule
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl
@@ -60,6 +62,8 @@
@Communal
fun bindCommunalSceneDataSource(@Communal delegator: SceneDataSourceDelegator): SceneDataSource
+ @Binds fun bindCommunalColors(impl: CommunalColorsImpl): CommunalColors
+
companion object {
@Provides
@Communal
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 373e1c9..619e052 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
@@ -55,7 +55,7 @@
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.UserTracker
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
@@ -107,7 +107,6 @@
private val userManager: UserManager,
private val dockManager: DockManager,
sceneInteractor: SceneInteractor,
- sceneContainerFlags: SceneContainerFlags,
@CommunalLog logBuffer: LogBuffer,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
@@ -216,7 +215,7 @@
*/
// TODO(b/323215860): rename to something more appropriate after cleaning up usages
val isCommunalShowing: Flow<Boolean> =
- flow { emit(sceneContainerFlags.isEnabled()) }
+ flow { emit(SceneContainerFlag.isEnabled) }
.flatMapLatest { sceneContainerEnabled ->
if (sceneContainerEnabled) {
sceneInteractor.currentScene.map { it == Scenes.Communal }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 095222a..71d719d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -36,6 +36,9 @@
) {
val currentScene: Flow<SceneKey> = communalInteractor.desiredScene
+ /** Whether communal hub can be focused to enable accessibility actions. */
+ val isFocusable: Flow<Boolean> = communalInteractor.isIdleOnCommunal
+
/** Whether widgets are currently being re-ordered. */
open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index b3002cd..3f92223 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -63,7 +63,7 @@
override val isEditMode = true
- // Only widgets are editable. The CTA tile comes last in the list and remains visible.
+ // Only widgets are editable.
override val communalContent: Flow<List<CommunalContentModel>> =
communalInteractor.widgetContent.onEach { models ->
logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index bdf4e72..1bee83b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -16,7 +16,9 @@
package com.android.systemui.communal.ui.viewmodel
+import android.graphics.Color
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -28,6 +30,7 @@
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.merge
@@ -38,6 +41,7 @@
class CommunalTransitionViewModel
@Inject
constructor(
+ communalColors: CommunalColors,
glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
dreamToGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
@@ -68,4 +72,13 @@
step.transitionState == TransitionState.FINISHED ||
step.transitionState == TransitionState.CANCELED
}
+
+ val recentsBackgroundColor: Flow<Color?> =
+ combine(showByDefault, communalColors.backgroundColor) { showByDefault, backgroundColor ->
+ if (showByDefault) {
+ backgroundColor
+ } else {
+ null
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt b/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt
new file mode 100644
index 0000000..1e04fe7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.util
+
+import android.content.Context
+import android.graphics.Color
+import com.android.settingslib.Utils
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Wrapper around colors used for the communal UI. */
+interface CommunalColors {
+ /** The background color of the glanceable hub. */
+ val backgroundColor: StateFlow<Color>
+}
+
+@SysUISingleton
+class CommunalColorsImpl
+@Inject
+constructor(
+ @Application applicationScope: CoroutineScope,
+ private val context: Context,
+ configurationInteractor: ConfigurationInteractor,
+) : CommunalColors {
+ override val backgroundColor: StateFlow<Color> =
+ configurationInteractor.onAnyConfigurationChange
+ .map { loadBackgroundColor() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = loadBackgroundColor()
+ )
+
+ private fun loadBackgroundColor(): Color =
+ Color.valueOf(
+ Utils.getColorAttrDefaultColor(
+ context,
+ com.android.internal.R.attr.materialColorOutlineVariant
+ )
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 1003050..00a8259 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -30,6 +30,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.doze.DozeHost;
+import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule;
import com.android.systemui.media.dagger.MediaModule;
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
@@ -99,6 +100,7 @@
BatterySaverModule.class,
CollapsedStatusBarFragmentStartableModule.class,
ConnectingDisplayViewModel.StartableModule.class,
+ DefaultBlueprintModule.class,
GestureModule.class,
HeadsUpModule.class,
KeyboardShortcutsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 21ee5bd..23fc8ac 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -55,6 +55,7 @@
import com.android.systemui.statusbar.notification.InstantAppNotifier
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener
+import com.android.systemui.statusbar.policy.BatteryControllerStartable
import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.theme.ThemeOverlayController
@@ -343,4 +344,10 @@
@IntoMap
@ClassKey(HomeControlsDreamStartable::class)
abstract fun bindHomeControlsDreamStartable(impl: HomeControlsDreamStartable): CoreStartable
+
+ /** Binds {@link BatteryControllerStartable} as a {@link CoreStartable}. */
+ @Binds
+ @IntoMap
+ @ClassKey(BatteryControllerStartable::class)
+ abstract fun bindsBatteryControllerStartable(impl: BatteryControllerStartable): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 7d86e06..6b85d30 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -32,6 +32,7 @@
import com.android.systemui.SystemUISecondaryUserService;
import com.android.systemui.accessibility.AccessibilityModule;
import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
+import com.android.systemui.ambient.dagger.AmbientModule;
import com.android.systemui.appops.dagger.AppOpsModule;
import com.android.systemui.assist.AssistModule;
import com.android.systemui.authentication.AuthenticationModule;
@@ -162,14 +163,14 @@
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
+import kotlinx.coroutines.CoroutineScope;
+
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Named;
-import kotlinx.coroutines.CoroutineScope;
-
/**
* A dagger module for injecting components of System UI that are required by System UI.
*
@@ -183,6 +184,7 @@
@Module(includes = {
AccessibilityModule.class,
AccessibilityRepositoryModule.class,
+ AmbientModule.class,
AppOpsModule.class,
AssistModule.class,
AuthenticationModule.class,
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
deleted file mode 100644
index 989b0de..0000000
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ /dev/null
@@ -1,98 +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.display.ui.view
-
-import android.content.Context
-import android.os.Bundle
-import android.view.View
-import android.view.WindowInsets
-import android.widget.TextView
-import androidx.core.view.updatePadding
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog
-import com.android.systemui.statusbar.policy.ConfigurationController
-import kotlin.math.max
-
-/**
- * Dialog used to decide what to do with a connected display.
- *
- * [onCancelMirroring] is called **only** if mirroring didn't start, or when the dismiss button is
- * pressed.
- */
-class MirroringConfirmationDialog(
- context: Context,
- private val onStartMirroringClickListener: View.OnClickListener,
- private val onCancelMirroring: View.OnClickListener,
- private val navbarBottomInsetsProvider: () -> Int,
- configurationController: ConfigurationController? = null,
- private val showConcurrentDisplayInfo: Boolean = false,
- theme: Int = R.style.Theme_SystemUI_Dialog,
-) : SystemUIBottomSheetDialog(context, configurationController, theme) {
-
- private lateinit var mirrorButton: TextView
- private lateinit var dismissButton: TextView
- private lateinit var dualDisplayWarning: TextView
- private lateinit var bottomSheet: View
- private var enabledPressed = false
- private val defaultDialogBottomInset =
- context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.connected_display_dialog)
-
- mirrorButton =
- requireViewById<TextView>(R.id.enable_display).apply {
- setOnClickListener(onStartMirroringClickListener)
- enabledPressed = true
- }
- dismissButton =
- requireViewById<TextView>(R.id.cancel).apply { setOnClickListener(onCancelMirroring) }
-
- dualDisplayWarning =
- requireViewById<TextView>(R.id.dual_display_warning).apply {
- visibility = if (showConcurrentDisplayInfo) View.VISIBLE else View.GONE
- }
-
- bottomSheet = requireViewById(R.id.cd_bottom_sheet)
-
- setOnDismissListener {
- if (!enabledPressed) {
- onCancelMirroring.onClick(null)
- }
- }
- setupInsets()
- }
-
- private fun setupInsets(navbarInsets: Int = navbarBottomInsetsProvider()) {
- // This avoids overlap between dialog content and navigation bars.
- // we only care about the bottom inset as in all other configuration where navigations
- // are in other display sides there is no overlap with the dialog.
- bottomSheet.updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
- }
-
- override fun onInsetsChanged(changedTypes: Int, insets: WindowInsets) {
- val navbarType = WindowInsets.Type.navigationBars()
- if (changedTypes and navbarType != 0) {
- setupInsets(insets.getInsets(navbarType).bottom)
- }
- }
-
- override fun onConfigurationChanged() {
- super.onConfigurationChanged()
- setupInsets()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegate.kt
new file mode 100644
index 0000000..19b2673
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegate.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.display.ui.view
+
+import android.app.Dialog
+import android.content.Context
+import android.content.res.Configuration
+import android.os.Bundle
+import android.view.View
+import android.view.WindowInsets
+import android.view.WindowInsetsAnimation
+import android.widget.TextView
+import androidx.annotation.StyleRes
+import androidx.annotation.VisibleForTesting
+import androidx.core.view.updatePadding
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.DialogDelegate
+import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog
+import javax.inject.Inject
+import kotlin.math.max
+
+/**
+ * Dialog used to decide what to do with a connected display.
+ *
+ * [onCancelMirroring] is called **only** if mirroring didn't start, or when the dismiss button is
+ * pressed.
+ */
+class MirroringConfirmationDialogDelegate
+@VisibleForTesting
+constructor(
+ context: Context,
+ private val showConcurrentDisplayInfo: Boolean = false,
+ private val onStartMirroringClickListener: View.OnClickListener,
+ private val onCancelMirroring: View.OnClickListener,
+ private val navbarBottomInsetsProvider: () -> Int,
+) : DialogDelegate<Dialog> {
+
+ private lateinit var mirrorButton: TextView
+ private lateinit var dismissButton: TextView
+ private lateinit var dualDisplayWarning: TextView
+ private lateinit var bottomSheet: View
+ private var enabledPressed = false
+ private val defaultDialogBottomInset =
+ context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
+
+ override fun onCreate(dialog: Dialog, savedInstanceState: Bundle?) {
+ dialog.setContentView(R.layout.connected_display_dialog)
+
+ mirrorButton =
+ dialog.requireViewById<TextView>(R.id.enable_display).apply {
+ setOnClickListener(onStartMirroringClickListener)
+ enabledPressed = true
+ }
+ dismissButton =
+ dialog.requireViewById<TextView>(R.id.cancel).apply {
+ setOnClickListener(onCancelMirroring)
+ }
+
+ dualDisplayWarning =
+ dialog.requireViewById<TextView>(R.id.dual_display_warning).apply {
+ visibility = if (showConcurrentDisplayInfo) View.VISIBLE else View.GONE
+ }
+
+ bottomSheet = dialog.requireViewById(R.id.cd_bottom_sheet)
+
+ dialog.setOnDismissListener {
+ if (!enabledPressed) {
+ onCancelMirroring.onClick(null)
+ }
+ }
+ setupInsets()
+ }
+
+ override fun onStart(dialog: Dialog) {
+ dialog.window?.decorView?.setWindowInsetsAnimationCallback(insetsAnimationCallback)
+ }
+
+ override fun onStop(dialog: Dialog) {
+ dialog.window?.decorView?.setWindowInsetsAnimationCallback(null)
+ }
+
+ private fun setupInsets(navbarInsets: Int = navbarBottomInsetsProvider()) {
+ // This avoids overlap between dialog content and navigation bars.
+ // we only care about the bottom inset as in all other configuration where navigations
+ // are in other display sides there is no overlap with the dialog.
+ bottomSheet.updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
+ }
+
+ override fun onConfigurationChanged(dialog: Dialog, configuration: Configuration) {
+ setupInsets()
+ }
+
+ private val insetsAnimationCallback =
+ object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+
+ private var lastInsets: WindowInsets? = null
+
+ override fun onEnd(animation: WindowInsetsAnimation) {
+ lastInsets?.let { onInsetsChanged(animation.typeMask, it) }
+ }
+
+ override fun onProgress(
+ insets: WindowInsets,
+ animations: MutableList<WindowInsetsAnimation>,
+ ): WindowInsets {
+ lastInsets = insets
+ onInsetsChanged(changedTypes = allAnimationMasks(animations), insets)
+ return insets
+ }
+
+ private fun allAnimationMasks(animations: List<WindowInsetsAnimation>): Int =
+ animations.fold(0) { acc: Int, it -> acc or it.typeMask }
+
+ private fun onInsetsChanged(changedTypes: Int, insets: WindowInsets) {
+ val navbarType = WindowInsets.Type.navigationBars()
+ if (changedTypes and navbarType != 0) {
+ setupInsets(insets.getInsets(navbarType).bottom)
+ }
+ }
+ }
+
+ class Factory
+ @Inject
+ constructor(
+ @Application private val context: Context,
+ private val dialogFactory: SystemUIBottomSheetDialog.Factory,
+ ) {
+
+ fun createDialog(
+ showConcurrentDisplayInfo: Boolean = false,
+ onStartMirroringClickListener: View.OnClickListener,
+ onCancelMirroring: View.OnClickListener,
+ navbarBottomInsetsProvider: () -> Int,
+ @StyleRes theme: Int = R.style.Theme_SystemUI_Dialog,
+ ): Dialog =
+ dialogFactory.create(
+ delegate =
+ MirroringConfirmationDialogDelegate(
+ context = context,
+ showConcurrentDisplayInfo = showConcurrentDisplayInfo,
+ onStartMirroringClickListener = onStartMirroringClickListener,
+ onCancelMirroring = onCancelMirroring,
+ navbarBottomInsetsProvider = navbarBottomInsetsProvider,
+ ),
+ theme = theme,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index fbf0538..81ea2e7 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -25,8 +25,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
-import com.android.systemui.display.ui.view.MirroringConfirmationDialog
-import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.display.ui.view.MirroringConfirmationDialogDelegate
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -54,7 +53,7 @@
private val connectedDisplayInteractor: ConnectedDisplayInteractor,
@Application private val scope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
- private val configurationController: ConfigurationController,
+ private val bottomSheetFactory: MirroringConfirmationDialogDelegate.Factory,
) : CoreStartable {
private var dialog: Dialog? = null
@@ -91,8 +90,8 @@
private fun showDialog(pendingDisplay: PendingDisplay, concurrentDisplaysInProgess: Boolean) {
hideDialog()
dialog =
- MirroringConfirmationDialog(
- context,
+ bottomSheetFactory
+ .createDialog(
onStartMirroringClickListener = {
scope.launch(bgDispatcher) { pendingDisplay.enable() }
hideDialog()
@@ -102,7 +101,6 @@
hideDialog()
},
navbarBottomInsetsProvider = { Utils.getNavbarInsets(context).bottom },
- configurationController,
showConcurrentDisplayInfo = concurrentDisplaysInProgess
)
.apply { show() }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index d0f2559..5a036b1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -33,14 +33,14 @@
import com.android.app.animation.Interpolators;
import com.android.dream.lowlight.LowLightTransitionCoordinator;
-import com.android.systemui.res.R;
+import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
import com.android.systemui.complication.ComplicationHostViewController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.dagger.DreamOverlayModule;
-import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.util.ViewController;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 675e8de..1135afe 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -43,11 +43,12 @@
import com.android.internal.policy.PhoneWindow;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.ambient.touch.TouchMonitor;
+import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent;
import com.android.systemui.complication.Complication;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
-import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -94,6 +95,8 @@
private final ComplicationComponent mComplicationComponent;
+ private final AmbientTouchComponent mAmbientTouchComponent;
+
private final com.android.systemui.dreams.complication.dagger.ComplicationComponent
mDreamComplicationComponent;
@@ -102,7 +105,7 @@
private final DreamOverlayLifecycleOwner mLifecycleOwner;
private final LifecycleRegistry mLifecycleRegistry;
- private DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
+ private TouchMonitor mTouchMonitor;
private final KeyguardUpdateMonitorCallback mKeyguardCallback =
new KeyguardUpdateMonitorCallback() {
@@ -162,6 +165,7 @@
com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
dreamComplicationComponentFactory,
DreamOverlayComponent.Factory dreamOverlayComponentFactory,
+ AmbientTouchComponent.Factory ambientTouchComponentFactory,
DreamOverlayStateController stateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
UiEventLogger uiEventLogger,
@@ -194,9 +198,11 @@
mDreamComplicationComponent = dreamComplicationComponentFactory.create(
mComplicationComponent.getVisibilityController(), touchInsetManager);
mDreamOverlayComponent = dreamOverlayComponentFactory.create(lifecycleOwner,
- mComplicationComponent.getComplicationHostViewController(), touchInsetManager,
+ mComplicationComponent.getComplicationHostViewController(), touchInsetManager);
+ mAmbientTouchComponent = ambientTouchComponentFactory.create(lifecycleOwner,
new HashSet<>(Arrays.asList(
- mDreamComplicationComponent.getHideComplicationTouchHandler())));
+ mDreamComplicationComponent.getHideComplicationTouchHandler(),
+ mDreamOverlayComponent.getCommunalTouchHandler())));
mLifecycleOwner = lifecycleOwner;
mLifecycleRegistry = mLifecycleOwner.getRegistry();
@@ -239,8 +245,8 @@
mDreamOverlayContainerViewController =
mDreamOverlayComponent.getDreamOverlayContainerViewController();
- mDreamOverlayTouchMonitor = mDreamOverlayComponent.getDreamOverlayTouchMonitor();
- mDreamOverlayTouchMonitor.init();
+ mTouchMonitor = mAmbientTouchComponent.getTouchMonitor();
+ mTouchMonitor.init();
mStateController.setShouldShowComplications(shouldShowComplications());
@@ -370,7 +376,7 @@
mStateController.setEntryAnimationsFinished(false);
mDreamOverlayContainerViewController = null;
- mDreamOverlayTouchMonitor = null;
+ mTouchMonitor = null;
mWindow = null;
mStarted = false;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index 8d8702e..da72a56 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -16,30 +16,30 @@
package com.android.systemui.dreams;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+
import android.app.AlarmManager;
import android.app.StatusBarManager;
import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.PluralsMessageFormatter;
import android.view.View;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.dagger.DreamLog;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository;
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -65,7 +65,6 @@
public class DreamOverlayStatusBarViewController extends ViewController<DreamOverlayStatusBarView> {
private static final String TAG = "DreamStatusBarCtrl";
- private final ConnectivityManager mConnectivityManager;
private final TouchInsetManager.TouchInsetSession mTouchInsetSession;
private final NextAlarmController mNextAlarmController;
private final AlarmManager mAlarmManager;
@@ -77,6 +76,7 @@
private final ZenModeController mZenModeController;
private final DreamOverlayStateController mDreamOverlayStateController;
private final UserTracker mUserTracker;
+ private final WifiRepository mWifiRepository;
private final StatusBarWindowStateController mStatusBarWindowStateController;
private final DreamOverlayStatusBarItemsProvider mStatusBarItemsProvider;
private final Executor mMainExecutor;
@@ -89,28 +89,6 @@
// Whether dream entry animations are finished.
private boolean mEntryAnimationsFinished = false;
- private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
- .clearCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
-
- private final NetworkCallback mNetworkCallback = new NetworkCallback() {
- @Override
- public void onCapabilitiesChanged(
- Network network, NetworkCapabilities networkCapabilities) {
- updateWifiUnavailableStatusIcon();
- }
-
- @Override
- public void onAvailable(Network network) {
- updateWifiUnavailableStatusIcon();
- }
-
- @Override
- public void onLost(Network network) {
- updateWifiUnavailableStatusIcon();
- }
- };
-
private final DreamOverlayStateController.Callback mDreamOverlayStateCallback =
new DreamOverlayStateController.Callback() {
@Override
@@ -151,7 +129,6 @@
DreamOverlayStatusBarView view,
@Main Resources resources,
@Main Executor mainExecutor,
- ConnectivityManager connectivityManager,
TouchInsetManager.TouchInsetSession touchInsetSession,
AlarmManager alarmManager,
NextAlarmController nextAlarmController,
@@ -163,11 +140,11 @@
DreamOverlayStatusBarItemsProvider statusBarItemsProvider,
DreamOverlayStateController dreamOverlayStateController,
UserTracker userTracker,
+ WifiRepository wifiRepository,
@DreamLog LogBuffer logBuffer) {
super(view);
mResources = resources;
mMainExecutor = mainExecutor;
- mConnectivityManager = connectivityManager;
mTouchInsetSession = touchInsetSession;
mAlarmManager = alarmManager;
mNextAlarmController = nextAlarmController;
@@ -179,6 +156,7 @@
mZenModeController = zenModeController;
mDreamOverlayStateController = dreamOverlayStateController;
mUserTracker = userTracker;
+ mWifiRepository = wifiRepository;
mLogger = new DreamLogger(logBuffer, TAG);
// Register to receive show/hide updates for the system status bar. Our custom status bar
@@ -190,8 +168,11 @@
protected void onViewAttached() {
mIsAttached = true;
- mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
- updateWifiUnavailableStatusIcon();
+ collectFlow(
+ mView,
+ mWifiRepository.getWifiNetwork(),
+ network -> updateWifiUnavailableStatusIcon(
+ network instanceof WifiNetworkModel.Active));
mNextAlarmController.addCallback(mNextAlarmCallback);
updateAlarmStatusIcon();
@@ -215,7 +196,6 @@
mZenModeController.removeCallback(mZenModeCallback);
mSensorPrivacyController.removeCallback(mSensorCallback);
mNextAlarmController.removeCallback(mNextAlarmCallback);
- mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
mDreamOverlayNotificationCountProvider.ifPresent(
provider -> provider.removeCallback(mNotificationCountCallback));
mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback);
@@ -258,12 +238,8 @@
&& !mStatusBarWindowStateController.windowIsShowing();
}
- private void updateWifiUnavailableStatusIcon() {
- final NetworkCapabilities capabilities =
- mConnectivityManager.getNetworkCapabilities(
- mConnectivityManager.getActiveNetwork());
- final boolean available = capabilities != null
- && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
+ @VisibleForTesting
+ void updateWifiUnavailableStatusIcon(boolean available) {
showIcon(DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, !available,
R.string.wifi_unavailable_dream_overlay_content_description);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
index d525ce3..f8ae5c2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
@@ -26,11 +26,11 @@
import androidx.annotation.Nullable;
+import com.android.systemui.ambient.touch.TouchHandler;
+import com.android.systemui.ambient.touch.TouchMonitor;
import com.android.systemui.complication.Complication;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -47,12 +47,12 @@
* {@link HideComplicationTouchHandler} is responsible for hiding the overlay complications from
* visibility whenever there is touch interactions outside the overlay. The overlay interaction
* scope includes touches to the complication plus any touch entry region for gestures as specified
- * to the {@link DreamOverlayTouchMonitor}.
+ * to the {@link TouchMonitor}.
*
- * This {@link DreamTouchHandler} is also responsible for fading in the complications at the end
- * of the {@link com.android.systemui.dreams.touch.DreamTouchHandler.TouchSession}.
+ * This {@link TouchHandler} is also responsible for fading in the complications at the end
+ * of the {@link TouchHandler.TouchSession}.
*/
-public class HideComplicationTouchHandler implements DreamTouchHandler {
+public class HideComplicationTouchHandler implements TouchHandler {
private static final String TAG = "HideComplicationHandler";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index ba74742..31710ac 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -25,6 +25,7 @@
import com.android.dream.lowlight.dagger.LowLightDreamModule;
import com.android.settingslib.dream.DreamBackend;
+import com.android.systemui.ambient.touch.scrim.dagger.ScrimModule;
import com.android.systemui.complication.dagger.RegisteredComplicationsModule;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -34,7 +35,6 @@
import com.android.systemui.dreams.homecontrols.DreamActivityProvider;
import com.android.systemui.dreams.homecontrols.DreamActivityProviderImpl;
import com.android.systemui.dreams.homecontrols.HomeControlsDreamService;
-import com.android.systemui.dreams.touch.scrim.dagger.ScrimModule;
import com.android.systemui.res.R;
import com.android.systemui.touch.TouchInsetManager;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
index cb587c2..16276fe 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
@@ -16,19 +16,14 @@
package com.android.systemui.dreams.dagger;
-import static com.android.systemui.dreams.dagger.DreamOverlayModule.DREAM_TOUCH_HANDLERS;
-
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import android.annotation.Nullable;
-
import androidx.lifecycle.LifecycleOwner;
import com.android.systemui.complication.ComplicationHostViewController;
import com.android.systemui.dreams.DreamOverlayContainerViewController;
-import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
-import com.android.systemui.dreams.touch.dagger.DreamTouchModule;
+import com.android.systemui.dreams.touch.CommunalTouchHandler;
+import com.android.systemui.dreams.touch.dagger.CommunalTouchModule;
import com.android.systemui.touch.TouchInsetManager;
import dagger.BindsInstance;
@@ -36,17 +31,15 @@
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
-import java.util.Set;
-import javax.inject.Named;
import javax.inject.Scope;
/**
* Dagger subcomponent for {@link DreamOverlayModule}.
*/
@Subcomponent(modules = {
- DreamTouchModule.class,
DreamOverlayModule.class,
+ CommunalTouchModule.class
})
@DreamOverlayComponent.DreamOverlayScope
public interface DreamOverlayComponent {
@@ -56,9 +49,7 @@
DreamOverlayComponent create(
@BindsInstance LifecycleOwner lifecycleOwner,
@BindsInstance ComplicationHostViewController complicationHostViewController,
- @BindsInstance TouchInsetManager touchInsetManager,
- @BindsInstance @Named(DREAM_TOUCH_HANDLERS) @Nullable
- Set<DreamTouchHandler> dreamTouchHandlers);
+ @BindsInstance TouchInsetManager touchInsetManager);
}
/** Scope annotation for singleton items within the {@link DreamOverlayComponent}. */
@@ -70,6 +61,6 @@
/** Builds a {@link DreamOverlayContainerViewController}. */
DreamOverlayContainerViewController getDreamOverlayContainerViewController();
- /** Builds a {@link DreamOverlayTouchMonitor} */
- DreamOverlayTouchMonitor getDreamOverlayTouchMonitor();
+ /** Builds communal touch handler */
+ CommunalTouchHandler getCommunalTouchHandler();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 34dd1008..999e681 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -16,7 +16,6 @@
package com.android.systemui.dreams.dagger;
-import android.annotation.Nullable;
import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -25,26 +24,20 @@
import androidx.lifecycle.LifecycleOwner;
import com.android.internal.util.Preconditions;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayContainerView;
import com.android.systemui.dreams.DreamOverlayStatusBarView;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
+import com.android.systemui.res.R;
import com.android.systemui.touch.TouchInsetManager;
import dagger.Module;
import dagger.Provides;
-import dagger.multibindings.ElementsIntoSet;
-
-import java.util.HashSet;
-import java.util.Set;
import javax.inject.Named;
/** Dagger module for {@link DreamOverlayComponent}. */
@Module
public abstract class DreamOverlayModule {
- public static final String DREAM_TOUCH_HANDLERS = "dream_touch_handlers";
public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view";
public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset";
public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
@@ -169,11 +162,4 @@
static Lifecycle providesLifecycle(LifecycleOwner lifecycleOwner) {
return lifecycleOwner.getLifecycle();
}
-
- @Provides
- @ElementsIntoSet
- static Set<DreamTouchHandler> providesDreamTouchHandlers(
- @Named(DREAM_TOUCH_HANDLERS) @Nullable Set<DreamTouchHandler> touchHandlers) {
- return touchHandlers != null ? touchHandlers : new HashSet<>();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index 13588c2..1c047dd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -16,7 +16,6 @@
package com.android.systemui.dreams.touch;
-import static com.android.systemui.dreams.touch.dagger.ShadeModule.COMMUNAL_GESTURE_INITIATION_WIDTH;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.graphics.Rect;
@@ -27,7 +26,9 @@
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
+import com.android.systemui.ambient.touch.TouchHandler;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.dreams.touch.dagger.CommunalTouchModule;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import java.util.Optional;
@@ -36,8 +37,8 @@
import javax.inject.Inject;
import javax.inject.Named;
-/** {@link DreamTouchHandler} responsible for handling touches to open communal hub. **/
-public class CommunalTouchHandler implements DreamTouchHandler {
+/** {@link TouchHandler} responsible for handling touches to open communal hub. **/
+public class CommunalTouchHandler implements TouchHandler {
private final int mInitiationWidth;
private final Optional<CentralSurfaces> mCentralSurfaces;
private final Lifecycle mLifecycle;
@@ -53,7 +54,7 @@
@Inject
public CommunalTouchHandler(
Optional<CentralSurfaces> centralSurfaces,
- @Named(COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth,
+ @Named(CommunalTouchModule.COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth,
CommunalInteractor communalInteractor,
Lifecycle lifecycle) {
mInitiationWidth = initiationWidth;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/CommunalTouchModule.kt b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/CommunalTouchModule.kt
new file mode 100644
index 0000000..927ea4ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/CommunalTouchModule.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.touch.dagger
+
+import android.content.res.Resources
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+@Module
+interface CommunalTouchModule {
+ companion object {
+ /** Provides the width of the gesture area for swiping open communal hub. */
+ @JvmStatic
+ @Provides
+ @Named(COMMUNAL_GESTURE_INITIATION_WIDTH)
+ fun providesCommunalGestureInitiationWidth(@Main resources: Resources): Int {
+ return resources.getDimensionPixelSize(R.dimen.communal_gesture_initiation_width)
+ }
+
+ /** Width of swipe gesture edge to show communal hub. */
+ const val COMMUNAL_GESTURE_INITIATION_WIDTH = "communal_gesture_initiation_width"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
deleted file mode 100644
index b719126..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
+++ /dev/null
@@ -1,33 +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.dreams.touch.dagger;
-
-import dagger.Module;
-
-/**
- * {@link DreamTouchModule} encapsulates dream touch-related components.
- */
-@Module(includes = {
- BouncerSwipeModule.class,
- ShadeModule.class,
- }, subcomponents = {
- InputSessionComponent.class,
-})
-public interface DreamTouchModule {
- String INPUT_SESSION_NAME = "INPUT_SESSION_NAME";
- String PILFER_ON_GESTURE_CONSUME = "PILFER_ON_GESTURE_CONSUME";
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index db6b8fe..04f1ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -255,9 +255,6 @@
val FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS =
releasedFlag("filter_provisioning_network_subscriptions")
- // TODO(b/292533677): Tracking Bug
- val WIFI_TRACKER_LIB_FOR_WIFI_ICON = releasedFlag("wifi_tracker_lib_for_wifi_icon")
-
// TODO(b/293863612): Tracking Bug
@JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON =
releasedFlag("incompatible_charging_battery_icon")
@@ -367,16 +364,6 @@
val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
sysPropBooleanFlag("persist.wm.debug.predictive_back_always_enforce", default = false)
- // TODO(b/254512728): Tracking Bug
- @JvmField val NEW_BACK_AFFORDANCE = releasedFlag("new_back_affordance")
-
-
- // TODO(b/270987164): Tracking Bug
- @JvmField val TRACKPAD_GESTURE_FEATURES = releasedFlag("trackpad_gesture_features")
-
- // TODO(b/273800936): Tracking Bug
- @JvmField val TRACKPAD_GESTURE_COMMON = releasedFlag("trackpad_gesture_common")
-
// TODO(b/251205791): Tracking Bug
@JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips")
@@ -487,12 +474,6 @@
@JvmField val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE =
unreleasedFlag("decouple_remote_input_delegate_and_callback_update")
- // 2900 - CentralSurfaces-related flags
-
- // TODO(b/285174336): Tracking Bug
- @JvmField
- val USE_REPOS_FOR_BOUNCER_SHOWING = releasedFlag("use_repos_for_bouncer_showing")
-
/** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */
@JvmField
val ENABLE_CLOCK_KEYGUARD_PRESENTATION = releasedFlag("enable_clock_keyguard_presentation")
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index 4327d18..bccc3c5 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -17,9 +17,7 @@
package com.android.systemui.haptics.qs
import android.animation.ValueAnimator
-import android.annotation.SuppressLint
import android.os.VibrationEffect
-import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.animation.AccelerateDecelerateInterpolator
@@ -27,18 +25,14 @@
import androidx.core.animation.doOnCancel
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
/**
* A class that handles the long press visuo-haptic effect for a QS tile.
@@ -55,34 +49,32 @@
@Inject
constructor(
private val vibratorHelper: VibratorHelper?,
- val keyguardInteractor: KeyguardInteractor,
- @Background bgScope: CoroutineScope,
-) : View.OnTouchListener {
+ keyguardInteractor: KeyguardInteractor,
+) {
private var effectDuration = 0
/** Current state */
private var _state = MutableStateFlow(State.IDLE)
- val state = _state.stateIn(bgScope, SharingStarted.Lazily, State.IDLE)
+ val state = _state.asStateFlow()
/** Flows for view control and action */
private val _effectProgress = MutableStateFlow<Float?>(null)
- val effectProgress = _effectProgress.stateIn(bgScope, SharingStarted.Lazily, null)
+ val effectProgress = _effectProgress.asStateFlow()
// Actions to perform
private val _postedActionType = MutableStateFlow<ActionType?>(null)
- val actionType: StateFlow<ActionType?> =
+ val actionType: Flow<ActionType?> =
combine(
- _postedActionType,
- keyguardInteractor.isKeyguardDismissible,
- ) { action, isDismissible ->
- if (!isDismissible && action == ActionType.LONG_PRESS) {
- ActionType.RESET_AND_LONG_PRESS
- } else {
- action
- }
+ _postedActionType,
+ keyguardInteractor.isKeyguardDismissible,
+ ) { action, isDismissible ->
+ if (!isDismissible && action == ActionType.LONG_PRESS) {
+ ActionType.RESET_AND_LONG_PRESS
+ } else {
+ action
}
- .stateIn(bgScope, SharingStarted.Lazily, null)
+ }
// Should a tap timeout countdown begin
val shouldWaitForTapTimeout: Flow<Boolean> = state.map { it == State.TIMEOUT_WAIT }
@@ -129,23 +121,7 @@
}
}
- /**
- * Handle relevant touch events for the operation of a Tile.
- *
- * A click action is performed following the relevant logic that originates from the
- * [MotionEvent.ACTION_UP] event depending on the current state.
- */
- @SuppressLint("ClickableViewAccessibility")
- override fun onTouch(view: View?, event: MotionEvent?): Boolean {
- when (event?.actionMasked) {
- MotionEvent.ACTION_DOWN -> handleActionDown()
- MotionEvent.ACTION_UP -> handleActionUp()
- MotionEvent.ACTION_CANCEL -> handleActionCancel()
- }
- return true
- }
-
- private fun handleActionDown() {
+ fun handleActionDown() {
when (_state.value) {
State.IDLE -> {
setState(State.TIMEOUT_WAIT)
@@ -155,7 +131,7 @@
}
}
- private fun handleActionUp() {
+ fun handleActionUp() {
when (_state.value) {
State.TIMEOUT_WAIT -> {
_postedActionType.value = ActionType.CLICK
@@ -169,7 +145,7 @@
}
}
- private fun handleActionCancel() {
+ fun handleActionCancel() {
when (_state.value) {
State.TIMEOUT_WAIT -> {
setState(State.IDLE)
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
index ddb9f35..2ef901d 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -16,6 +16,8 @@
package com.android.systemui.haptics.qs
+import android.annotation.SuppressLint
+import android.view.MotionEvent
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launch
@@ -25,10 +27,9 @@
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.launch
-// TODO(b/332903800)
object QSLongPressEffectViewBinder {
+
fun bind(
tile: QSTileViewImpl,
qsLongPressEffect: QSLongPressEffect?,
@@ -36,11 +37,13 @@
): DisposableHandle? {
if (qsLongPressEffect == null) return null
+ // Set the touch listener as the long-press effect
+ setTouchListener(tile, qsLongPressEffect)
+
return tile.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- val tag = "${tileSpec ?: "unknownTileSpec"}#LongPressEffect"
// Progress of the effect
- launch("$tag#progress") {
+ launch({ "${tileSpec ?: "unknownTileSpec"}#LongPressEffect#progress" }) {
qsLongPressEffect.effectProgress.collect { progress ->
progress?.let {
if (it == 0f) {
@@ -53,7 +56,7 @@
}
// Action to perform
- launch("$tag#action") {
+ launch({ "${tileSpec ?: "unknownTileSpec"}#LongPressEffect#action" }) {
qsLongPressEffect.actionType.collect { action ->
action?.let {
when (it) {
@@ -70,7 +73,7 @@
}
// Tap timeout wait
- launch("$tag#timeout") {
+ launch({ "${tileSpec ?: "unknownTileSpec"}#LongPressEffect#timeout" }) {
qsLongPressEffect.shouldWaitForTapTimeout
.filter { it }
.collect {
@@ -85,4 +88,16 @@
}
}
}
+
+ @SuppressLint("ClickableViewAccessibility")
+ private fun setTouchListener(tile: QSTileViewImpl, longPressEffect: QSLongPressEffect?) {
+ tile.setOnTouchListener { _, event ->
+ when (event.actionMasked) {
+ MotionEvent.ACTION_DOWN -> longPressEffect?.handleActionDown()
+ MotionEvent.ACTION_UP -> longPressEffect?.handleActionUp()
+ MotionEvent.ACTION_CANCEL -> longPressEffect?.handleActionCancel()
+ }
+ true
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2a9dad0..a1ac5b3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -43,7 +43,6 @@
import static com.android.systemui.Flags.notifyPowerManagerUserActivityBackground;
import static com.android.systemui.Flags.refactorGetCurrentUser;
import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS;
-import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -98,7 +97,6 @@
import android.view.SyncRtSurfaceTransactionApplier;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants;
import android.view.animation.Animation;
@@ -165,7 +163,6 @@
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -1373,7 +1370,7 @@
private final Lazy<DreamViewModel> mDreamViewModel;
private final Lazy<CommunalTransitionViewModel> mCommunalTransitionViewModel;
private RemoteAnimationTarget mRemoteAnimationTarget;
- private Boolean mShowCommunalByDefault;
+ private boolean mShowCommunalByDefault = false;
private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
@@ -1623,24 +1620,18 @@
adjustStatusBarLocked();
mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
- ViewRootImpl viewRootImpl = mKeyguardViewControllerLazy.get().getViewRootImpl();
- if (viewRootImpl != null) {
- final DreamViewModel dreamViewModel = mDreamViewModel.get();
- final CommunalTransitionViewModel communalViewModel =
- mCommunalTransitionViewModel.get();
- collectFlow(viewRootImpl.getView(), dreamViewModel.getDreamAlpha(),
- getRemoteSurfaceAlphaApplier(), mMainDispatcher);
- collectFlow(viewRootImpl.getView(), dreamViewModel.getTransitionEnded(),
- getFinishedCallbackConsumer(), mMainDispatcher);
- collectFlow(viewRootImpl.getView(), communalViewModel.getShowByDefault(),
- (showByDefault) ->
- mShowCommunalByDefault = showByDefault, mMainDispatcher);
- collectFlow(viewRootImpl.getView(),
- communalViewModel.getTransitionFromOccludedEnded(),
- getFinishedCallbackConsumer(), mMainDispatcher);
- } else {
- Log.e(TAG, "Keyguard ViewRootImpl is null");
- }
+ final DreamViewModel dreamViewModel = mDreamViewModel.get();
+ final CommunalTransitionViewModel communalViewModel =
+ mCommunalTransitionViewModel.get();
+
+ mJavaAdapter.alwaysCollectFlow(dreamViewModel.getDreamAlpha(),
+ getRemoteSurfaceAlphaApplier());
+ mJavaAdapter.alwaysCollectFlow(dreamViewModel.getTransitionEnded(),
+ getFinishedCallbackConsumer());
+ mJavaAdapter.alwaysCollectFlow(communalViewModel.getShowByDefault(),
+ (showByDefault) -> mShowCommunalByDefault = showByDefault);
+ mJavaAdapter.alwaysCollectFlow(communalViewModel.getTransitionFromOccludedEnded(),
+ getFinishedCallbackConsumer());
}
// Most services aren't available until the system reaches the ready state, so we
// send it here when the device first boots.
@@ -3559,15 +3550,14 @@
ShadeLockscreenInteractor shadeLockscreenInteractor,
@Nullable ShadeExpansionStateManager shadeExpansionStateManager,
BiometricUnlockController biometricUnlockController,
- View notificationContainer, KeyguardBypassController bypassController) {
+ View notificationContainer) {
mCentralSurfaces = centralSurfaces;
mKeyguardViewControllerLazy.get().registerCentralSurfaces(
centralSurfaces,
shadeLockscreenInteractor,
shadeExpansionStateManager,
biometricUnlockController,
- notificationContainer,
- bypassController);
+ notificationContainer);
return mKeyguardViewControllerLazy.get();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index bf1f074..eef4b97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -27,7 +27,7 @@
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -52,7 +52,6 @@
@Application private val applicationScope: CoroutineScope,
@Application private val context: Context,
deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
- private val sceneContainerFlags: SceneContainerFlags,
private val sceneInteractor: SceneInteractor,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
alternateBouncerInteractor: AlternateBouncerInteractor,
@@ -75,7 +74,7 @@
get() = context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
private val isBouncerSceneActive: Flow<Boolean> =
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
sceneInteractor.currentScene.map { it == Scenes.Bouncer }.distinctUntilChanged()
} else {
flowOf(false)
@@ -115,7 +114,7 @@
.distinctUntilChanged()
private fun isBouncerActive(): Boolean {
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
return sceneInteractor.currentScene.value == Scenes.Bouncer
}
return primaryBouncerInteractor.isBouncerShowing() &&
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 c476948..7224536 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
@@ -44,7 +44,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.CommandQueue
@@ -84,7 +84,6 @@
private val repository: KeyguardRepository,
private val commandQueue: CommandQueue,
powerInteractor: PowerInteractor,
- sceneContainerFlags: SceneContainerFlags,
bouncerRepository: KeyguardBouncerRepository,
configurationInteractor: ConfigurationInteractor,
shadeRepository: ShadeRepository,
@@ -331,7 +330,7 @@
/** Whether to animate the next doze mode transition. */
val animateDozingTransitions: Flow<Boolean> by lazy {
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
sceneInteractorProvider
.get()
.transitioningTo
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
index 80e94a2..20b7b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -23,12 +23,14 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.kotlin.toPx
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
/**
* Distance over which the surface behind the keyguard is animated in during a Y-translation
@@ -96,13 +98,21 @@
.distinctUntilChanged()
/**
+ * Whether a notification launch animation is running when we're not already in the GONE state.
+ */
+ private val isNotificationLaunchAnimationRunningOnKeyguard =
+ notificationLaunchInteractor.isLaunchAnimationRunning
+ .sample(transitionInteractor.finishedKeyguardState)
+ .map { it != KeyguardState.GONE }
+
+ /**
* Whether we're animating the surface, or a notification launch animation is running (which
* means we're going to animate the surface, even if animators aren't yet running).
*/
val isAnimatingSurface =
combine(
repository.isAnimatingSurface,
- notificationLaunchInteractor.isLaunchAnimationRunning
+ isNotificationLaunchAnimationRunningOnKeyguard,
) { animatingSurface, animatingLaunch ->
animatingSurface || animatingLaunch
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AccessibilityActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AccessibilityActionsViewBinder.kt
new file mode 100644
index 0000000..8f5a6a1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AccessibilityActionsViewBinder.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.os.Bundle
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.keyguard.ui.viewmodel.AccessibilityActionsViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+
+/** View binder for accessibility actions placeholder on keyguard. */
+object AccessibilityActionsViewBinder {
+ fun bind(
+ view: View,
+ viewModel: AccessibilityActionsViewModel,
+ ): DisposableHandle {
+ val disposableHandle =
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ view.contentDescription =
+ view.resources.getString(R.string.accessibility_desc_lock_screen)
+
+ launch {
+ viewModel.isOnKeyguard.collect { isOnKeyguard ->
+ view.importantForAccessibility =
+ if (isOnKeyguard) {
+ View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ } else {
+ // The border won't be displayed when keyguard is not showing or
+ // when the focus was previously on it but is now transitioning
+ // away from the keyguard.
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ }
+ }
+ }
+
+ launch {
+ viewModel.isCommunalAvailable.collect { canOpenGlanceableHub ->
+ view.accessibilityDelegate =
+ object : View.AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(
+ host: View,
+ info: AccessibilityNodeInfo
+ ) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ // Add custom actions
+ if (canOpenGlanceableHub) {
+ val action =
+ AccessibilityNodeInfo.AccessibilityAction(
+ R.id.accessibility_action_open_communal_hub,
+ view.resources.getString(
+ R.string
+ .accessibility_action_open_communal_hub
+ ),
+ )
+ info.addAction(action)
+ }
+ }
+
+ override fun performAccessibilityAction(
+ host: View,
+ action: Int,
+ args: Bundle?
+ ): Boolean {
+ return if (
+ action == R.id.accessibility_action_open_communal_hub
+ ) {
+ viewModel.openCommunalHub()
+ true
+ } else super.performAccessibilityAction(host, action, args)
+ }
+ }
+ }
+ }
+ }
+ }
+ return disposableHandle
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index b5d6177..6b8e896 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -38,6 +38,7 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
+import com.android.systemui.shared.R as sharedR
import javax.inject.Inject
import kotlin.math.max
@@ -217,15 +218,21 @@
if (!DEBUG || currentClock == null) return
val smallClockViewId = R.id.lockscreen_clock_view
val largeClockViewId = currentClock.largeClock.layout.views[0].id
+ val smartspaceDateId = sharedR.id.date_smartspace_view
Log.i(
TAG,
"applyCsToSmallClock: vis=${cs.getVisibility(smallClockViewId)} " +
- "alpha=${cs.getConstraint(smallClockViewId).propertySet}"
+ "alpha=${cs.getConstraint(smallClockViewId).propertySet.alpha}"
)
Log.i(
TAG,
"applyCsToLargeClock: vis=${cs.getVisibility(largeClockViewId)} " +
- "alpha=${cs.getConstraint(largeClockViewId).propertySet}"
+ "alpha=${cs.getConstraint(largeClockViewId).propertySet.alpha}"
+ )
+ Log.i(
+ TAG,
+ "applyCsToSmartspaceDate: vis=${cs.getVisibility(smartspaceDateId)} " +
+ "alpha=${cs.getConstraint(smartspaceDateId).propertySet.alpha}"
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 6255f0d4..7178e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -36,7 +36,6 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
import kotlinx.coroutines.launch
object KeyguardClockViewBinder {
@@ -76,13 +75,13 @@
}
launch {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
- viewModel.clockShouldBeCentered.collect { clockShouldBeCentered ->
+ viewModel.clockShouldBeCentered.collect {
viewModel.currentClock.value?.let {
- // Weather clock also has hasCustomPositionUpdatedAnimation as true
- // TODO(b/323020908): remove ID check
+ // TODO(b/301502635): remove "!it.config.useCustomClockScene" when
+ // migrate clocks to blueprint is fully rolled out
if (
it.largeClock.config.hasCustomPositionUpdatedAnimation &&
- it.config.id == DEFAULT_CLOCK_ID
+ !it.config.useCustomClockScene
) {
blueprintInteractor.refreshBlueprint(Type.DefaultClockStepping)
} else {
@@ -93,12 +92,9 @@
}
launch {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
- viewModel.isAodIconsVisible.collect { isAodIconsVisible ->
+ viewModel.isAodIconsVisible.collect {
viewModel.currentClock.value?.let {
- // Weather clock also has hasCustomPositionUpdatedAnimation as true
- if (
- viewModel.useLargeClock && it.config.id == "DIGITAL_CLOCK_WEATHER"
- ) {
+ if (viewModel.useLargeClock && it.config.useCustomClockScene) {
blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
}
}
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 5ee35e4f..cc54920 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
@@ -30,6 +30,9 @@
import android.view.ViewGroup.OnHierarchyChangeListener
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
@@ -49,6 +52,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
@@ -125,6 +129,21 @@
disposables +=
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
+ if (ComposeLockscreen.isEnabled) {
+ view.setViewTreeOnBackPressedDispatcherOwner(
+ object : OnBackPressedDispatcherOwner {
+ override val onBackPressedDispatcher =
+ OnBackPressedDispatcher().apply {
+ setOnBackInvokedDispatcher(
+ view.viewRootImpl.onBackInvokedDispatcher
+ )
+ }
+
+ override val lifecycle: Lifecycle =
+ [email protected]
+ }
+ )
+ }
launch {
occludingAppDeviceEntryMessageViewModel.message.collect { biometricMessage
->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index 77f7ac8..d5a9655 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -21,6 +21,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.view.layout.sections.AccessibilityActionsSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
@@ -52,6 +53,7 @@
class DefaultKeyguardBlueprint
@Inject
constructor(
+ accessibilityActionsSection: AccessibilityActionsSection,
defaultIndicationAreaSection: DefaultIndicationAreaSection,
defaultDeviceEntrySection: DefaultDeviceEntrySection,
defaultShortcutsSection: DefaultShortcutsSection,
@@ -73,6 +75,7 @@
override val sections =
listOfNotNull(
+ accessibilityActionsSection,
defaultIndicationAreaSection,
defaultShortcutsSection,
defaultAmbientIndicationAreaSection.getOrNull(),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index 55b2381..b984a68 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -21,6 +21,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.view.layout.sections.AccessibilityActionsSection
import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdfpsSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
@@ -47,6 +48,7 @@
class ShortcutsBesideUdfpsKeyguardBlueprint
@Inject
constructor(
+ accessibilityActionsSection: AccessibilityActionsSection,
alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
defaultIndicationAreaSection: DefaultIndicationAreaSection,
defaultDeviceEntrySection: DefaultDeviceEntrySection,
@@ -68,6 +70,7 @@
override val sections =
listOfNotNull(
+ accessibilityActionsSection,
defaultIndicationAreaSection,
alignShortcutsToUdfpsSection,
defaultAmbientIndicationAreaSection.getOrNull(),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index 8472a9f..3447177 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
@@ -21,6 +21,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.view.layout.sections.AccessibilityActionsSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
@@ -49,6 +50,7 @@
class SplitShadeKeyguardBlueprint
@Inject
constructor(
+ accessibilityActionsSection: AccessibilityActionsSection,
defaultIndicationAreaSection: DefaultIndicationAreaSection,
defaultDeviceEntrySection: DefaultDeviceEntrySection,
defaultShortcutsSection: DefaultShortcutsSection,
@@ -70,6 +72,7 @@
override val sections =
listOfNotNull(
+ accessibilityActionsSection,
defaultIndicationAreaSection,
defaultShortcutsSection,
defaultAmbientIndicationAreaSection.getOrNull(),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AccessibilityActionsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AccessibilityActionsSection.kt
new file mode 100644
index 0000000..5e5330e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AccessibilityActionsSection.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.Flags
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.binder.AccessibilityActionsViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.AccessibilityActionsViewModel
+import com.android.systemui.res.R
+import com.android.systemui.util.Utils
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+
+/**
+ * A placeholder section that provides shortcuts for navigating on the keyguard through
+ * accessibility actions.
+ */
+class AccessibilityActionsSection
+@Inject
+constructor(
+ private val context: Context,
+ private val accessibilityActionsViewModel: AccessibilityActionsViewModel,
+) : KeyguardSection() {
+ private var accessibilityActionsViewHandle: DisposableHandle? = null
+
+ override fun addViews(constraintLayout: ConstraintLayout) {
+ if (!communalEnabled(context)) {
+ return
+ }
+ val view = View(constraintLayout.context).apply { id = R.id.accessibility_actions_view }
+ constraintLayout.addView(view)
+ }
+
+ override fun bindData(constraintLayout: ConstraintLayout) {
+ if (!communalEnabled(context)) {
+ return
+ }
+ accessibilityActionsViewHandle =
+ AccessibilityActionsViewBinder.bind(
+ constraintLayout.requireViewById(R.id.accessibility_actions_view),
+ accessibilityActionsViewModel,
+ )
+ }
+
+ override fun applyConstraints(constraintSet: ConstraintSet) {
+ val accessibilityActionsViewId = R.id.accessibility_actions_view
+ constraintSet.apply {
+ // Starts from the bottom of the status bar.
+ connect(
+ accessibilityActionsViewId,
+ ConstraintSet.TOP,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.TOP,
+ Utils.getStatusBarHeaderHeightKeyguard(context)
+ )
+ connect(
+ accessibilityActionsViewId,
+ ConstraintSet.BOTTOM,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.BOTTOM,
+ )
+ // Full width
+ connect(
+ accessibilityActionsViewId,
+ ConstraintSet.START,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.START
+ )
+ connect(
+ accessibilityActionsViewId,
+ ConstraintSet.END,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.END
+ )
+ }
+ }
+
+ override fun removeViews(constraintLayout: ConstraintLayout) {
+ accessibilityActionsViewHandle?.dispose()
+ accessibilityActionsViewHandle = null
+ constraintLayout.removeView(R.id.accessibility_actions_view)
+ }
+}
+
+private fun communalEnabled(context: Context): Boolean {
+ return context.resources.getBoolean(R.bool.config_communalServiceEnabled) && Flags.communalHub()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index eaa5e33..9ec7a65 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.view.View
-import android.view.View.GONE
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintLayout
@@ -205,8 +204,7 @@
smartspaceController.requestSmartspaceUpdate()
constraintSet.apply {
- setVisibility(
- sharedR.id.weather_smartspace_view,
+ val weatherVisibility =
when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
true -> ConstraintSet.GONE
false ->
@@ -215,11 +213,18 @@
false -> ConstraintSet.GONE
}
}
+ setVisibility(sharedR.id.weather_smartspace_view, weatherVisibility)
+ setAlpha(
+ sharedR.id.weather_smartspace_view,
+ if (weatherVisibility == ConstraintSet.VISIBLE) 1f else 0f
)
- setVisibility(
- sharedR.id.date_smartspace_view,
+ val dateVisibility =
if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE
else ConstraintSet.VISIBLE
+ setVisibility(sharedR.id.date_smartspace_view, dateVisibility)
+ setAlpha(
+ sharedR.id.date_smartspace_view,
+ if (dateVisibility == ConstraintSet.VISIBLE) 1f else 0f
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt
new file mode 100644
index 0000000..34c1436
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** View model for accessibility actions placeholder on keyguard */
+class AccessibilityActionsViewModel
+@Inject
+constructor(
+ private val communalInteractor: CommunalInteractor,
+ keyguardInteractor: KeyguardInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) {
+ val isCommunalAvailable = communalInteractor.isCommunalAvailable
+
+ // Checks that we are fully in lockscreen, not transitioning to another state, and shade is not
+ // opened.
+ val isOnKeyguard =
+ combine(
+ keyguardTransitionInteractor.transitionValue(KeyguardState.LOCKSCREEN).map {
+ it == 1f
+ },
+ keyguardInteractor.statusBarState
+ ) { transitionFinishedOnLockscreen, statusBarState ->
+ transitionFinishedOnLockscreen && statusBarState == StatusBarState.KEYGUARD
+ }
+ .distinctUntilChanged()
+
+ fun openCommunalHub() = communalInteractor.changeScene(CommunalScenes.Communal)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
index 06a0c72..5a559fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
@@ -18,61 +18,29 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.graphics.Color
-import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TRANSITION_DURATION_MS
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
-import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.wm.shell.animation.Interpolators
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
@ExperimentalCoroutinesApi
class AlternateBouncerViewModel
@Inject
constructor(
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
- animationFlow: KeyguardTransitionAnimationFlow,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
// When we're fully transitioned to the AlternateBouncer, the alpha of the scrim should be:
private val alternateBouncerScrimAlpha = .66f
- private val toAlternateBouncerTransition =
- animationFlow
- .setup(
- duration = TRANSITION_DURATION_MS,
- from = null,
- to = ALTERNATE_BOUNCER,
- )
- .sharedFlow(
- duration = TRANSITION_DURATION_MS,
- onStep = { it },
- onFinish = { 1f },
- // Reset on cancel
- onCancel = { 0f },
- interpolator = Interpolators.FAST_OUT_SLOW_IN,
- )
- private val fromAlternateBouncerTransition =
- animationFlow
- .setup(
- TRANSITION_DURATION_MS,
- from = ALTERNATE_BOUNCER,
- to = null,
- )
- .sharedFlow(
- duration = TRANSITION_DURATION_MS,
- onStep = { 1f - it },
- // Reset on cancel
- onCancel = { 0f },
- interpolator = Interpolators.FAST_OUT_SLOW_IN,
- )
/** Progress to a fully transitioned alternate bouncer. 1f represents fully transitioned. */
val transitionToAlternateBouncerProgress =
- merge(fromAlternateBouncerTransition, toAlternateBouncerTransition)
+ keyguardTransitionInteractor.transitionValue(ALTERNATE_BOUNCER)
val forcePluginOpen: Flow<Boolean> =
transitionToAlternateBouncerProgress.map { it > 0f }.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 49fffdd..45dca99 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -30,7 +30,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.util.kotlin.sample
import dagger.Lazy
@@ -64,7 +64,6 @@
transitionInteractor: KeyguardTransitionInteractor,
val keyguardInteractor: KeyguardInteractor,
val viewModel: AodToLockscreenTransitionViewModel,
- private val sceneContainerFlags: SceneContainerFlags,
private val keyguardViewController: Lazy<KeyguardViewController>,
private val deviceEntryInteractor: DeviceEntryInteractor,
private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
@@ -242,7 +241,7 @@
}
suspend fun onLongPress() {
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
deviceEntryInteractor.attemptDeviceEntry()
} else {
keyguardViewController.get().showPrimaryBouncer(/* scrim */ true)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index f6da033..a6d3312 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -118,8 +118,7 @@
currentClock
) { isLargeClockVisible, clockShouldBeCentered, shadeMode, currentClock ->
val shouldUseSplitShade = shadeMode == ShadeMode.Split
- // TODO(b/326098079): make id a constant field in config
- if (currentClock?.config?.id == "DIGITAL_CLOCK_WEATHER") {
+ if (currentClock?.config?.useCustomClockScene == true) {
val weatherClockLayout =
when {
shouldUseSplitShade && clockShouldBeCentered ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 36896f9..ecad148 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -27,6 +27,7 @@
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -46,6 +47,7 @@
val longPress: KeyguardLongPressViewModel,
val shadeInteractor: ShadeInteractor,
@Application private val applicationScope: CoroutineScope,
+ private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
) {
private val clockSize = clockInteractor.clockSize
@@ -75,6 +77,23 @@
initialValue = false,
)
+ /** Amount of horizontal translation that should be applied to elements in the scene. */
+ val unfoldTranslations: StateFlow<UnfoldTranslations> =
+ combine(
+ unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true),
+ unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false),
+ ) { start, end ->
+ UnfoldTranslations(
+ start = start,
+ end = end,
+ )
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = UnfoldTranslations(),
+ )
+
fun getSmartSpacePaddingTop(resources: Resources): Int {
return if (isLargeClockVisible) {
resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
@@ -94,4 +113,20 @@
initialValue = interactor.getCurrentBlueprint().id,
)
}
+
+ data class UnfoldTranslations(
+
+ /**
+ * Amount of horizontal translation to apply to elements that are aligned to the start side
+ * (left in left-to-right layouts). Can also be used as horizontal padding for elements that
+ * need horizontal padding on both side. In pixels.
+ */
+ val start: Float = 0f,
+
+ /**
+ * Amount of horizontal translation to apply to elements that are aligned to the end side
+ * (right in left-to-right layouts). In pixels.
+ */
+ val end: Float = 0f,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceEntryIconLog.kt
similarity index 65%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
rename to packages/SystemUI/src/com/android/systemui/log/dagger/DeviceEntryIconLog.kt
index b84b01e..f3414b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceEntryIconLog.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,13 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.systemui.log.dagger
-package com.android.systemui.statusbar.pipeline.dagger
-
+import java.lang.annotation.Documented
import javax.inject.Qualifier
-/** Wifi logs for inputs into [WifiRepositoryViaTrackerLib]. */
-@Qualifier
-@MustBeDocumented
[email protected](AnnotationRetention.RUNTIME)
-annotation class WifiTrackerLibInputLog
+/** A [com.android.systemui.log.LogBuffer] for DeviceEntryIcon state. */
+@Qualifier @Documented @Retention(AnnotationRetention.RUNTIME) annotation class DeviceEntryIconLog
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 f2013be..5babc8b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -654,4 +654,13 @@
public static LogBuffer provideNavbarOrientationTrackingLogBuffer(LogBufferFactory factory) {
return factory.create("NavbarOrientationTrackingLog", 50);
}
+
+ /** Provides a {@link LogBuffer} for use by the DeviceEntryIcon and related classes. */
+ @Provides
+ @SysUISingleton
+ @DeviceEntryIconLog
+ public static LogBuffer provideDeviceEntryIconLogBuffer(LogBufferFactory factory) {
+ return factory.create("DeviceEntryIconLog", 100);
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/model/MediaSortKeyModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/model/MediaSortKeyModel.kt
new file mode 100644
index 0000000..cfe5cde
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/model/MediaSortKeyModel.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.data.model
+
+import com.android.internal.logging.InstanceId
+import com.android.systemui.media.controls.shared.model.MediaData.Companion.PLAYBACK_LOCAL
+
+data class MediaSortKeyModel(
+ /** Whether the item represents a Smartspace media recommendation that should be prioritized. */
+ val isPrioritizedRec: Boolean = false,
+ val isPlaying: Boolean? = null,
+ val playbackLocation: Int = PLAYBACK_LOCAL,
+ val active: Boolean = true,
+ val isResume: Boolean = false,
+ val lastActive: Long = 0L,
+ val notificationKey: String? = null,
+ val updateTime: Long = 0,
+ val instanceId: InstanceId? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
index 9dc5900..7e57cf4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
@@ -18,10 +18,14 @@
import com.android.internal.logging.InstanceId
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.controls.data.model.MediaSortKeyModel
+import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
+import com.android.systemui.util.time.SystemClock
+import java.util.TreeMap
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -29,7 +33,7 @@
/** A repository that holds the state of filtered media data on the device. */
@SysUISingleton
-class MediaFilterRepository @Inject constructor() {
+class MediaFilterRepository @Inject constructor(private val systemClock: SystemClock) {
/** Instance id of media control that recommendations card reactivated. */
private val _reactivatedId: MutableStateFlow<InstanceId?> = MutableStateFlow(null)
@@ -58,6 +62,26 @@
val recommendationsLoadingState: StateFlow<SmartspaceMediaLoadingModel> =
_recommendationsLoadingState.asStateFlow()
+ private val comparator =
+ compareByDescending<MediaSortKeyModel> {
+ it.isPlaying == true && it.playbackLocation == MediaData.PLAYBACK_LOCAL
+ }
+ .thenByDescending {
+ it.isPlaying == true && it.playbackLocation == MediaData.PLAYBACK_CAST_LOCAL
+ }
+ .thenByDescending { it.active }
+ .thenByDescending { it.isPrioritizedRec }
+ .thenByDescending { !it.isResume }
+ .thenByDescending { it.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE }
+ .thenByDescending { it.lastActive }
+ .thenByDescending { it.updateTime }
+ .thenByDescending { it.notificationKey }
+
+ private val _sortedMedia: MutableStateFlow<TreeMap<MediaSortKeyModel, MediaCommonModel>> =
+ MutableStateFlow(TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator))
+ val sortedMedia: StateFlow<Map<MediaSortKeyModel, MediaCommonModel>> =
+ _sortedMedia.asStateFlow()
+
fun addMediaEntry(key: String, data: MediaData) {
val entries = LinkedHashMap<String, MediaData>(_allUserEntries.value)
entries[key] = data
@@ -138,9 +162,91 @@
} else {
emptyList()
}
+
+ addMediaLoadingToSortedMap(mediaDataLoadingModel)
}
- fun setRecommedationsLoadingState(smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel) {
+ fun setRecommendationsLoadingState(smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel) {
_recommendationsLoadingState.value = smartspaceMediaLoadingModel
+
+ addRecsLoadingToSortedMap(smartspaceMediaLoadingModel)
+ }
+
+ private fun addMediaLoadingToSortedMap(mediaDataLoadingModel: MediaDataLoadingModel) {
+ val instanceId =
+ when (mediaDataLoadingModel) {
+ is MediaDataLoadingModel.Loaded -> mediaDataLoadingModel.instanceId
+ is MediaDataLoadingModel.Removed -> mediaDataLoadingModel.instanceId
+ MediaDataLoadingModel.Unknown -> null
+ }
+ val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)
+ sortedMap.putAll(
+ _sortedMedia.value.filter { (_, commonModel) ->
+ commonModel !is MediaCommonModel.MediaControl ||
+ commonModel.instanceId != instanceId
+ }
+ )
+
+ _selectedUserEntries.value[instanceId]?.let {
+ val sortKey =
+ MediaSortKeyModel(
+ isPrioritizedRec = false,
+ it.isPlaying,
+ it.playbackLocation,
+ it.active,
+ it.resumption,
+ it.lastActive,
+ it.notificationKey,
+ systemClock.currentTimeMillis(),
+ it.instanceId,
+ )
+
+ if (mediaDataLoadingModel is MediaDataLoadingModel.Loaded) {
+ sortedMap[sortKey] = MediaCommonModel.MediaControl(it.instanceId)
+ }
+ }
+
+ _sortedMedia.value = sortedMap
+ }
+
+ private fun addRecsLoadingToSortedMap(
+ smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel
+ ) {
+ val isPrioritized: Boolean
+ val key: String?
+ when (smartspaceMediaLoadingModel) {
+ is SmartspaceMediaLoadingModel.Loaded -> {
+ isPrioritized = smartspaceMediaLoadingModel.isPrioritized
+ key = smartspaceMediaLoadingModel.key
+ }
+ is SmartspaceMediaLoadingModel.Removed -> {
+ isPrioritized = false
+ key = smartspaceMediaLoadingModel.key
+ }
+ SmartspaceMediaLoadingModel.Unknown -> {
+ isPrioritized = false
+ key = null
+ }
+ }
+ val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)
+ sortedMap.putAll(
+ _sortedMedia.value.filter { (_, commonModel) ->
+ commonModel !is MediaCommonModel.MediaRecommendations || commonModel.key != key
+ }
+ )
+
+ key?.let {
+ val sortKey =
+ MediaSortKeyModel(
+ isPrioritizedRec = isPrioritized,
+ isPlaying = false,
+ active = _smartspaceMediaData.value.isActive,
+ )
+ if (smartspaceMediaLoadingModel is SmartspaceMediaLoadingModel.Loaded) {
+ sortedMap[sortKey] = MediaCommonModel.MediaRecommendations(key)
+ }
+ }
+
+ _sortedMedia.value = sortedMap
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
index a30e582..5432a18 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
@@ -69,7 +69,11 @@
private val mediaFlags: MediaFlags,
private val mediaFilterRepository: MediaFilterRepository,
) : MediaDataManager.Listener {
- lateinit var mediaDataManager: MediaDataManager
+ /** Non-UI listeners to media changes. */
+ private val _listeners: MutableSet<MediaDataProcessor.Listener> = mutableSetOf()
+ val listeners: Set<MediaDataProcessor.Listener>
+ get() = _listeners.toSet()
+ lateinit var mediaDataProcessor: MediaDataProcessor
// Ensure the field (and associated reference) isn't removed during optimization.
@KeepForWeakReference
@@ -113,6 +117,9 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Loaded(data.instanceId)
)
+
+ // Notify listeners
+ listeners.forEach { it.onMediaDataLoaded(key, oldKey, data) }
}
override fun onSmartspaceMediaDataLoaded(
@@ -171,6 +178,20 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Loaded(lastActiveId)
)
+ listeners.forEach { listener ->
+ getKey(lastActiveId)?.let { lastActiveKey ->
+ listener.onMediaDataLoaded(
+ lastActiveKey,
+ lastActiveKey,
+ mediaData,
+ receivedSmartspaceCardLatency =
+ (systemClock.currentTimeMillis() -
+ data.headphoneConnectionTimeMillis)
+ .toInt(),
+ isSsReactivated = true
+ )
+ }
+ }
}
} else if (data.isActive) {
// Mark to prioritize Smartspace card if no recent media.
@@ -186,9 +207,10 @@
smartspaceMediaData.packageName,
smartspaceMediaData.instanceId
)
- mediaFilterRepository.setRecommedationsLoadingState(
+ mediaFilterRepository.setRecommendationsLoadingState(
SmartspaceMediaLoadingModel.Loaded(key, shouldPrioritizeMutable)
)
+ listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
}
override fun onMediaDataRemoved(key: String) {
@@ -198,6 +220,8 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Removed(instanceId)
)
+ // Only notify listeners if something actually changed
+ listeners.forEach { it.onMediaDataRemoved(key) }
}
}
}
@@ -212,6 +236,11 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Loaded(lastActiveId, immediately)
)
+ listeners.forEach { listener ->
+ getKey(lastActiveId)?.let { lastActiveKey ->
+ listener.onMediaDataLoaded(lastActiveKey, lastActiveKey, it, immediately)
+ }
+ }
}
}
@@ -224,9 +253,10 @@
)
)
}
- mediaFilterRepository.setRecommedationsLoadingState(
+ mediaFilterRepository.setRecommendationsLoadingState(
SmartspaceMediaLoadingModel.Removed(key, immediately)
)
+ listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
}
@VisibleForTesting
@@ -240,6 +270,7 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Removed(data.instanceId)
)
+ listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
}
}
}
@@ -247,6 +278,7 @@
@VisibleForTesting
internal fun handleUserSwitched() {
// If the user changes, remove all current MediaData objects.
+ val listenersCopy = listeners
val keyCopy = mediaFilterRepository.selectedUserEntries.value.keys.toMutableList()
// Clear the list first and update loading state to remove media from UI.
mediaFilterRepository.clearSelectedUserMedia()
@@ -255,6 +287,9 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Removed(instanceId)
)
+ getKey(instanceId)?.let {
+ listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
+ }
}
mediaFilterRepository.allUserEntries.value.forEach { (key, data) ->
@@ -268,6 +303,7 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Loaded(data.instanceId)
)
+ listenersCopy.forEach { listener -> listener.onMediaDataLoaded(key, null, data) }
}
}
}
@@ -279,7 +315,7 @@
mediaEntries.forEach { (key, data) ->
if (mediaFilterRepository.selectedUserEntries.value.containsKey(data.instanceId)) {
// Force updates to listeners, needed for re-activated card
- mediaDataManager.setInactive(key, timedOut = true, forceUpdate = true)
+ mediaDataProcessor.setInactive(key, timedOut = true, forceUpdate = true)
}
}
val smartspaceMediaData = mediaFilterRepository.smartspaceMediaData.value
@@ -301,7 +337,7 @@
if (mediaFlags.isPersistentSsCardEnabled()) {
mediaFilterRepository.setRecommendation(smartspaceMediaData.copy(isActive = false))
- mediaDataManager.setRecommendationInactive(smartspaceMediaData.targetId)
+ mediaDataProcessor.setRecommendationInactive(smartspaceMediaData.targetId)
} else {
mediaFilterRepository.setRecommendation(
EMPTY_SMARTSPACE_MEDIA_DATA.copy(
@@ -309,7 +345,7 @@
instanceId = smartspaceMediaData.instanceId,
)
)
- mediaDataManager.dismissSmartspaceRecommendation(
+ mediaDataProcessor.dismissSmartspaceRecommendation(
smartspaceMediaData.targetId,
delay = 0L,
)
@@ -317,6 +353,12 @@
}
}
+ /** Add a listener for filtered [MediaData] changes */
+ fun addListener(listener: MediaDataProcessor.Listener) = _listeners.add(listener)
+
+ /** Remove a listener that was registered with addListener */
+ fun removeListener(listener: MediaDataProcessor.Listener) = _listeners.remove(listener)
+
/**
* Return the time since last active for the most-recent media.
*
@@ -336,6 +378,16 @@
return sortedEntries[lastActiveInstanceId]?.let { now - it.lastActive } ?: Long.MAX_VALUE
}
+ private fun getKey(instanceId: InstanceId): String? {
+ val allEntries = mediaFilterRepository.allUserEntries.value
+ val filteredEntries = allEntries.filter { (_, data) -> data.instanceId == instanceId }
+ return if (filteredEntries.isNotEmpty()) {
+ filteredEntries.keys.first()
+ } else {
+ null
+ }
+ }
+
companion object {
/**
* Maximum age of a media control to re-activate on smartspace signal. If there is no media
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index c7cfb0b..0e2814b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -35,6 +35,7 @@
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.media.PhoneMediaDevice
+import com.android.settingslib.media.flags.Flags
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.shared.model.MediaData
@@ -178,7 +179,9 @@
bgExecutor.execute {
if (!started) {
localMediaManager.registerCallback(this)
- localMediaManager.startScan()
+ if (!Flags.removeUnnecessaryRouteScanning()) {
+ localMediaManager.startScan()
+ }
muteAwaitConnectionManager.startListening()
playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
playbackVolumeControlId = controller?.playbackInfo?.volumeControlId
@@ -195,7 +198,9 @@
if (started) {
started = false
controller?.unregisterCallback(this)
- localMediaManager.stopScan()
+ if (!Flags.removeUnnecessaryRouteScanning()) {
+ localMediaManager.stopScan()
+ }
localMediaManager.unregisterCallback(this)
muteAwaitConnectionManager.stopListening()
configurationController.removeCallback(configListener)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
index cdcf363..b04e938 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
@@ -34,8 +34,10 @@
import com.android.systemui.media.controls.domain.pipeline.MediaSessionBasedFilter
import com.android.systemui.media.controls.domain.pipeline.MediaTimeoutListener
import com.android.systemui.media.controls.domain.resume.MediaResumeListener
+import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
+import com.android.systemui.media.controls.util.MediaControlsRefactorFlag
import com.android.systemui.media.controls.util.MediaFlags
import java.io.PrintWriter
import javax.inject.Inject
@@ -46,6 +48,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
@@ -120,6 +123,10 @@
val recommendationsLoadingState: Flow<SmartspaceMediaLoadingModel> =
mediaFilterRepository.recommendationsLoadingState
+ /** The most recent sorted set for user media instances */
+ val sortedMedia: Flow<List<MediaCommonModel>> =
+ mediaFilterRepository.sortedMedia.map { it.values.toList() }
+
override fun start() {
if (!mediaFlags.isMediaControlsRefactorEnabled()) {
return
@@ -150,13 +157,19 @@
mediaDataProcessor.onSessionDestroyed(key)
}
mediaResumeListener.setManager(this)
- mediaDataFilter.mediaDataManager = this
+ mediaDataFilter.mediaDataProcessor = mediaDataProcessor
}
- override fun setInactive(key: String, timedOut: Boolean, forceUpdate: Boolean) {
- mediaDataProcessor.setInactive(key, timedOut, forceUpdate)
+ override fun addListener(listener: MediaDataManager.Listener) {
+ mediaDataFilter.addListener(listener)
}
+ override fun removeListener(listener: MediaDataManager.Listener) {
+ mediaDataFilter.removeListener(listener)
+ }
+
+ override fun setInactive(key: String, timedOut: Boolean, forceUpdate: Boolean) = unsupported
+
override fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
mediaDataProcessor.onNotificationAdded(key, sbn)
}
@@ -201,9 +214,7 @@
return mediaDataProcessor.dismissSmartspaceRecommendation(key, delay)
}
- override fun setRecommendationInactive(key: String) {
- mediaDataProcessor.setRecommendationInactive(key)
- }
+ override fun setRecommendationInactive(key: String) = unsupported
override fun onNotificationRemoved(key: String) {
mediaDataProcessor.onNotificationRemoved(key)
@@ -234,4 +245,12 @@
override fun dump(pw: PrintWriter, args: Array<out String>) {
mediaDeviceManager.dump(pw)
}
+
+ companion object {
+ val unsupported: Nothing
+ get() =
+ error(
+ "Code path not supported when ${MediaControlsRefactorFlag.FLAG_NAME} is enabled"
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt
new file mode 100644
index 0000000..83e2765
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.shared.model
+
+import com.android.internal.logging.InstanceId
+
+/** Models any type of media. */
+sealed class MediaCommonModel {
+ data class MediaControl(val instanceId: InstanceId) : MediaCommonModel()
+
+ data class MediaRecommendations(val key: String) : MediaCommonModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
new file mode 100644
index 0000000..8dd3379
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -0,0 +1,644 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.binder
+
+import android.content.Context
+import android.graphics.BlendMode
+import android.graphics.Color
+import android.graphics.ColorMatrix
+import android.graphics.ColorMatrixColorFilter
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.LayerDrawable
+import android.graphics.drawable.TransitionDrawable
+import android.os.Trace
+import android.util.Pair
+import android.view.Gravity
+import android.view.View
+import android.widget.ImageButton
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settingslib.widget.AdaptiveIcon
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.media.controls.ui.animation.AnimationBindHandler
+import com.android.systemui.media.controls.ui.animation.ColorSchemeTransition
+import com.android.systemui.media.controls.ui.controller.MediaViewController
+import com.android.systemui.media.controls.ui.util.MediaArtworkHelper
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.MediaActionViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_END_ALPHA
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.MEDIA_PLAYER_SCRIM_START_ALPHA
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.SEMANTIC_ACTIONS_ALL
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.Companion.SEMANTIC_ACTIONS_COMPACT
+import com.android.systemui.media.controls.ui.viewmodel.MediaOutputSwitcherViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaPlayerViewModel
+import com.android.systemui.media.controls.util.MediaDataUtils
+import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.monet.ColorScheme
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.ripple.RippleAnimation
+import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig
+import com.android.systemui.surfaceeffects.ripple.RippleShader
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+object MediaControlViewBinder {
+
+ fun bind(
+ viewHolder: MediaViewHolder,
+ viewModel: MediaControlViewModel,
+ viewController: MediaViewController,
+ falsingManager: FalsingManager,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ @Main mainDispatcher: CoroutineDispatcher,
+ mediaFlags: MediaFlags,
+ ) {
+ val mediaCard = viewHolder.player
+ mediaCard.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.player.collectLatest { playerViewModel ->
+ playerViewModel?.let {
+ bindMediaCard(
+ viewHolder,
+ viewController,
+ it,
+ falsingManager,
+ backgroundDispatcher,
+ mainDispatcher,
+ mediaFlags
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private suspend fun bindMediaCard(
+ viewHolder: MediaViewHolder,
+ viewController: MediaViewController,
+ viewModel: MediaPlayerViewModel,
+ falsingManager: FalsingManager,
+ backgroundDispatcher: CoroutineDispatcher,
+ mainDispatcher: CoroutineDispatcher,
+ mediaFlags: MediaFlags,
+ ) {
+ with(viewHolder) {
+ // AlbumView uses a hardware layer so that clipping of the foreground is handled with
+ // clipping the album art. Otherwise album art shows through at the edges.
+ albumView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ turbulenceNoiseView.setBlendMode(BlendMode.SCREEN)
+ loadingEffectView.setBlendMode(BlendMode.SCREEN)
+ loadingEffectView.visibility = View.INVISIBLE
+
+ player.contentDescription =
+ viewModel.contentDescription.invoke(viewController.isGutsVisible)
+ player.setOnClickListener {
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ if (!viewController.isGutsVisible) {
+ viewModel.onClicked(Expandable.fromView(player))
+ }
+ }
+ }
+ player.setOnLongClickListener {
+ if (!falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
+ if (!viewController.isGutsVisible) {
+ openGuts(viewHolder, viewController, viewModel)
+ } else {
+ closeGuts(viewHolder, viewController, viewModel)
+ }
+ }
+ return@setOnLongClickListener true
+ }
+ }
+
+ viewController.bindSeekBar(viewModel.onSeek, viewModel.onBindSeekbar)
+ bindOutputSwitcherModel(
+ viewHolder,
+ viewModel.outputSwitcher,
+ viewController,
+ falsingManager
+ )
+ bindGutsViewModel(viewHolder, viewModel, viewController, falsingManager)
+ bindActionButtons(viewHolder, viewModel, viewController, falsingManager)
+ bindScrubbingTime(viewHolder, viewModel, viewController)
+
+ val isSongUpdated = bindSongMetadata(viewHolder, viewModel, viewController)
+
+ bindArtworkAndColor(
+ viewHolder,
+ viewModel,
+ viewController,
+ backgroundDispatcher,
+ mainDispatcher,
+ mediaFlags,
+ isSongUpdated
+ )
+
+ // TODO: We don't need to refresh this state constantly, only if the
+ // state actually changed to something which might impact the
+ // measurement. State refresh interferes with the translation
+ // animation, only run it if it's not running.
+ if (!viewController.metadataAnimationHandler.isRunning) {
+ // Don't refresh in scene framework, because it will calculate
+ // with invalid layout sizes
+ if (!mediaFlags.isSceneContainerEnabled()) {
+ viewController.refreshState()
+ }
+ }
+
+ if (viewModel.playTurbulenceNoise) {
+ viewController.setUpTurbulenceNoise()
+ }
+ }
+
+ private fun bindOutputSwitcherModel(
+ viewHolder: MediaViewHolder,
+ viewModel: MediaOutputSwitcherViewModel,
+ viewController: MediaViewController,
+ falsingManager: FalsingManager,
+ ) {
+ with(viewHolder.seamless) {
+ visibility = View.VISIBLE
+ isEnabled = viewModel.isTapEnabled
+ contentDescription = viewModel.deviceString
+ setOnClickListener {
+ if (!falsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY)) {
+ viewModel.onClicked.invoke(Expandable.fromView(viewHolder.seamlessButton))
+ }
+ }
+ }
+ when (viewModel.deviceIcon) {
+ is Icon.Loaded -> {
+ val icon = viewModel.deviceIcon.drawable
+ if (icon is AdaptiveIcon) {
+ icon.setBackgroundColor(viewController.colorSchemeTransition.bgColor)
+ }
+ viewHolder.seamlessIcon.setImageDrawable(icon)
+ }
+ is Icon.Resource -> viewHolder.seamlessIcon.setImageResource(viewModel.deviceIcon.res)
+ }
+ viewHolder.seamlessButton.alpha = viewModel.alpha
+ viewHolder.seamlessText.text = viewModel.deviceString
+ }
+
+ private fun bindGutsViewModel(
+ viewHolder: MediaViewHolder,
+ viewModel: MediaPlayerViewModel,
+ viewController: MediaViewController,
+ falsingManager: FalsingManager,
+ ) {
+ val gutsViewHolder = viewHolder.gutsViewHolder
+ val model = viewModel.gutsMenu
+ with(gutsViewHolder) {
+ gutsText.text = model.gutsText
+ dismissText.visibility = if (model.isDismissEnabled) View.VISIBLE else View.GONE
+ dismiss.isEnabled = model.isDismissEnabled
+ dismiss.setOnClickListener {
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ model.onDismissClicked.invoke()
+ }
+ }
+ cancelText.background = model.cancelTextBackground
+ cancel.setOnClickListener {
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ closeGuts(viewHolder, viewController, viewModel)
+ }
+ }
+ settings.setOnClickListener {
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ model.onSettingsClicked.invoke()
+ }
+ }
+ setDismissible(model.isDismissEnabled)
+ setTextPrimaryColor(model.textPrimaryColor)
+ setAccentPrimaryColor(model.accentPrimaryColor)
+ setSurfaceColor(model.surfaceColor)
+ }
+ }
+
+ private fun bindActionButtons(
+ viewHolder: MediaViewHolder,
+ viewModel: MediaPlayerViewModel,
+ viewController: MediaViewController,
+ falsingManager: FalsingManager,
+ ) {
+ val genericButtons = MediaViewHolder.genericButtonIds.map { viewHolder.getAction(it) }
+ val expandedSet = viewController.expandedLayout
+ val collapsedSet = viewController.collapsedLayout
+ if (viewModel.useSemanticActions) {
+ // Hide all generic buttons
+ genericButtons.forEach {
+ setVisibleAndAlpha(expandedSet, it.id, false)
+ setVisibleAndAlpha(collapsedSet, it.id, false)
+ }
+
+ SEMANTIC_ACTIONS_ALL.forEachIndexed { index, id ->
+ val button = viewHolder.getAction(id)
+ val actionViewModel = viewModel.actionButtons[index]
+ if (button.id == R.id.actionPrev) {
+ actionViewModel?.let {
+ viewController.setUpPrevButtonInfo(true, it.notVisibleValue)
+ }
+ } else if (button.id == R.id.actionNext) {
+ actionViewModel?.let {
+ viewController.setUpNextButtonInfo(true, it.notVisibleValue)
+ }
+ }
+ actionViewModel?.let { action ->
+ val animHandler = (button.tag ?: AnimationBindHandler()) as AnimationBindHandler
+ animHandler.tryExecute {
+ if (animHandler.updateRebindId(action.rebindId)) {
+ animHandler.unregisterAll()
+ animHandler.tryRegister(action.icon)
+ animHandler.tryRegister(action.background)
+ bindButtonCommon(
+ button,
+ viewHolder.multiRippleView,
+ action,
+ viewController,
+ falsingManager,
+ )
+ }
+ val visible = action.isVisibleWhenScrubbing || !viewController.isScrubbing
+ setSemanticButtonVisibleAndAlpha(
+ viewHolder.getAction(id),
+ viewController.expandedLayout,
+ viewController.collapsedLayout,
+ visible,
+ action.notVisibleValue,
+ action.showInCollapsed
+ )
+ }
+ }
+ ?: clearButton(button)
+ }
+ } else {
+ // Hide buttons that only appear for semantic actions
+ SEMANTIC_ACTIONS_COMPACT.forEach { buttonId ->
+ setVisibleAndAlpha(expandedSet, buttonId, visible = false)
+ setVisibleAndAlpha(expandedSet, buttonId, visible = false)
+ }
+
+ // Set all generic buttons
+ genericButtons.forEachIndexed { index, button ->
+ if (index < viewModel.actionButtons.size) {
+ viewModel.actionButtons[index]?.let { action ->
+ bindButtonCommon(
+ button,
+ viewHolder.multiRippleView,
+ action,
+ viewController,
+ falsingManager,
+ )
+ setVisibleAndAlpha(expandedSet, button.id, visible = true)
+ setVisibleAndAlpha(
+ collapsedSet,
+ button.id,
+ visible = action.showInCollapsed
+ )
+ }
+ ?: clearButton(button)
+ } else {
+ // Hide any unused buttons
+ clearButton(button)
+ setVisibleAndAlpha(expandedSet, button.id, visible = false)
+ setVisibleAndAlpha(collapsedSet, button.id, visible = false)
+ }
+ }
+ }
+ updateSeekBarVisibility(viewController.expandedLayout, isSeekBarEnabled = false)
+ }
+
+ private fun bindButtonCommon(
+ button: ImageButton,
+ multiRippleView: MultiRippleView,
+ actionViewModel: MediaActionViewModel,
+ viewController: MediaViewController,
+ falsingManager: FalsingManager,
+ ) {
+ button.setImageDrawable(actionViewModel.icon)
+ button.background = actionViewModel.background
+ button.contentDescription = actionViewModel.contentDescription
+ button.isEnabled = actionViewModel.isEnabled
+ if (actionViewModel.isEnabled) {
+ button.setOnClickListener {
+ if (!falsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY)) {
+ actionViewModel.onClicked.invoke(it.id)
+
+ viewController.multiRippleController.play(
+ createTouchRippleAnimation(
+ button,
+ viewController.colorSchemeTransition,
+ multiRippleView
+ )
+ )
+
+ if (actionViewModel.icon is Animatable) {
+ actionViewModel.icon.start()
+ }
+
+ if (actionViewModel.background is Animatable) {
+ actionViewModel.background.start()
+ }
+ }
+ }
+ }
+ }
+
+ private fun bindSongMetadata(
+ viewHolder: MediaViewHolder,
+ viewModel: MediaPlayerViewModel,
+ viewController: MediaViewController,
+ ): Boolean {
+ val expandedSet = viewController.expandedLayout
+ val collapsedSet = viewController.collapsedLayout
+
+ return viewController.metadataAnimationHandler.setNext(
+ Triple(viewModel.titleName, viewModel.artistName, viewModel.isExplicitVisible),
+ {
+ viewHolder.titleText.text = viewModel.titleName
+ viewHolder.artistText.text = viewModel.artistName
+ setVisibleAndAlpha(
+ expandedSet,
+ R.id.media_explicit_indicator,
+ viewModel.isExplicitVisible
+ )
+ setVisibleAndAlpha(
+ collapsedSet,
+ R.id.media_explicit_indicator,
+ viewModel.isExplicitVisible
+ )
+
+ // refreshState is required here to resize the text views (and prevent ellipsis)
+ viewController.refreshState()
+ },
+ {
+ // After finishing the enter animation, we refresh state. This could pop if
+ // something is incorrectly bound, but needs to be run if other elements were
+ // updated while the enter animation was running
+ viewController.refreshState()
+ }
+ )
+ }
+
+ private suspend fun bindArtworkAndColor(
+ viewHolder: MediaViewHolder,
+ viewModel: MediaPlayerViewModel,
+ viewController: MediaViewController,
+ backgroundDispatcher: CoroutineDispatcher,
+ mainDispatcher: CoroutineDispatcher,
+ mediaFlags: MediaFlags,
+ updateBackground: Boolean,
+ ) {
+ val traceCookie = viewHolder.hashCode()
+ val traceName = "MediaControlViewBinder#bindArtworkAndColor"
+ Trace.beginAsyncSection(traceName, traceCookie)
+ if (updateBackground) {
+ viewController.isArtworkBound = false
+ }
+ // Capture width & height from views in foreground for artwork scaling in background
+ var width = viewHolder.albumView.measuredWidth
+ var height = viewHolder.albumView.measuredHeight
+ if (mediaFlags.isSceneContainerEnabled() && (width <= 0 || height <= 0)) {
+ // TODO(b/312714128): ensure we have a valid size before setting background
+ width = viewController.widthInSceneContainerPx
+ height = viewController.heightInSceneContainerPx
+ }
+ withContext(backgroundDispatcher) {
+ val artwork =
+ if (viewModel.shouldAddGradient) {
+ addGradientToPlayerAlbum(
+ viewHolder.albumView.context,
+ viewModel.backgroundCover!!,
+ viewModel.colorScheme,
+ width,
+ height
+ )
+ } else {
+ ColorDrawable(Color.TRANSPARENT)
+ }
+ withContext(mainDispatcher) {
+ // Transition Colors to current color scheme
+ val colorSchemeChanged =
+ viewController.colorSchemeTransition.updateColorScheme(viewModel.colorScheme)
+ val albumView = viewHolder.albumView
+ albumView.setPadding(0, 0, 0, 0)
+ if (
+ updateBackground ||
+ colorSchemeChanged ||
+ (!viewController.isArtworkBound && viewModel.shouldAddGradient)
+ ) {
+ viewController.prevArtwork?.let {
+ // Since we throw away the last transition, this will pop if your
+ // backgrounds are cycled too fast (or the correct background arrives very
+ // soon after the metadata changes).
+ val transitionDrawable = TransitionDrawable(arrayOf(it, artwork))
+
+ scaleTransitionDrawableLayer(transitionDrawable, 0, width, height)
+ scaleTransitionDrawableLayer(transitionDrawable, 1, width, height)
+ transitionDrawable.setLayerGravity(0, Gravity.CENTER)
+ transitionDrawable.setLayerGravity(1, Gravity.CENTER)
+ transitionDrawable.isCrossFadeEnabled = true
+
+ albumView.setImageDrawable(transitionDrawable)
+ transitionDrawable.startTransition(
+ if (viewModel.shouldAddGradient) 333 else 80
+ )
+ }
+ }
+ viewController.isArtworkBound = viewModel.shouldAddGradient
+ viewController.prevArtwork = artwork
+
+ if (viewModel.useGrayColorFilter) {
+ // Used for resume players to use launcher icon
+ viewHolder.appIcon.colorFilter = getGrayscaleFilter()
+ when (viewModel.launcherIcon) {
+ is Icon.Loaded ->
+ viewHolder.appIcon.setImageDrawable(viewModel.launcherIcon.drawable)
+ is Icon.Resource ->
+ viewHolder.appIcon.setImageResource(viewModel.launcherIcon.res)
+ }
+ } else {
+ viewHolder.appIcon.setColorFilter(
+ viewController.colorSchemeTransition.accentPrimary.targetColor
+ )
+ viewHolder.appIcon.setImageIcon(viewModel.appIcon)
+ }
+ Trace.endAsyncSection(traceName, traceCookie)
+ }
+ }
+ }
+
+ private fun scaleTransitionDrawableLayer(
+ transitionDrawable: TransitionDrawable,
+ layer: Int,
+ targetWidth: Int,
+ targetHeight: Int
+ ) {
+ val drawable = transitionDrawable.getDrawable(layer) ?: return
+ val width = drawable.intrinsicWidth
+ val height = drawable.intrinsicHeight
+ val scale =
+ MediaDataUtils.getScaleFactor(Pair(width, height), Pair(targetWidth, targetHeight))
+ if (scale == 0f) return
+ transitionDrawable.setLayerSize(layer, (scale * width).toInt(), (scale * height).toInt())
+ }
+
+ private fun addGradientToPlayerAlbum(
+ context: Context,
+ artworkIcon: android.graphics.drawable.Icon,
+ mutableColorScheme: ColorScheme,
+ width: Int,
+ height: Int
+ ): LayerDrawable {
+ val albumArt = MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height)
+ return MediaArtworkHelper.setUpGradientColorOnDrawable(
+ albumArt,
+ context.getDrawable(R.drawable.qs_media_scrim)?.mutate() as GradientDrawable,
+ mutableColorScheme,
+ MEDIA_PLAYER_SCRIM_START_ALPHA,
+ MEDIA_PLAYER_SCRIM_END_ALPHA
+ )
+ }
+
+ private fun clearButton(button: ImageButton) {
+ button.setImageDrawable(null)
+ button.contentDescription = null
+ button.isEnabled = false
+ button.background = null
+ }
+
+ private fun bindScrubbingTime(
+ viewHolder: MediaViewHolder,
+ viewModel: MediaPlayerViewModel,
+ viewController: MediaViewController,
+ ) {
+ val expandedSet = viewController.expandedLayout
+ val visible = viewModel.canShowTime && viewController.isScrubbing
+ viewController.canShowScrubbingTime = viewModel.canShowTime
+ setVisibleAndAlpha(expandedSet, viewHolder.scrubbingElapsedTimeView.id, visible)
+ setVisibleAndAlpha(expandedSet, viewHolder.scrubbingTotalTimeView.id, visible)
+ // Collapsed view is always GONE as set in XML, so doesn't need to be updated dynamically.
+ }
+
+ private fun createTouchRippleAnimation(
+ button: ImageButton,
+ colorSchemeTransition: ColorSchemeTransition,
+ multiRippleView: MultiRippleView
+ ): RippleAnimation {
+ val maxSize = (multiRippleView.width * 2).toFloat()
+ return RippleAnimation(
+ RippleAnimationConfig(
+ RippleShader.RippleShape.CIRCLE,
+ duration = 1500L,
+ centerX = button.x + button.width * 0.5f,
+ centerY = button.y + button.height * 0.5f,
+ maxSize,
+ maxSize,
+ button.context.resources.displayMetrics.density,
+ colorSchemeTransition.accentPrimary.currentColor,
+ opacity = 100,
+ sparkleStrength = 0f,
+ baseRingFadeParams = null,
+ sparkleRingFadeParams = null,
+ centerFillFadeParams = null,
+ shouldDistort = false
+ )
+ )
+ }
+
+ private fun openGuts(
+ viewHolder: MediaViewHolder,
+ viewController: MediaViewController,
+ viewModel: MediaPlayerViewModel,
+ ) {
+ viewHolder.marquee(true, MediaViewController.GUTS_ANIMATION_DURATION)
+ viewController.openGuts()
+ viewHolder.player.contentDescription = viewModel.contentDescription.invoke(true)
+ viewModel.onLongClicked.invoke()
+ }
+
+ private fun closeGuts(
+ viewHolder: MediaViewHolder,
+ viewController: MediaViewController,
+ viewModel: MediaPlayerViewModel,
+ ) {
+ viewHolder.marquee(false, MediaViewController.GUTS_ANIMATION_DURATION)
+ viewController.closeGuts(false)
+ viewHolder.player.contentDescription = viewModel.contentDescription.invoke(false)
+ }
+
+ fun setVisibleAndAlpha(set: ConstraintSet, resId: Int, visible: Boolean) {
+ setVisibleAndAlpha(set, resId, visible, ConstraintSet.GONE)
+ }
+
+ private fun setVisibleAndAlpha(
+ set: ConstraintSet,
+ resId: Int,
+ visible: Boolean,
+ notVisibleValue: Int
+ ) {
+ set.setVisibility(resId, if (visible) ConstraintSet.VISIBLE else notVisibleValue)
+ set.setAlpha(resId, if (visible) 1.0f else 0.0f)
+ }
+
+ fun updateSeekBarVisibility(constraintSet: ConstraintSet, isSeekBarEnabled: Boolean) {
+ if (isSeekBarEnabled) {
+ constraintSet.setVisibility(R.id.media_progress_bar, ConstraintSet.VISIBLE)
+ constraintSet.setAlpha(R.id.media_progress_bar, 1.0f)
+ } else {
+ constraintSet.setVisibility(R.id.media_progress_bar, ConstraintSet.INVISIBLE)
+ constraintSet.setAlpha(R.id.media_progress_bar, 0.0f)
+ }
+ }
+
+ fun setSemanticButtonVisibleAndAlpha(
+ button: ImageButton,
+ expandedSet: ConstraintSet,
+ collapsedSet: ConstraintSet,
+ visible: Boolean,
+ notVisibleValue: Int,
+ showInCollapsed: Boolean
+ ) {
+ if (notVisibleValue == ConstraintSet.INVISIBLE) {
+ // Since time views should appear instead of buttons.
+ button.isFocusable = visible
+ button.isClickable = visible
+ }
+ setVisibleAndAlpha(expandedSet, button.id, visible, notVisibleValue)
+ setVisibleAndAlpha(collapsedSet, button.id, visible = visible && showInCollapsed)
+ }
+
+ private fun getGrayscaleFilter(): ColorMatrixColorFilter {
+ val matrix = ColorMatrix()
+ matrix.setSaturation(0f)
+ return ColorMatrixColorFilter(matrix)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt
new file mode 100644
index 0000000..9c6d59e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.binder
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.content.res.Configuration
+import android.graphics.Matrix
+import android.util.TypedValue
+import android.view.View
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.animation.Expandable
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.media.controls.shared.model.NUM_REQUIRED_RECOMMENDATIONS
+import com.android.systemui.media.controls.ui.controller.MediaViewController
+import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.MediaRecViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaRecommendationsViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaRecsCardViewModel
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
+import com.android.systemui.util.animation.TransitionLayout
+import kotlin.math.min
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+object MediaRecommendationsViewBinder {
+
+ /** Binds recommendations view holder to the given view-model */
+ fun bind(
+ viewHolder: RecommendationViewHolder,
+ viewModel: MediaRecommendationsViewModel,
+ mediaViewController: MediaViewController,
+ falsingManager: FalsingManager,
+ ) {
+ mediaViewController.recsConfigurationChangeListener = this::updateRecommendationsVisibility
+ val cardView = viewHolder.recommendations
+ cardView.repeatWhenAttached {
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.mediaRecsCard.collectLatest { viewModel ->
+ viewModel?.let {
+ bindRecsCard(viewHolder, it, mediaViewController, falsingManager)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun bindRecsCard(
+ viewHolder: RecommendationViewHolder,
+ viewModel: MediaRecsCardViewModel,
+ mediaViewController: MediaViewController,
+ falsingManager: FalsingManager,
+ ) {
+ // Bind main card.
+ viewHolder.cardTitle.setTextColor(viewModel.cardTitleColor)
+ viewHolder.recommendations.backgroundTintList = ColorStateList.valueOf(viewModel.cardColor)
+ viewHolder.recommendations.contentDescription =
+ viewModel.contentDescription.invoke(mediaViewController.isGutsVisible)
+
+ viewHolder.recommendations.setOnClickListener {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener
+ viewModel.onClicked(Expandable.fromView(it))
+ }
+
+ viewHolder.recommendations.setOnLongClickListener {
+ if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY))
+ return@setOnLongClickListener true
+ if (!mediaViewController.isGutsVisible) {
+ openGuts(viewHolder, viewModel, mediaViewController)
+ } else {
+ closeGuts(viewHolder, viewModel, mediaViewController)
+ }
+ return@setOnLongClickListener true
+ }
+
+ // Bind all recommendations.
+ bindRecommendationsList(viewHolder, viewModel.mediaRecs, falsingManager)
+ updateRecommendationsVisibility(mediaViewController, viewHolder.recommendations)
+
+ // Set visibility of recommendations.
+ val expandedSet: ConstraintSet = mediaViewController.expandedLayout
+ val collapsedSet: ConstraintSet = mediaViewController.collapsedLayout
+ viewHolder.mediaTitles.forEach {
+ setVisibleAndAlpha(expandedSet, it.id, viewModel.areTitlesVisible)
+ setVisibleAndAlpha(collapsedSet, it.id, viewModel.areTitlesVisible)
+ }
+ viewHolder.mediaSubtitles.forEach {
+ setVisibleAndAlpha(expandedSet, it.id, viewModel.areSubtitlesVisible)
+ setVisibleAndAlpha(collapsedSet, it.id, viewModel.areSubtitlesVisible)
+ }
+
+ bindRecommendationsGuts(viewHolder, viewModel, mediaViewController, falsingManager)
+
+ mediaViewController.refreshState()
+ }
+
+ private fun bindRecommendationsGuts(
+ viewHolder: RecommendationViewHolder,
+ viewModel: MediaRecsCardViewModel,
+ mediaViewController: MediaViewController,
+ falsingManager: FalsingManager,
+ ) {
+ val gutsViewHolder = viewHolder.gutsViewHolder
+ val gutsViewModel = viewModel.gutsMenu
+
+ gutsViewHolder.gutsText.text = gutsViewModel.gutsText
+ gutsViewHolder.dismissText.visibility = View.VISIBLE
+ gutsViewHolder.dismiss.isEnabled = true
+ gutsViewHolder.dismiss.setOnClickListener {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener
+ closeGuts(viewHolder, viewModel, mediaViewController)
+ gutsViewModel.onDismissClicked.invoke()
+ }
+
+ gutsViewHolder.cancelText.background = gutsViewModel.cancelTextBackground
+ gutsViewHolder.cancel.setOnClickListener {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ closeGuts(viewHolder, viewModel, mediaViewController)
+ }
+ }
+
+ gutsViewHolder.settings.setOnClickListener {
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ gutsViewModel.onSettingsClicked.invoke()
+ }
+ }
+
+ gutsViewHolder.setDismissible(gutsViewModel.isDismissEnabled)
+ gutsViewHolder.setTextPrimaryColor(gutsViewModel.textPrimaryColor)
+ gutsViewHolder.setAccentPrimaryColor(gutsViewModel.accentPrimaryColor)
+ gutsViewHolder.setSurfaceColor(gutsViewModel.surfaceColor)
+ }
+
+ private fun bindRecommendationsList(
+ viewHolder: RecommendationViewHolder,
+ mediaRecs: List<MediaRecViewModel>,
+ falsingManager: FalsingManager
+ ) {
+ mediaRecs.forEachIndexed { index, mediaRecViewModel ->
+ if (index >= NUM_REQUIRED_RECOMMENDATIONS) return@forEachIndexed
+
+ val appIconView = viewHolder.mediaAppIcons[index]
+ appIconView.clearColorFilter()
+ if (mediaRecViewModel.appIcon != null) {
+ appIconView.setImageDrawable(mediaRecViewModel.appIcon)
+ } else {
+ appIconView.setImageResource(R.drawable.ic_music_note)
+ }
+
+ val mediaCoverContainer = viewHolder.mediaCoverContainers[index]
+ mediaCoverContainer.setOnClickListener {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener
+ mediaRecViewModel.onClicked.invoke(Expandable.fromView(it), index)
+ }
+ mediaCoverContainer.setOnLongClickListener {
+ if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY))
+ return@setOnLongClickListener true
+ (it.parent as View).performLongClick()
+ return@setOnLongClickListener true
+ }
+
+ val mediaCover = viewHolder.mediaCoverItems[index]
+ val width: Int =
+ mediaCover.context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_width)
+ val height: Int =
+ mediaCover.context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_rec_album_height_expanded
+ )
+ val coverMatrix = Matrix(mediaCover.imageMatrix)
+ coverMatrix.postScale(1.25f, 1.25f, 0.5f * width, 0.5f * height)
+ mediaCover.imageMatrix = coverMatrix
+ mediaCover.setImageDrawable(mediaRecViewModel.albumIcon)
+ mediaCover.contentDescription = mediaRecViewModel.contentDescription
+
+ val title = viewHolder.mediaTitles[index]
+ title.text = mediaRecViewModel.title
+ title.setTextColor(ColorStateList.valueOf(mediaRecViewModel.titleColor))
+
+ val subtitle = viewHolder.mediaSubtitles[index]
+ subtitle.text = mediaRecViewModel.subtitle
+ subtitle.setTextColor(ColorStateList.valueOf(mediaRecViewModel.subtitleColor))
+
+ val progressBar = viewHolder.mediaProgressBars[index]
+ progressBar.progress = mediaRecViewModel.progress
+ progressBar.progressTintList = ColorStateList.valueOf(mediaRecViewModel.progressColor)
+ if (mediaRecViewModel.progress == 0) {
+ progressBar.visibility = View.GONE
+ }
+ }
+ }
+
+ private fun openGuts(
+ viewHolder: RecommendationViewHolder,
+ viewModel: MediaRecsCardViewModel,
+ mediaViewController: MediaViewController,
+ ) {
+ viewHolder.marquee(true, MediaViewController.GUTS_ANIMATION_DURATION)
+ mediaViewController.openGuts()
+ viewHolder.recommendations.contentDescription = viewModel.contentDescription.invoke(true)
+ viewModel.onLongClicked.invoke()
+ }
+
+ private fun closeGuts(
+ viewHolder: RecommendationViewHolder,
+ mediaRecsCardViewModel: MediaRecsCardViewModel,
+ mediaViewController: MediaViewController,
+ ) {
+ viewHolder.marquee(false, MediaViewController.GUTS_ANIMATION_DURATION)
+ mediaViewController.closeGuts(false)
+ viewHolder.recommendations.contentDescription =
+ mediaRecsCardViewModel.contentDescription.invoke(false)
+ }
+
+ private fun setVisibleAndAlpha(set: ConstraintSet, resId: Int, visible: Boolean) {
+ set.setVisibility(resId, if (visible) ConstraintSet.VISIBLE else ConstraintSet.GONE)
+ set.setAlpha(resId, if (visible) 1.0f else 0.0f)
+ }
+
+ private fun updateRecommendationsVisibility(
+ mediaViewController: MediaViewController,
+ cardView: TransitionLayout,
+ ) {
+ val fittedRecsNum = getNumberOfFittedRecommendations(cardView.context)
+ val expandedSet = mediaViewController.expandedLayout
+ val collapsedSet = mediaViewController.collapsedLayout
+ val mediaCoverContainers = getMediaCoverContainers(cardView)
+ // Hide media cover that cannot fit in the recommendation card.
+ mediaCoverContainers.forEachIndexed { index, container ->
+ setVisibleAndAlpha(expandedSet, container.id, index < fittedRecsNum)
+ setVisibleAndAlpha(collapsedSet, container.id, index < fittedRecsNum)
+ }
+ }
+
+ private fun getMediaCoverContainers(cardView: TransitionLayout): List<ViewGroup> {
+ return listOf<ViewGroup>(
+ cardView.requireViewById(R.id.media_cover1_container),
+ cardView.requireViewById(R.id.media_cover2_container),
+ cardView.requireViewById(R.id.media_cover3_container),
+ )
+ }
+
+ private fun getNumberOfFittedRecommendations(context: Context): Int {
+ val res = context.resources
+ val config = res.configuration
+ val defaultDpWidth = res.getInteger(R.integer.default_qs_media_rec_width_dp)
+ val recCoverWidth =
+ (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
+ res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
+
+ // On landscape, media controls should take half of the screen width.
+ val displayAvailableDpWidth =
+ if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ config.screenWidthDp / 2
+ } else {
+ config.screenWidthDp
+ }
+ val fittedNum =
+ if (displayAvailableDpWidth > defaultDpWidth) {
+ val recCoverDefaultWidth =
+ res.getDimensionPixelSize(R.dimen.qs_media_rec_default_width)
+ recCoverDefaultWidth / recCoverWidth
+ } else {
+ val displayAvailableWidth =
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ displayAvailableDpWidth.toFloat(),
+ res.displayMetrics
+ )
+ .toInt()
+ displayAvailableWidth / recCoverWidth
+ }
+ return min(fittedNum.toDouble(), NUM_REQUIRED_RECOMMENDATIONS.toDouble()).toInt()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 1a56a9b..bd3893b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -738,8 +738,11 @@
mPackageName, mMediaViewHolder.getSeamlessButton());
} else {
mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
- mMediaOutputDialogManager.createAndShow(mPackageName, true,
- mMediaViewHolder.getSeamlessButton());
+ // TODO: b/321969740 - Populate the userHandle parameter. The user
+ // handle is necessary to disambiguate the same package running on
+ // different users.
+ mMediaOutputDialogManager.createAndShow(
+ mPackageName, true, mMediaViewHolder.getSeamlessButton(), null);
}
} else {
mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
@@ -767,8 +770,11 @@
Log.w(TAG, "Device pending intent is not an activity.");
}
} else {
- mMediaOutputDialogManager.createAndShow(mPackageName, true,
- mMediaViewHolder.getSeamlessButton());
+ // TODO: b/321969740 - Populate the userHandle parameter. The user
+ // handle is necessary to disambiguate the same package running on
+ // different users.
+ mMediaOutputDialogManager.createAndShow(
+ mPackageName, true, mMediaViewHolder.getSeamlessButton(), null);
}
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index ad7990b..7fced5f8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -16,41 +16,73 @@
package com.android.systemui.media.controls.ui.controller
+import android.animation.Animator
+import android.animation.AnimatorInflater
+import android.animation.AnimatorSet
import android.content.Context
import android.content.res.Configuration
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.drawable.Drawable
+import android.provider.Settings
+import android.view.View
+import android.view.animation.Interpolator
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
+import com.android.app.animation.Interpolators
import com.android.app.tracing.traceSection
+import com.android.systemui.Flags
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.controls.ui.animation.ColorSchemeTransition
+import com.android.systemui.media.controls.ui.animation.MetadataAnimationHandler
+import com.android.systemui.media.controls.ui.binder.MediaControlViewBinder
+import com.android.systemui.media.controls.ui.binder.SeekBarObserver
import com.android.systemui.media.controls.ui.controller.MediaCarouselController.Companion.calculateAlpha
import com.android.systemui.media.controls.ui.view.GutsViewHolder
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
+import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.surfaceeffects.PaintDrawCallback
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.MeasurementOutput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionLayoutController
import com.android.systemui.util.animation.TransitionViewState
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.settings.GlobalSettings
import java.lang.Float.max
import java.lang.Float.min
+import java.util.Random
import javax.inject.Inject
/**
* A class responsible for controlling a single instance of a media player handling interactions
* with the view instance and keeping the media view states up to date.
*/
-class MediaViewController
+open class MediaViewController
@Inject
constructor(
private val context: Context,
private val configurationController: ConfigurationController,
private val mediaHostStatesManager: MediaHostStatesManager,
private val logger: MediaViewLogger,
+ private val seekBarViewModel: SeekBarViewModel,
+ @Main private val mainExecutor: DelayableExecutor,
private val mediaFlags: MediaFlags,
+ private val globalSettings: GlobalSettings,
) {
/**
@@ -69,6 +101,7 @@
/** A listener when the current dimensions of the player change */
lateinit var sizeChangedListener: () -> Unit
lateinit var configurationChangeListener: () -> Unit
+ lateinit var recsConfigurationChangeListener: (MediaViewController, TransitionLayout) -> Unit
private var firstRefresh: Boolean = true
@VisibleForTesting private var transitionLayout: TransitionLayout? = null
private val layoutController = TransitionLayoutController()
@@ -130,6 +163,72 @@
return transitionLayout?.translationY ?: 0.0f
}
+ /** Whether artwork is bound. */
+ var isArtworkBound: Boolean = false
+
+ /** previous background artwork */
+ var prevArtwork: Drawable? = null
+
+ /** Whether scrubbing time can show */
+ var canShowScrubbingTime: Boolean = false
+
+ /** Whether user is touching the seek bar to change the position */
+ var isScrubbing: Boolean = false
+
+ var isSeekBarEnabled: Boolean = false
+
+ /** Not visible value for previous button when scrubbing */
+ private var prevNotVisibleValue = ConstraintSet.GONE
+ private var isPrevButtonAvailable = false
+
+ /** Not visible value for next button when scrubbing */
+ private var nextNotVisibleValue = ConstraintSet.GONE
+ private var isNextButtonAvailable = false
+
+ private lateinit var mediaViewHolder: MediaViewHolder
+ private lateinit var seekBarObserver: SeekBarObserver
+ private lateinit var turbulenceNoiseController: TurbulenceNoiseController
+ private lateinit var loadingEffect: LoadingEffect
+ private lateinit var turbulenceNoiseAnimationConfig: TurbulenceNoiseAnimationConfig
+ private lateinit var noiseDrawCallback: PaintDrawCallback
+ private lateinit var stateChangedCallback: LoadingEffect.AnimationStateChangedCallback
+ internal lateinit var metadataAnimationHandler: MetadataAnimationHandler
+ internal lateinit var colorSchemeTransition: ColorSchemeTransition
+ internal lateinit var multiRippleController: MultiRippleController
+
+ private val scrubbingChangeListener =
+ object : SeekBarViewModel.ScrubbingChangeListener {
+ override fun onScrubbingChanged(scrubbing: Boolean) {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (isScrubbing == scrubbing) return
+ isScrubbing = scrubbing
+ updateDisplayForScrubbingChange()
+ }
+ }
+
+ private val enabledChangeListener =
+ object : SeekBarViewModel.EnabledChangeListener {
+ override fun onEnabledChanged(enabled: Boolean) {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (isSeekBarEnabled == enabled) return
+ isSeekBarEnabled = enabled
+ MediaControlViewBinder.updateSeekBarVisibility(expandedLayout, isSeekBarEnabled)
+ }
+ }
+
+ /**
+ * Sets the listening state of the player.
+ *
+ * Should be set to true when the QS panel is open. Otherwise, false. This is a signal to avoid
+ * unnecessary work when the QS panel is closed.
+ *
+ * @param listening True when player should be active. Otherwise, false.
+ */
+ fun setListening(listening: Boolean) {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ seekBarViewModel.listening = listening
+ }
+
/** A callback for config changes */
private val configurationListener =
object : ConfigurationController.ConfigurationListener {
@@ -160,7 +259,17 @@
)
)
}
- if (this@MediaViewController::configurationChangeListener.isInitialized) {
+ if (mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (
+ this@MediaViewController::recsConfigurationChangeListener.isInitialized
+ ) {
+ transitionLayout?.let {
+ recsConfigurationChangeListener.invoke(this@MediaViewController, it)
+ }
+ }
+ } else if (
+ this@MediaViewController::configurationChangeListener.isInitialized
+ ) {
configurationChangeListener.invoke()
refreshState()
}
@@ -221,6 +330,14 @@
* Notify this controller that the view has been removed and all listeners should be destroyed
*/
fun onDestroy() {
+ if (mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (this::seekBarObserver.isInitialized) {
+ seekBarViewModel.progress.removeObserver(seekBarObserver)
+ }
+ seekBarViewModel.removeScrubbingChangeListener(scrubbingChangeListener)
+ seekBarViewModel.removeEnabledChangeListener(enabledChangeListener)
+ seekBarViewModel.onDestroy()
+ }
mediaHostStatesManager.removeController(this)
configurationController.removeCallback(configurationListener)
}
@@ -535,6 +652,178 @@
)
}
+ fun attachPlayer(mediaViewHolder: MediaViewHolder) {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ this.mediaViewHolder = mediaViewHolder
+
+ // Setting up seek bar.
+ seekBarObserver = SeekBarObserver(mediaViewHolder)
+ seekBarViewModel.progress.observeForever(seekBarObserver)
+ seekBarViewModel.attachTouchHandlers(mediaViewHolder.seekBar)
+ seekBarViewModel.setScrubbingChangeListener(scrubbingChangeListener)
+ seekBarViewModel.setEnabledChangeListener(enabledChangeListener)
+
+ val mediaCard = mediaViewHolder.player
+ attach(mediaViewHolder.player, TYPE.PLAYER)
+
+ val turbulenceNoiseView = mediaViewHolder.turbulenceNoiseView
+ turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
+
+ multiRippleController = MultiRippleController(mediaViewHolder.multiRippleView)
+
+ // Metadata Animation
+ val titleText = mediaViewHolder.titleText
+ val artistText = mediaViewHolder.artistText
+ val explicitIndicator = mediaViewHolder.explicitIndicator
+ val enter =
+ loadAnimator(
+ mediaCard.context,
+ R.anim.media_metadata_enter,
+ Interpolators.EMPHASIZED_DECELERATE,
+ titleText,
+ artistText,
+ explicitIndicator
+ )
+ val exit =
+ loadAnimator(
+ mediaCard.context,
+ R.anim.media_metadata_exit,
+ Interpolators.EMPHASIZED_ACCELERATE,
+ titleText,
+ artistText,
+ explicitIndicator
+ )
+ metadataAnimationHandler = MetadataAnimationHandler(exit, enter)
+
+ colorSchemeTransition =
+ ColorSchemeTransition(
+ mediaCard.context,
+ mediaViewHolder,
+ multiRippleController,
+ turbulenceNoiseController
+ )
+
+ // For Turbulence noise.
+ val loadingEffectView = mediaViewHolder.loadingEffectView
+ turbulenceNoiseAnimationConfig =
+ createTurbulenceNoiseConfig(
+ loadingEffectView,
+ turbulenceNoiseView,
+ colorSchemeTransition
+ )
+ noiseDrawCallback =
+ object : PaintDrawCallback {
+ override fun onDraw(paint: Paint) {
+ loadingEffectView.draw(paint)
+ }
+ }
+ stateChangedCallback =
+ object : LoadingEffect.AnimationStateChangedCallback {
+ override fun onStateChanged(
+ oldState: LoadingEffect.AnimationState,
+ newState: LoadingEffect.AnimationState
+ ) {
+ if (newState === LoadingEffect.AnimationState.NOT_PLAYING) {
+ loadingEffectView.visibility = View.INVISIBLE
+ } else {
+ loadingEffectView.visibility = View.VISIBLE
+ }
+ }
+ }
+ }
+
+ fun updateAnimatorDurationScale() {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (this::seekBarObserver.isInitialized) {
+ seekBarObserver.animationEnabled =
+ globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) > 0f
+ }
+ }
+
+ /** update view with the needed UI changes when user touches seekbar. */
+ private fun updateDisplayForScrubbingChange() {
+ mainExecutor.execute {
+ val isTimeVisible = canShowScrubbingTime && isScrubbing
+ MediaControlViewBinder.setVisibleAndAlpha(
+ expandedLayout,
+ mediaViewHolder.scrubbingTotalTimeView.id,
+ isTimeVisible
+ )
+ MediaControlViewBinder.setVisibleAndAlpha(
+ expandedLayout,
+ mediaViewHolder.scrubbingElapsedTimeView.id,
+ isTimeVisible
+ )
+
+ MediaControlViewModel.SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.forEach { id ->
+ val isButtonVisible: Boolean
+ val notVisibleValue: Int
+ when (id) {
+ R.id.actionPrev -> {
+ isButtonVisible = isPrevButtonAvailable && !isTimeVisible
+ notVisibleValue = prevNotVisibleValue
+ }
+ R.id.actionNext -> {
+ isButtonVisible = isNextButtonAvailable && !isTimeVisible
+ notVisibleValue = nextNotVisibleValue
+ }
+ else -> {
+ isButtonVisible = !isTimeVisible
+ notVisibleValue = ConstraintSet.GONE
+ }
+ }
+ MediaControlViewBinder.setSemanticButtonVisibleAndAlpha(
+ mediaViewHolder.getAction(id),
+ expandedLayout,
+ collapsedLayout,
+ isButtonVisible,
+ notVisibleValue,
+ showInCollapsed = true
+ )
+ }
+
+ if (!metadataAnimationHandler.isRunning) {
+ refreshState()
+ }
+ }
+ }
+
+ fun bindSeekBar(onSeek: () -> Unit, onBindSeekBar: (SeekBarViewModel) -> Unit) {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ seekBarViewModel.logSeek = onSeek
+ onBindSeekBar.invoke(seekBarViewModel)
+ }
+
+ fun setUpTurbulenceNoise() {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (Flags.shaderlibLoadingEffectRefactor()) {
+ if (!this::loadingEffect.isInitialized) {
+ loadingEffect =
+ LoadingEffect(
+ TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
+ turbulenceNoiseAnimationConfig,
+ noiseDrawCallback,
+ stateChangedCallback
+ )
+ }
+ colorSchemeTransition.loadingEffect = loadingEffect
+ loadingEffect.play()
+ mainExecutor.executeDelayed(
+ loadingEffect::finish,
+ MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION
+ )
+ } else {
+ turbulenceNoiseController.play(
+ TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
+ turbulenceNoiseAnimationConfig
+ )
+ mainExecutor.executeDelayed(
+ turbulenceNoiseController::finish,
+ MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION
+ )
+ }
+ }
+
/**
* Obtain a measurement for a given location. This makes sure that the state is up to date and
* all widgets know their location. Calling this method may create a measurement if we don't
@@ -790,6 +1079,75 @@
applyImmediately = true
)
}
+
+ @VisibleForTesting
+ protected open fun loadAnimator(
+ context: Context,
+ animId: Int,
+ motionInterpolator: Interpolator?,
+ vararg targets: View?
+ ): AnimatorSet {
+ val animators = ArrayList<Animator>()
+ for (target in targets) {
+ val animator = AnimatorInflater.loadAnimator(context, animId) as AnimatorSet
+ animator.childAnimations[0].interpolator = motionInterpolator
+ animator.setTarget(target)
+ animators.add(animator)
+ }
+ val result = AnimatorSet()
+ result.playTogether(animators)
+ return result
+ }
+
+ private fun createTurbulenceNoiseConfig(
+ loadingEffectView: LoadingEffectView,
+ turbulenceNoiseView: TurbulenceNoiseView,
+ colorSchemeTransition: ColorSchemeTransition
+ ): TurbulenceNoiseAnimationConfig {
+ val targetView: View =
+ if (Flags.shaderlibLoadingEffectRefactor()) {
+ loadingEffectView
+ } else {
+ turbulenceNoiseView
+ }
+ val width = targetView.width
+ val height = targetView.height
+ val random = Random()
+ return TurbulenceNoiseAnimationConfig(
+ gridCount = 2.14f,
+ TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
+ random.nextFloat(),
+ random.nextFloat(),
+ random.nextFloat(),
+ noiseMoveSpeedX = 0.42f,
+ noiseMoveSpeedY = 0f,
+ TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
+ // Color will be correctly updated in ColorSchemeTransition.
+ colorSchemeTransition.accentPrimary.currentColor,
+ screenColor = Color.BLACK,
+ width.toFloat(),
+ height.toFloat(),
+ TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS,
+ easeInDuration = 1350f,
+ easeOutDuration = 1350f,
+ targetView.context.resources.displayMetrics.density,
+ lumaMatteBlendFactor = 0.26f,
+ lumaMatteOverallBrightness = 0.09f,
+ shouldInverseNoiseLuminosity = false
+ )
+ }
+
+ fun setUpPrevButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ isPrevButtonAvailable = isAvailable
+ prevNotVisibleValue = notVisibleValue
+ }
+
+ fun setUpNextButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ isNextButtonAvailable = isAvailable
+ nextNotVisibleValue = notVisibleValue
+ }
}
/** An internal key for the cache of mediaViewStates. This is a subset of the full host state. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt
index e508e1b..6c7c31c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt
@@ -22,9 +22,9 @@
/** Models UI state for media guts menu */
data class GutsViewModel(
val gutsText: CharSequence,
- @ColorInt val textColor: Int,
- @ColorInt val buttonBackgroundColor: Int,
- @ColorInt val buttonTextColor: Int,
+ @ColorInt val textPrimaryColor: Int,
+ @ColorInt val accentPrimaryColor: Int,
+ @ColorInt val surfaceColor: Int,
val isDismissEnabled: Boolean = true,
val onDismissClicked: () -> Unit,
val cancelTextBackground: Drawable?,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt
index 1e67a77..82099e6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt
@@ -24,7 +24,8 @@
val icon: Drawable?,
val contentDescription: CharSequence?,
val background: Drawable?,
- val isVisible: Boolean = true,
+ /** whether action is visible if user is touching seekbar to change position. */
+ val isVisibleWhenScrubbing: Boolean = true,
val notVisibleValue: Int = ConstraintSet.GONE,
val showInCollapsed: Boolean,
val rebindId: Int? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 117b2af..d74506d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.content.pm.PackageManager
+import android.media.session.MediaController
import android.media.session.MediaSession.Token
import android.text.TextUtils
import android.util.Log
@@ -40,6 +41,7 @@
import com.android.systemui.monet.Style
import com.android.systemui.res.R
import com.android.systemui.util.kotlin.sample
+import java.util.concurrent.Executor
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -51,6 +53,7 @@
class MediaControlViewModel(
@Application private val applicationContext: Context,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Background private val backgroundExecutor: Executor,
private val interactor: MediaControlInteractor,
private val logger: MediaUiEventLogger,
) {
@@ -124,13 +127,15 @@
}
},
backgroundCover = model.artwork,
- appIcon = getAppIcon(model.appIcon, model.isResume, model.packageName),
+ appIcon = model.appIcon,
+ launcherIcon = getIconFromApp(model.packageName),
useGrayColorFilter = model.appIcon == null || model.isResume,
artistName = model.artistName ?: "",
titleName = model.songName ?: "",
isExplicitVisible = model.showExplicit,
+ shouldAddGradient = wallpaperColors != null,
colorScheme = scheme,
- isTimeVisible = canShowScrubbingTimeViews(model.semanticActionButtons),
+ canShowTime = canShowScrubbingTimeViews(model.semanticActionButtons),
playTurbulenceNoise = playTurbulenceNoise,
useSemanticActions = model.semanticActionButtons != null,
actionButtons = toActionViewModels(model),
@@ -146,6 +151,21 @@
onLongClicked = {
logger.logLongPressOpen(model.uid, model.packageName, model.instanceId)
},
+ onSeek = {
+ logger.logSeek(model.uid, model.packageName, model.instanceId)
+ // TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_CLICK_EVENT)
+ },
+ onBindSeekbar = { seekBarViewModel ->
+ if (model.isResume && model.resumeProgress != null) {
+ seekBarViewModel.updateStaticProgress(model.resumeProgress)
+ } else {
+ backgroundExecutor.execute {
+ seekBarViewModel.updateController(
+ model.token?.let { MediaController(applicationContext, it) }
+ )
+ }
+ }
+ }
)
}
@@ -235,9 +255,9 @@
} else {
applicationContext.getString(R.string.controls_media_active_session)
},
- textColor = textPrimaryFromScheme(scheme),
- buttonBackgroundColor = accentPrimaryFromScheme(scheme),
- buttonTextColor = surfaceFromScheme(scheme),
+ textPrimaryColor = textPrimaryFromScheme(scheme),
+ accentPrimaryColor = accentPrimaryFromScheme(scheme),
+ surfaceColor = surfaceFromScheme(scheme),
isDismissEnabled = model.isDismissible,
onDismissClicked = {
onDismissMediaData(model.token, model.uid, model.packageName, model.instanceId)
@@ -278,16 +298,16 @@
model: MediaControlModel,
mediaAction: MediaAction,
buttonId: Int,
- isScrubbingTimeEnabled: Boolean
+ canShowScrubbingTimeViews: Boolean
): MediaActionViewModel {
val showInCollapsed = SEMANTIC_ACTIONS_COMPACT.contains(buttonId)
val hideWhenScrubbing = SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.contains(buttonId)
- val shouldHideDueToScrubbing = isScrubbingTimeEnabled && hideWhenScrubbing
+ val shouldHideWhenScrubbing = canShowScrubbingTimeViews && hideWhenScrubbing
return MediaActionViewModel(
icon = mediaAction.icon,
contentDescription = mediaAction.contentDescription,
background = mediaAction.background,
- isVisible = !shouldHideDueToScrubbing,
+ isVisibleWhenScrubbing = !shouldHideWhenScrubbing,
notVisibleValue =
if (
(buttonId == R.id.actionPrev && model.semanticActionButtons!!.reservePrev) ||
@@ -342,19 +362,6 @@
action.run()
}
- private fun getAppIcon(
- icon: android.graphics.drawable.Icon?,
- isResume: Boolean,
- packageName: String
- ): Icon {
- if (icon != null && !isResume) {
- icon.loadDrawable(applicationContext)?.let { drawable ->
- return Icon.Loaded(drawable, null)
- }
- }
- return getIconFromApp(packageName)
- }
-
private fun getIconFromApp(packageName: String): Icon {
return try {
Icon.Loaded(applicationContext.packageManager.getApplicationIcon(packageName), null)
@@ -381,17 +388,17 @@
private const val DISABLED_ALPHA = 0.38f
/** Buttons to show in small player when using semantic actions */
- private val SEMANTIC_ACTIONS_COMPACT =
+ val SEMANTIC_ACTIONS_COMPACT =
listOf(R.id.actionPlayPause, R.id.actionPrev, R.id.actionNext)
/**
* Buttons that should get hidden when we are scrubbing (they will be replaced with the
* views showing scrubbing time)
*/
- private val SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING = listOf(R.id.actionPrev, R.id.actionNext)
+ val SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING = listOf(R.id.actionPrev, R.id.actionNext)
/** Buttons to show in player when using semantic actions. */
- private val SEMANTIC_ACTIONS_ALL =
+ val SEMANTIC_ACTIONS_ALL =
listOf(
R.id.actionPlayPause,
R.id.actionPrev,
@@ -399,5 +406,9 @@
R.id.action0,
R.id.action1
)
+
+ const val TURBULENCE_NOISE_PLAY_MS_DURATION = 7500L
+ const val MEDIA_PLAYER_SCRIM_START_ALPHA = 0.25f
+ const val MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
index 9029a65..d1014e8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
@@ -24,13 +24,15 @@
data class MediaPlayerViewModel(
val contentDescription: (Boolean) -> CharSequence,
val backgroundCover: android.graphics.drawable.Icon?,
- val appIcon: Icon,
+ val appIcon: android.graphics.drawable.Icon?,
+ val launcherIcon: Icon,
val useGrayColorFilter: Boolean,
val artistName: CharSequence,
val titleName: CharSequence,
val isExplicitVisible: Boolean,
+ val shouldAddGradient: Boolean,
val colorScheme: ColorScheme,
- val isTimeVisible: Boolean,
+ val canShowTime: Boolean,
val playTurbulenceNoise: Boolean,
val useSemanticActions: Boolean,
val actionButtons: List<MediaActionViewModel?>,
@@ -38,4 +40,6 @@
val gutsMenu: GutsViewModel,
val onClicked: (Expandable) -> Unit,
val onLongClicked: () -> Unit,
+ val onSeek: () -> Unit,
+ val onBindSeekbar: (SeekBarViewModel) -> Unit,
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
index b0375f0..a2307d4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
@@ -213,9 +213,9 @@
return GutsViewModel(
gutsText =
applicationContext.getString(R.string.controls_media_close_session, model.appName),
- textColor = textPrimaryFromScheme(scheme),
- buttonBackgroundColor = accentPrimaryFromScheme(scheme),
- buttonTextColor = surfaceFromScheme(scheme),
+ textPrimaryColor = textPrimaryFromScheme(scheme),
+ accentPrimaryColor = accentPrimaryFromScheme(scheme),
+ surfaceColor = surfaceFromScheme(scheme),
onDismissClicked = {
onMediaRecommendationsDismissed(
model.key,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
index 452cb7e..ff8e903b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
@@ -31,8 +31,9 @@
) {
/** Creates a [LocalMediaManager] for the given package. */
fun create(packageName: String?): LocalMediaManager {
- return InfoMediaManager.createInstance(context, packageName, localBluetoothManager).run {
- LocalMediaManager(context, localBluetoothManager, this, packageName)
- }
+ // TODO: b/321969740 - Populate the userHandle parameter in InfoMediaManager. The user
+ // handle is necessary to disambiguate the same package running on different users.
+ return InfoMediaManager.createInstance(context, packageName, null, localBluetoothManager)
+ .run { LocalMediaManager(context, localBluetoothManager, this, packageName) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index d4bd6da..4e77d13 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -21,16 +21,11 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import javax.inject.Inject
@SysUISingleton
-class MediaFlags
-@Inject
-constructor(
- private val featureFlags: FeatureFlagsClassic,
- private val sceneContainerFlags: SceneContainerFlags
-) {
+class MediaFlags @Inject constructor(private val featureFlags: FeatureFlagsClassic) {
/**
* Check whether media control actions should be based on PlaybackState instead of notification
*/
@@ -57,7 +52,7 @@
/** Check whether to use scene framework */
fun isSceneContainerEnabled() =
- sceneContainerFlags.isEnabled() && MediaInSceneContainerFlag.isEnabled
+ SceneContainerFlag.isEnabled && MediaInSceneContainerFlag.isEnabled
/** Check whether to use media refactor code */
fun isMediaControlsRefactorEnabled() = MediaControlsRefactorFlag.isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
index 54d175c..06267e2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
@@ -38,7 +38,9 @@
// Dismiss the previous dialog, if any.
mediaOutputBroadcastDialog?.dismiss()
- val controller = mediaOutputControllerFactory.create(packageName)
+ // TODO: b/321969740 - Populate the userHandle parameter. The user handle is necessary to
+ // disambiguate the same package running on different users.
+ val controller = mediaOutputControllerFactory.create(packageName, /* userHandle= */ null)
val dialog =
MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
mediaOutputBroadcastDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 4db89d1..d6ca320 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -124,6 +124,7 @@
private static final String ALLOWLIST_REASON = "mediaoutput:remote_transfer";
private final String mPackageName;
+ private final UserHandle mUserHandle;
private final Context mContext;
private final MediaSessionManager mMediaSessionManager;
private final LocalBluetoothManager mLocalBluetoothManager;
@@ -177,6 +178,7 @@
public MediaOutputController(
Context context,
@Assisted String packageName,
+ @Assisted @Nullable UserHandle userHandle,
MediaSessionManager mediaSessionManager,
@Nullable LocalBluetoothManager lbm,
ActivityStarter starter,
@@ -190,6 +192,7 @@
UserTracker userTracker) {
mContext = context;
mPackageName = packageName;
+ mUserHandle = userHandle;
mMediaSessionManager = mediaSessionManager;
mLocalBluetoothManager = lbm;
mActivityStarter = starter;
@@ -199,7 +202,8 @@
mKeyGuardManager = keyGuardManager;
mFeatureFlags = featureFlags;
mUserTracker = userTracker;
- InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, lbm);
+ InfoMediaManager imm =
+ InfoMediaManager.createInstance(mContext, packageName, userHandle, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
mDialogTransitionAnimator = dialogTransitionAnimator;
@@ -231,7 +235,7 @@
@AssistedFactory
public interface Factory {
/** Construct a MediaOutputController */
- MediaOutputController create(String packageName);
+ MediaOutputController create(String packageName, UserHandle userHandle);
}
protected void start(@NonNull Callback cb) {
@@ -946,11 +950,22 @@
}
void launchMediaOutputBroadcastDialog(View mediaOutputDialog, BroadcastSender broadcastSender) {
- MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
- mMediaSessionManager, mLocalBluetoothManager, mActivityStarter,
- mNotifCollection, mDialogTransitionAnimator, mNearbyMediaDevicesManager,
- mAudioManager, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags,
- mUserTracker);
+ MediaOutputController controller =
+ new MediaOutputController(
+ mContext,
+ mPackageName,
+ mUserHandle,
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mActivityStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyGuardManager,
+ mFeatureFlags,
+ mUserTracker);
MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
broadcastSender, controller);
dialog.show();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
index e7816a4..04d1492 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.dialog
import android.content.Context
+import android.os.UserHandle
import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
@@ -41,7 +42,15 @@
}
/** Creates a [MediaOutputDialog] for the given package. */
- open fun createAndShow(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+ // TODO: b/321969740 - Make the userHandle non-optional, and place the parameter next to the
+ // package name. The user handle is necessary to disambiguate the same package running on
+ // different users.
+ open fun createAndShow(
+ packageName: String,
+ aboveStatusBar: Boolean,
+ view: View? = null,
+ userHandle: UserHandle? = null
+ ) {
createAndShowWithController(
packageName,
aboveStatusBar,
@@ -55,20 +64,26 @@
)
)
},
+ userHandle = userHandle,
)
}
/** Creates a [MediaOutputDialog] for the given package. */
+ // TODO: b/321969740 - Make the userHandle non-optional, and place the parameter next to the
+ // package name. The user handle is necessary to disambiguate the same package running on
+ // different users.
open fun createAndShowWithController(
packageName: String,
aboveStatusBar: Boolean,
controller: DialogTransitionAnimator.Controller?,
+ userHandle: UserHandle? = null,
) {
createAndShow(
packageName,
aboveStatusBar,
dialogTransitionAnimatorController = controller,
- includePlaybackAndAppMetadata = true
+ includePlaybackAndAppMetadata = true,
+ userHandle = userHandle,
)
}
@@ -79,20 +94,25 @@
packageName = null,
aboveStatusBar = false,
dialogTransitionAnimatorController = null,
- includePlaybackAndAppMetadata = false
+ includePlaybackAndAppMetadata = false,
+ userHandle = null,
)
}
+ // TODO: b/321969740 - Make the userHandle non-optional, and place the parameter next to the
+ // package name. The user handle is necessary to disambiguate the same package running on
+ // different users.
private fun createAndShow(
packageName: String?,
aboveStatusBar: Boolean,
dialogTransitionAnimatorController: DialogTransitionAnimator.Controller?,
- includePlaybackAndAppMetadata: Boolean = true
+ includePlaybackAndAppMetadata: Boolean = true,
+ userHandle: UserHandle? = null,
) {
// Dismiss the previous dialog, if any.
mediaOutputDialog?.dismiss()
- val controller = mediaOutputControllerFactory.create(packageName)
+ val controller = mediaOutputControllerFactory.create(packageName, userHandle)
val mediaOutputDialog =
MediaOutputDialog(
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
index da85234..9cc2888 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
@@ -55,8 +55,8 @@
@MainThread
public void showMediaOutputSwitcher(String packageName, UserHandle userHandle) {
if (!TextUtils.isEmpty(packageName)) {
- // TODO: b/279555229 - Pass the userHandle into the output dialog manager.
- mMediaOutputDialogManager.createAndShow(packageName, false, null);
+ mMediaOutputDialogManager.createAndShow(
+ packageName, /* aboveStatusBar= */ false, /* view= */ null, userHandle);
} else {
Log.e(TAG, "Unable to launch media output dialog. Package name is empty.");
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt
index 9514c4a..b7942f7 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt
@@ -27,8 +27,8 @@
import android.util.Log
import android.view.SurfaceControl
import android.view.animation.DecelerateInterpolator
-import android.window.IRemoteTransition
import android.window.IRemoteTransitionFinishedCallback
+import android.window.RemoteTransitionStub
import android.window.TransitionInfo
import android.window.WindowContainerToken
import com.android.app.viewcapture.ViewCapture
@@ -41,7 +41,7 @@
private val viewPosition: IntArray,
private val screenBounds: Rect,
private val handleResult: () -> Unit,
-) : IRemoteTransition.Stub() {
+) : RemoteTransitionStub() {
override fun startAnimation(
transition: IBinder?,
info: TransitionInfo?,
@@ -114,14 +114,6 @@
}
}
- override fun mergeAnimation(
- transition: IBinder?,
- info: TransitionInfo?,
- t: SurfaceControl.Transaction?,
- mergeTarget: IBinder?,
- finishedCallback: IRemoteTransitionFinishedCallback?
- ) {}
-
@Throws(RemoteException::class)
override fun onTransitionConsumed(transition: IBinder, aborted: Boolean) {
Log.w(TAG, "unexpected consumption of app selector transition: aborted=$aborted")
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 63989ef..a6b6d61 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -35,6 +35,7 @@
import android.content.ContentResolver;
import android.content.Context;
+import android.content.res.Configuration;
import android.database.ContentObserver;
import android.inputmethodservice.InputMethodService;
import android.net.Uri;
@@ -74,6 +75,7 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import dagger.Lazy;
@@ -101,7 +103,7 @@
AccessibilityButtonModeObserver.ModeChangedListener,
AccessibilityButtonTargetsObserver.TargetsChangedListener,
OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
- Dumpable, CommandQueue.Callbacks {
+ Dumpable, CommandQueue.Callbacks, ConfigurationController.ConfigurationListener {
private static final String TAG = NavBarHelper.class.getSimpleName();
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -189,6 +191,7 @@
UserTracker userTracker,
DisplayTracker displayTracker,
NotificationShadeWindowController notificationShadeWindowController,
+ ConfigurationController configurationController,
DumpManager dumpManager,
CommandQueue commandQueue,
@Main Executor mainExecutor) {
@@ -215,6 +218,7 @@
mNavBarMode = navigationModeController.addListener(this);
mCommandQueue.addCallback(this);
+ configurationController.addCallback(this);
overviewProxyService.addCallback(this);
dumpManager.registerDumpable(this);
}
@@ -359,6 +363,11 @@
updateA11yState();
}
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ mEdgeBackGestureHandler.onConfigurationChanged(newConfig);
+ }
+
/**
* Updates the current accessibility button state. The accessibility button state is only
* used for {@link Secure#ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR} and
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index ade56c4..43c73c4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -411,18 +411,16 @@
@Override
public void setOverrideHomeButtonLongPress(long duration, float slopMultiplier) {
+ Log.d(TAG, "setOverrideHomeButtonLongPress receives: " + duration + "; "
+ + slopMultiplier);
mOverrideHomeButtonLongPressDurationMs = Optional.of(duration)
.filter(value -> value > 0);
mOverrideHomeButtonLongPressSlopMultiplier = Optional.of(slopMultiplier)
.filter(value -> value > 0);
- if (mOverrideHomeButtonLongPressDurationMs.isPresent()) {
- Log.d(TAG, "Receive duration override: "
- + mOverrideHomeButtonLongPressDurationMs.get());
- }
- if (mOverrideHomeButtonLongPressSlopMultiplier.isPresent()) {
- Log.d(TAG, "Receive slop multiplier override: "
- + mOverrideHomeButtonLongPressSlopMultiplier.get());
- }
+ mOverrideHomeButtonLongPressDurationMs.ifPresent(aLong
+ -> Log.d(TAG, "Use duration override: " + aLong));
+ mOverrideHomeButtonLongPressSlopMultiplier.ifPresent(aFloat
+ -> Log.d(TAG, "Use slop multiplier override: " + aFloat));
if (mView != null) {
reconfigureHomeLongClick();
}
@@ -902,11 +900,6 @@
refreshLayout(ld);
}
repositionNavigationBar(rotation);
- // NOTE(b/260220098): In some cases, the recreated nav bar will already have the right
- // configuration, which means that NavBarView will not receive a configuration change to
- // propagate to EdgeBackGestureHandler (which is injected into this and NBV). As such, we
- // should also force-update the gesture handler to ensure it updates to the right bounds
- mEdgeBackGestureHandler.onConfigurationChanged(newConfig);
if (canShowSecondaryHandle()) {
if (rotation != mCurrentRotation) {
mCurrentRotation = rotation;
@@ -1400,9 +1393,10 @@
break;
case MotionEvent.ACTION_MOVE:
if (!mHandler.hasCallbacks(mOnVariableDurationHomeLongClick)) {
- Log.w(TAG, "No callback. Don't handle touch slop.");
+ Log.v(TAG, "ACTION_MOVE no callback. Don't handle touch slop.");
break;
}
+ Log.v(TAG, "ACTION_MOVE handle touch slop");
float customSlopMultiplier = mOverrideHomeButtonLongPressSlopMultiplier.orElse(1f);
float touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
float calculatedTouchSlop =
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 9f7d1b3..12f27038 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -162,14 +162,11 @@
mIsLargeScreen = isLargeScreen(mContext);
boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources());
boolean largeScreenChanged = mIsLargeScreen != isOldConfigLargeScreen;
- // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
+ // TODO(b/332635834): Disable this logging once b/332635834 is fixed.
Log.i(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
+ " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized()
+ " willApplyConfigToNavbars=" + willApplyConfig
+ " navBarCount=" + mNavigationBars.size());
- if (mTaskbarDelegate.isInitialized()) {
- mTaskbarDelegate.onConfigurationChanged(newConfig);
- }
// If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded
if (largeScreenChanged && updateNavbarForTaskbar()) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 1927f49..3c69ed9 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -1031,7 +1031,6 @@
updateIcons(mTmpLastConfiguration);
updateRecentsIcon();
updateCurrentRotation();
- mEdgeBackGestureHandler.onConfigurationChanged(mConfiguration);
if (uiCarModeChanged || mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi
|| mTmpLastConfiguration.getLayoutDirection() != mConfiguration.getLayoutDirection()) {
// If car mode or density changes, we need to reset the icons.
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 5dd1bd8..f67973b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -39,7 +39,6 @@
import android.app.StatusBarManager;
import android.app.StatusBarManager.WindowVisibleState;
import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.inputmethodservice.InputMethodService;
@@ -72,7 +71,6 @@
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoHideController;
-import com.android.systemui.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -250,8 +248,6 @@
mLightBarController.setNavigationBar(mLightBarTransitionsController);
mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener);
mEdgeBackGestureHandler.setBackAnimation(mBackAnimation);
- mEdgeBackGestureHandler.onConfigurationChanged(
- mContext.getResources().getConfiguration());
mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
mInitialized = true;
} finally {
@@ -495,10 +491,6 @@
return mBehavior != BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
}
- public void onConfigurationChanged(Configuration configuration) {
- mEdgeBackGestureHandler.onConfigurationChanged(configuration);
- }
-
@Override
public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
mOverviewProxyService.onNavigationBarLumaSamplingEnabled(displayId, enable);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 9d0ea5e..b50ee57 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -71,8 +71,6 @@
import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.FalsingManager;
@@ -219,10 +217,8 @@
private final Region mExcludeRegion = new Region();
private final Region mDesktopModeExcludeRegion = new Region();
private final Region mUnrestrictedExcludeRegion = new Region();
- private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider;
private final Provider<BackGestureTfClassifierProvider>
mBackGestureTfClassifierProviderProvider;
- private final FeatureFlags mFeatureFlags;
private final Provider<LightBarController> mLightBarControllerProvider;
// The left side edge width where touch down is allowed
@@ -264,8 +260,6 @@
private boolean mIsEnabled;
private boolean mIsNavBarShownTransiently;
private boolean mIsBackGestureAllowed;
- private boolean mIsNewBackAffordanceEnabled;
- private boolean mIsTrackpadGestureFeaturesEnabled;
private boolean mIsTrackpadThreeFingerSwipe;
private boolean mIsButtonForcedVisible;
@@ -413,9 +407,7 @@
Optional<Pip> pipOptional,
Optional<DesktopMode> desktopModeOptional,
FalsingManager falsingManager,
- Provider<NavigationBarEdgePanel> navigationBarEdgePanelProvider,
Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
- FeatureFlags featureFlags,
Provider<LightBarController> lightBarControllerProvider) {
mContext = context;
mDisplayId = context.getDisplayId();
@@ -435,13 +427,9 @@
mPipOptional = pipOptional;
mDesktopModeOptional = desktopModeOptional;
mFalsingManager = falsingManager;
- mNavBarEdgePanelProvider = navigationBarEdgePanelProvider;
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
- mFeatureFlags = featureFlags;
mLightBarControllerProvider = lightBarControllerProvider;
mLastReportedConfig.setTo(mContext.getResources().getConfiguration());
- mIsTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(
- Flags.TRACKPAD_GESTURE_FEATURES);
ComponentName recentsComponentName = ComponentName.unflattenFromString(
context.getString(com.android.internal.R.string.config_recentsComponentName));
if (recentsComponentName != null) {
@@ -559,12 +547,10 @@
mIsAttached = true;
mOverviewProxyService.addCallback(mQuickSwitchListener);
mSysUiState.addCallback(mSysUiStateCallback);
- if (mIsTrackpadGestureFeaturesEnabled) {
- mInputManager.registerInputDeviceListener(mInputDeviceListener, mMainHandler);
- int [] inputDevices = mInputManager.getInputDeviceIds();
- for (int inputDeviceId : inputDevices) {
- mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
- }
+ mInputManager.registerInputDeviceListener(mInputDeviceListener, mMainHandler);
+ int [] inputDevices = mInputManager.getInputDeviceIds();
+ for (int inputDeviceId : inputDevices) {
+ mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
}
updateIsEnabled();
mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
@@ -616,9 +602,8 @@
try {
Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled");
- mIsGestureHandlingEnabled =
- mInGestureNavMode || (mIsTrackpadGestureFeaturesEnabled && mUsingThreeButtonNav
- && mIsTrackpadConnected);
+ mIsGestureHandlingEnabled = mInGestureNavMode || (mUsingThreeButtonNav
+ && mIsTrackpadConnected);
boolean isEnabled = mIsAttached && mIsGestureHandlingEnabled;
if (isEnabled == mIsEnabled) {
return;
@@ -678,7 +663,6 @@
Choreographer.getInstance(), this::onInputEvent);
// Add a nav bar panel window
- mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE);
resetEdgeBackPlugin();
mPluginManager.addPluginListener(
this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
@@ -701,12 +685,7 @@
}
private void resetEdgeBackPlugin() {
- if (mIsNewBackAffordanceEnabled) {
- setEdgeBackPlugin(
- mBackPanelControllerFactory.create(mContext));
- } else {
- setEdgeBackPlugin(mNavBarEdgePanelProvider.get());
- }
+ setEdgeBackPlugin(mBackPanelControllerFactory.create(mContext));
}
private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) {
@@ -1001,8 +980,7 @@
Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev);
}
- mIsTrackpadThreeFingerSwipe = isTrackpadThreeFingerSwipe(
- mIsTrackpadGestureFeaturesEnabled, ev);
+ mIsTrackpadThreeFingerSwipe = isTrackpadThreeFingerSwipe(ev);
// ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
// ACTION_DOWN, in that case we should just reuse the old instance.
@@ -1027,7 +1005,7 @@
&& !mGestureBlockingActivityRunning.get()
&& !QuickStepContract.isBackGestureDisabled(mSysUiFlags,
mIsTrackpadThreeFingerSwipe)
- && !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev);
+ && !isTrackpadScroll(ev);
if (mIsTrackpadThreeFingerSwipe) {
// Trackpad back gestures don't have zones, so we don't need to check if the down
// event is within insets.
@@ -1187,7 +1165,7 @@
updateDisabledForQuickstep(newConfig);
}
- // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
+ // TODO(b/332635834): Disable this logging once b/332635834 is fixed.
Log.i(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
+ " lastReportedConfig=" + mLastReportedConfig);
mLastReportedConfig.updateFrom(newConfig);
@@ -1321,10 +1299,8 @@
private final Optional<Pip> mPipOptional;
private final Optional<DesktopMode> mDesktopModeOptional;
private final FalsingManager mFalsingManager;
- private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider;
private final Provider<BackGestureTfClassifierProvider>
mBackGestureTfClassifierProviderProvider;
- private final FeatureFlags mFeatureFlags;
private final Provider<LightBarController> mLightBarControllerProvider;
@Inject
@@ -1344,10 +1320,8 @@
Optional<Pip> pipOptional,
Optional<DesktopMode> desktopModeOptional,
FalsingManager falsingManager,
- Provider<NavigationBarEdgePanel> navBarEdgePanelProvider,
Provider<BackGestureTfClassifierProvider>
backGestureTfClassifierProviderProvider,
- FeatureFlags featureFlags,
Provider<LightBarController> lightBarControllerProvider) {
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
@@ -1365,9 +1339,7 @@
mPipOptional = pipOptional;
mDesktopModeOptional = desktopModeOptional;
mFalsingManager = falsingManager;
- mNavBarEdgePanelProvider = navBarEdgePanelProvider;
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
- mFeatureFlags = featureFlags;
mLightBarControllerProvider = lightBarControllerProvider;
}
@@ -1391,9 +1363,7 @@
mPipOptional,
mDesktopModeOptional,
mFalsingManager,
- mNavBarEdgePanelProvider,
mBackGestureTfClassifierProviderProvider,
- mFeatureFlags,
mLightBarControllerProvider);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
deleted file mode 100644
index 380846e..0000000
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ /dev/null
@@ -1,944 +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.systemui.navigationbar.gestural;
-
-import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE;
-import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.os.VibrationEffect;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.ContextThemeWrapper;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
-
-import androidx.core.graphics.ColorUtils;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
-import com.android.app.animation.Interpolators;
-import com.android.internal.util.LatencyTracker;
-import com.android.settingslib.Utils;
-import com.android.systemui.res.R;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.plugins.NavigationEdgeBackPlugin;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
-import com.android.systemui.statusbar.VibratorHelper;
-
-import java.io.PrintWriter;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
-public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPlugin {
-
- private static final String TAG = "NavigationBarEdgePanel";
-
- private static final boolean ENABLE_FAILSAFE = true;
-
- private static final long COLOR_ANIMATION_DURATION_MS = 120;
- private static final long DISAPPEAR_FADE_ANIMATION_DURATION_MS = 80;
- private static final long DISAPPEAR_ARROW_ANIMATION_DURATION_MS = 100;
- private static final long FAILSAFE_DELAY_MS = 200;
-
- /**
- * The time required since the first vibration effect to automatically trigger a click
- */
- private static final int GESTURE_DURATION_FOR_CLICK_MS = 400;
-
- /**
- * The size of the protection of the arrow in px. Only used if this is not background protected
- */
- private static final int PROTECTION_WIDTH_PX = 2;
-
- /**
- * The basic translation in dp where the arrow resides
- */
- private static final int BASE_TRANSLATION_DP = 32;
-
- /**
- * The length of the arrow leg measured from the center to the end
- */
- private static final int ARROW_LENGTH_DP = 18;
-
- /**
- * The angle measured from the xAxis, where the leg is when the arrow rests
- */
- private static final int ARROW_ANGLE_WHEN_EXTENDED_DEGREES = 56;
-
- /**
- * The angle that is added per 1000 px speed to the angle of the leg
- */
- private static final int ARROW_ANGLE_ADDED_PER_1000_SPEED = 4;
-
- /**
- * The maximum angle offset allowed due to speed
- */
- private static final int ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES = 4;
-
- /**
- * The thickness of the arrow. Adjusted to match the home handle (approximately)
- */
- private static final float ARROW_THICKNESS_DP = 2.5f;
-
- /**
- * The amount of rubber banding we do for the vertical translation
- */
- private static final int RUBBER_BAND_AMOUNT = 15;
-
- /**
- * The interpolator used to rubberband
- */
- private static final Interpolator RUBBER_BAND_INTERPOLATOR
- = new PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f);
-
- /**
- * The amount of rubber banding we do for the translation before base translation
- */
- private static final int RUBBER_BAND_AMOUNT_APPEAR = 4;
-
- /**
- * The interpolator used to rubberband the appearing of the arrow.
- */
- private static final Interpolator RUBBER_BAND_INTERPOLATOR_APPEAR
- = new PathInterpolator(1.0f / RUBBER_BAND_AMOUNT_APPEAR, 1.0f, 1.0f, 1.0f);
-
- private final WindowManager mWindowManager;
- private final VibratorHelper mVibratorHelper;
-
- /**
- * The paint the arrow is drawn with
- */
- private final Paint mPaint = new Paint();
- /**
- * The paint the arrow protection is drawn with
- */
- private final Paint mProtectionPaint;
-
- private final float mDensity;
- private final float mBaseTranslation;
- private final float mArrowLength;
- private final float mArrowThickness;
-
- /**
- * The minimum delta needed in movement for the arrow to change direction / stop triggering back
- */
- private final float mMinDeltaForSwitch;
- // The closest to y = 0 that the arrow will be displayed.
- private int mMinArrowPosition;
- // The amount the arrow is shifted to avoid the finger.
- private int mFingerOffset;
-
- private final float mSwipeTriggerThreshold;
- private final float mSwipeProgressThreshold;
- private final Path mArrowPath = new Path();
- private final Point mDisplaySize = new Point();
-
- private final SpringAnimation mAngleAnimation;
- private final SpringAnimation mTranslationAnimation;
- private final SpringAnimation mVerticalTranslationAnimation;
- private final SpringForce mAngleAppearForce;
- private final SpringForce mAngleDisappearForce;
- private final ValueAnimator mArrowColorAnimator;
- private final ValueAnimator mArrowDisappearAnimation;
- private final SpringForce mRegularTranslationSpring;
- private final SpringForce mTriggerBackSpring;
- private final LatencyTracker mLatencyTracker;
-
- private VelocityTracker mVelocityTracker;
- private boolean mIsDark = false;
- private boolean mShowProtection = false;
- private int mProtectionColorLight;
- private int mArrowPaddingEnd;
- private int mArrowColorLight;
- private int mProtectionColorDark;
- private int mArrowColorDark;
- private int mProtectionColor;
- private int mArrowColor;
- private RegionSamplingHelper mRegionSamplingHelper;
- private final Rect mSamplingRect = new Rect();
- private WindowManager.LayoutParams mLayoutParams;
- private int mLeftInset;
- private int mRightInset;
-
- /**
- * True if the panel is currently on the left of the screen
- */
- private boolean mIsLeftPanel;
-
- private float mStartX;
- private float mStartY;
- private float mCurrentAngle;
- /**
- * The current translation of the arrow
- */
- private float mCurrentTranslation;
- /**
- * Where the arrow will be in the resting position.
- */
- private float mDesiredTranslation;
-
- private boolean mDragSlopPassed;
- private boolean mArrowsPointLeft;
- private float mMaxTranslation;
- private boolean mTriggerBack;
- private float mPreviousTouchTranslation;
- private float mTotalTouchDelta;
- private float mVerticalTranslation;
- private float mDesiredVerticalTranslation;
- private float mDesiredAngle;
- private float mAngleOffset;
- private int mArrowStartColor;
- private int mCurrentArrowColor;
- private float mDisappearAmount;
- private long mVibrationTime;
- private int mScreenSize;
- private boolean mTrackingBackArrowLatency = false;
-
- private final Handler mHandler = new Handler();
- private final Runnable mFailsafeRunnable = this::onFailsafe;
-
- private DynamicAnimation.OnAnimationEndListener mSetGoneEndListener
- = new DynamicAnimation.OnAnimationEndListener() {
- @Override
- public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
- float velocity) {
- animation.removeEndListener(this);
- if (!canceled) {
- setVisibility(GONE);
- }
- }
- };
- private static final FloatPropertyCompat<NavigationBarEdgePanel> CURRENT_ANGLE =
- new FloatPropertyCompat<NavigationBarEdgePanel>("currentAngle") {
- @Override
- public void setValue(NavigationBarEdgePanel object, float value) {
- object.setCurrentAngle(value);
- }
-
- @Override
- public float getValue(NavigationBarEdgePanel object) {
- return object.getCurrentAngle();
- }
- };
-
- private static final FloatPropertyCompat<NavigationBarEdgePanel> CURRENT_TRANSLATION =
- new FloatPropertyCompat<NavigationBarEdgePanel>("currentTranslation") {
-
- @Override
- public void setValue(NavigationBarEdgePanel object, float value) {
- object.setCurrentTranslation(value);
- }
-
- @Override
- public float getValue(NavigationBarEdgePanel object) {
- return object.getCurrentTranslation();
- }
- };
- private static final FloatPropertyCompat<NavigationBarEdgePanel> CURRENT_VERTICAL_TRANSLATION =
- new FloatPropertyCompat<NavigationBarEdgePanel>("verticalTranslation") {
-
- @Override
- public void setValue(NavigationBarEdgePanel object, float value) {
- object.setVerticalTranslation(value);
- }
-
- @Override
- public float getValue(NavigationBarEdgePanel object) {
- return object.getVerticalTranslation();
- }
- };
- private BackCallback mBackCallback;
-
- @Inject
- public NavigationBarEdgePanel(
- Context context,
- LatencyTracker latencyTracker,
- VibratorHelper vibratorHelper,
- @Background Executor backgroundExecutor,
- DisplayTracker displayTracker) {
- super(context);
-
- mWindowManager = context.getSystemService(WindowManager.class);
- mVibratorHelper = vibratorHelper;
-
- mDensity = context.getResources().getDisplayMetrics().density;
-
- mBaseTranslation = dp(BASE_TRANSLATION_DP);
- mArrowLength = dp(ARROW_LENGTH_DP);
- mArrowThickness = dp(ARROW_THICKNESS_DP);
- mMinDeltaForSwitch = dp(32);
-
- mPaint.setStrokeWidth(mArrowThickness);
- mPaint.setStrokeCap(Paint.Cap.ROUND);
- mPaint.setAntiAlias(true);
- mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setStrokeJoin(Paint.Join.ROUND);
-
- mArrowColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
- mArrowColorAnimator.setDuration(COLOR_ANIMATION_DURATION_MS);
- mArrowColorAnimator.addUpdateListener(animation -> {
- int newColor = ColorUtils.blendARGB(
- mArrowStartColor, mArrowColor, animation.getAnimatedFraction());
- setCurrentArrowColor(newColor);
- });
-
- mArrowDisappearAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
- mArrowDisappearAnimation.setDuration(DISAPPEAR_ARROW_ANIMATION_DURATION_MS);
- mArrowDisappearAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mArrowDisappearAnimation.addUpdateListener(animation -> {
- mDisappearAmount = (float) animation.getAnimatedValue();
- invalidate();
- });
-
- mAngleAnimation =
- new SpringAnimation(this, CURRENT_ANGLE);
- mAngleAppearForce = new SpringForce()
- .setStiffness(500)
- .setDampingRatio(0.5f);
- mAngleDisappearForce = new SpringForce()
- .setStiffness(SpringForce.STIFFNESS_MEDIUM)
- .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
- .setFinalPosition(90);
- mAngleAnimation.setSpring(mAngleAppearForce).setMaxValue(90);
-
- mTranslationAnimation =
- new SpringAnimation(this, CURRENT_TRANSLATION);
- mRegularTranslationSpring = new SpringForce()
- .setStiffness(SpringForce.STIFFNESS_MEDIUM)
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
- mTriggerBackSpring = new SpringForce()
- .setStiffness(450)
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
- mTranslationAnimation.setSpring(mRegularTranslationSpring);
- mVerticalTranslationAnimation =
- new SpringAnimation(this, CURRENT_VERTICAL_TRANSLATION);
- mVerticalTranslationAnimation.setSpring(
- new SpringForce()
- .setStiffness(SpringForce.STIFFNESS_MEDIUM)
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
-
- mProtectionPaint = new Paint(mPaint);
- mProtectionPaint.setStrokeWidth(mArrowThickness + PROTECTION_WIDTH_PX);
- loadDimens();
-
- loadColors(context);
- updateArrowDirection();
-
- mSwipeTriggerThreshold = context.getResources()
- .getDimension(R.dimen.navigation_edge_action_drag_threshold);
- mSwipeProgressThreshold = context.getResources()
- .getDimension(R.dimen.navigation_edge_action_progress_threshold);
-
- setVisibility(GONE);
-
- boolean isPrimaryDisplay = mContext.getDisplayId() == displayTracker.getDefaultDisplayId();
- mRegionSamplingHelper = new RegionSamplingHelper(this,
- new RegionSamplingHelper.SamplingCallback() {
- @Override
- public void onRegionDarknessChanged(boolean isRegionDark) {
- setIsDark(!isRegionDark, true /* animate */);
- }
-
- @Override
- public Rect getSampledRegion(View sampledView) {
- return mSamplingRect;
- }
-
- @Override
- public boolean isSamplingEnabled() {
- return isPrimaryDisplay;
- }
- }, backgroundExecutor);
- mRegionSamplingHelper.setWindowVisible(true);
- mShowProtection = !isPrimaryDisplay;
- mLatencyTracker = latencyTracker;
- }
-
- @Override
- public void onDestroy() {
- cancelFailsafe();
- mWindowManager.removeView(this);
- mRegionSamplingHelper.stop();
- mRegionSamplingHelper = null;
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
-
- private void setIsDark(boolean isDark, boolean animate) {
- mIsDark = isDark;
- updateIsDark(animate);
- }
-
- @Override
- public void setIsLeftPanel(boolean isLeftPanel) {
- mIsLeftPanel = isLeftPanel;
- mLayoutParams.gravity = mIsLeftPanel
- ? (Gravity.LEFT | Gravity.TOP)
- : (Gravity.RIGHT | Gravity.TOP);
- }
-
- @Override
- public void setInsets(int leftInset, int rightInset) {
- mLeftInset = leftInset;
- mRightInset = rightInset;
- }
-
- @Override
- public void setDisplaySize(Point displaySize) {
- mDisplaySize.set(displaySize.x, displaySize.y);
- mScreenSize = Math.min(mDisplaySize.x, mDisplaySize.y);
- }
-
- @Override
- public void setBackCallback(BackCallback callback) {
- mBackCallback = callback;
- }
-
- @Override
- public void setLayoutParams(WindowManager.LayoutParams layoutParams) {
- mLayoutParams = layoutParams;
- mWindowManager.addView(this, mLayoutParams);
- }
-
- /**
- * Adjusts the sampling rect to conform to the actual visible bounding box of the arrow.
- */
- private void adjustSamplingRectToBoundingBox() {
- float translation = mDesiredTranslation;
- if (!mTriggerBack) {
- // Let's take the resting position and bounds as the sampling rect, since we are not
- // visible right now
- translation = mBaseTranslation;
- if (mIsLeftPanel && mArrowsPointLeft
- || (!mIsLeftPanel && !mArrowsPointLeft)) {
- // If we're on the left we should move less, because the arrow is facing the other
- // direction
- translation -= getStaticArrowWidth();
- }
- }
- float left = translation - mArrowThickness / 2.0f;
- left = mIsLeftPanel ? left : mSamplingRect.width() - left;
-
- // Let's calculate the position of the end based on the angle
- float width = getStaticArrowWidth();
- float height = polarToCartY(ARROW_ANGLE_WHEN_EXTENDED_DEGREES) * mArrowLength * 2.0f;
- if (!mArrowsPointLeft) {
- left -= width;
- }
-
- float top = (getHeight() * 0.5f) + mDesiredVerticalTranslation - height / 2.0f;
- mSamplingRect.offset((int) left, (int) top);
- mSamplingRect.set(mSamplingRect.left, mSamplingRect.top,
- (int) (mSamplingRect.left + width),
- (int) (mSamplingRect.top + height));
- mRegionSamplingHelper.updateSamplingRect();
- }
-
- @Override
- public void onMotionEvent(MotionEvent event) {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(event);
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mDragSlopPassed = false;
- resetOnDown();
- mStartX = event.getX();
- mStartY = event.getY();
- setVisibility(VISIBLE);
- updatePosition(event.getY());
- mRegionSamplingHelper.start(mSamplingRect);
- mWindowManager.updateViewLayout(this, mLayoutParams);
- mLatencyTracker.onActionStart(LatencyTracker.ACTION_SHOW_BACK_ARROW);
- mTrackingBackArrowLatency = true;
- break;
- case MotionEvent.ACTION_MOVE:
- handleMoveEvent(event);
- break;
- case MotionEvent.ACTION_UP:
- if (DEBUG_MISSING_GESTURE) {
- Log.d(DEBUG_MISSING_GESTURE_TAG,
- "NavigationBarEdgePanel ACTION_UP, mTriggerBack=" + mTriggerBack);
- }
- if (mTriggerBack) {
- triggerBack();
- } else {
- cancelBack();
- }
- mRegionSamplingHelper.stop();
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- break;
- case MotionEvent.ACTION_CANCEL:
- if (DEBUG_MISSING_GESTURE) {
- Log.d(DEBUG_MISSING_GESTURE_TAG, "NavigationBarEdgePanel ACTION_CANCEL");
- }
- cancelBack();
- mRegionSamplingHelper.stop();
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- break;
- }
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- updateArrowDirection();
- loadDimens();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- float pointerPosition = mCurrentTranslation - mArrowThickness / 2.0f;
- canvas.save();
- canvas.translate(
- mIsLeftPanel ? pointerPosition : getWidth() - pointerPosition,
- (getHeight() * 0.5f) + mVerticalTranslation);
-
- // Let's calculate the position of the end based on the angle
- float x = (polarToCartX(mCurrentAngle) * mArrowLength);
- float y = (polarToCartY(mCurrentAngle) * mArrowLength);
- Path arrowPath = calculatePath(x,y);
- if (mShowProtection) {
- canvas.drawPath(arrowPath, mProtectionPaint);
- }
-
- canvas.drawPath(arrowPath, mPaint);
- canvas.restore();
- if (mTrackingBackArrowLatency) {
- mLatencyTracker.onActionEnd(LatencyTracker.ACTION_SHOW_BACK_ARROW);
- mTrackingBackArrowLatency = false;
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
-
- mMaxTranslation = getWidth() - mArrowPaddingEnd;
- }
-
- private void loadDimens() {
- Resources res = getResources();
- mArrowPaddingEnd = res.getDimensionPixelSize(R.dimen.navigation_edge_panel_padding);
- mMinArrowPosition = res.getDimensionPixelSize(R.dimen.navigation_edge_arrow_min_y);
- mFingerOffset = res.getDimensionPixelSize(R.dimen.navigation_edge_finger_offset);
- }
-
- private void updateArrowDirection() {
- // Both panels arrow point the same way
- mArrowsPointLeft = getLayoutDirection() == LAYOUT_DIRECTION_LTR;
- invalidate();
- }
-
- private void loadColors(Context context) {
- final int dualToneDarkTheme = Utils.getThemeAttr(context, R.attr.darkIconTheme);
- final int dualToneLightTheme = Utils.getThemeAttr(context, R.attr.lightIconTheme);
- Context lightContext = new ContextThemeWrapper(context, dualToneLightTheme);
- Context darkContext = new ContextThemeWrapper(context, dualToneDarkTheme);
- mArrowColorLight = Utils.getColorAttrDefaultColor(lightContext, R.attr.singleToneColor);
- mArrowColorDark = Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor);
- mProtectionColorDark = mArrowColorLight;
- mProtectionColorLight = mArrowColorDark;
- updateIsDark(false /* animate */);
- }
-
- private void updateIsDark(boolean animate) {
- // TODO: Maybe animate protection as well
- mProtectionColor = mIsDark ? mProtectionColorDark : mProtectionColorLight;
- mProtectionPaint.setColor(mProtectionColor);
- mArrowColor = mIsDark ? mArrowColorDark : mArrowColorLight;
- mArrowColorAnimator.cancel();
- if (!animate) {
- setCurrentArrowColor(mArrowColor);
- } else {
- mArrowStartColor = mCurrentArrowColor;
- mArrowColorAnimator.start();
- }
- }
-
- private void setCurrentArrowColor(int color) {
- mCurrentArrowColor = color;
- mPaint.setColor(color);
- invalidate();
- }
-
- private float getStaticArrowWidth() {
- return polarToCartX(ARROW_ANGLE_WHEN_EXTENDED_DEGREES) * mArrowLength;
- }
-
- private float polarToCartX(float angleInDegrees) {
- return (float) Math.cos(Math.toRadians(angleInDegrees));
- }
-
- private float polarToCartY(float angleInDegrees) {
- return (float) Math.sin(Math.toRadians(angleInDegrees));
- }
-
- private Path calculatePath(float x, float y) {
- if (!mArrowsPointLeft) {
- x = -x;
- }
- float extent = MathUtils.lerp(1.0f, 0.75f, mDisappearAmount);
- x = x * extent;
- y = y * extent;
- mArrowPath.reset();
- mArrowPath.moveTo(x, y);
- mArrowPath.lineTo(0, 0);
- mArrowPath.lineTo(x, -y);
- return mArrowPath;
- }
-
- private float getCurrentAngle() {
- return mCurrentAngle;
- }
-
- private float getCurrentTranslation() {
- return mCurrentTranslation;
- }
-
- private void triggerBack() {
- mBackCallback.triggerBack();
-
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.computeCurrentVelocity(1000);
- // Only do the extra translation if we're not already flinging
- boolean isSlow = Math.abs(mVelocityTracker.getXVelocity()) < 500;
- if (isSlow
- || SystemClock.uptimeMillis() - mVibrationTime >= GESTURE_DURATION_FOR_CLICK_MS) {
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK);
- }
-
- // Let's also snap the angle a bit
- if (mAngleOffset > -4) {
- mAngleOffset = Math.max(-8, mAngleOffset - 8);
- updateAngle(true /* animated */);
- }
-
- // Finally, after the translation, animate back and disappear the arrow
- Runnable translationEnd = () -> {
- // let's snap it back
- mAngleOffset = Math.max(0, mAngleOffset + 8);
- updateAngle(true /* animated */);
-
- mTranslationAnimation.setSpring(mTriggerBackSpring);
- // Translate the arrow back a bit to make for a nice transition
- setDesiredTranslation(mDesiredTranslation - dp(32), true /* animated */);
- animate().alpha(0f).setDuration(DISAPPEAR_FADE_ANIMATION_DURATION_MS)
- .withEndAction(() -> setVisibility(GONE));
- mArrowDisappearAnimation.start();
- // Schedule failsafe in case alpha end callback is not called
- scheduleFailsafe();
- };
- if (mTranslationAnimation.isRunning()) {
- mTranslationAnimation.addEndListener(new DynamicAnimation.OnAnimationEndListener() {
- @Override
- public void onAnimationEnd(DynamicAnimation animation, boolean canceled,
- float value,
- float velocity) {
- animation.removeEndListener(this);
- if (!canceled) {
- translationEnd.run();
- }
- }
- });
- // Schedule failsafe in case mTranslationAnimation end callback is not called
- scheduleFailsafe();
- } else {
- translationEnd.run();
- }
- }
-
- private void cancelBack() {
- mBackCallback.cancelBack();
-
- if (mTranslationAnimation.isRunning()) {
- mTranslationAnimation.addEndListener(mSetGoneEndListener);
- // Schedule failsafe in case mTranslationAnimation end callback is not called
- scheduleFailsafe();
- } else {
- setVisibility(GONE);
- }
- }
-
- private void resetOnDown() {
- animate().cancel();
- mAngleAnimation.cancel();
- mTranslationAnimation.cancel();
- mVerticalTranslationAnimation.cancel();
- mArrowDisappearAnimation.cancel();
- mAngleOffset = 0;
- mTranslationAnimation.setSpring(mRegularTranslationSpring);
- // Reset the arrow to the side
- if (DEBUG_MISSING_GESTURE) {
- Log.d(DEBUG_MISSING_GESTURE_TAG, "reset mTriggerBack=false");
- }
- setTriggerBack(false /* triggerBack */, false /* animated */);
- setDesiredTranslation(0, false /* animated */);
- setCurrentTranslation(0);
- updateAngle(false /* animate */);
- mPreviousTouchTranslation = 0;
- mTotalTouchDelta = 0;
- mVibrationTime = 0;
- setDesiredVerticalTransition(0, false /* animated */);
- cancelFailsafe();
- }
-
- private void handleMoveEvent(MotionEvent event) {
- float x = event.getX();
- float y = event.getY();
- float touchTranslation = MathUtils.abs(x - mStartX);
- float yOffset = y - mStartY;
- float delta = touchTranslation - mPreviousTouchTranslation;
- if (Math.abs(delta) > 0) {
- if (Math.signum(delta) == Math.signum(mTotalTouchDelta)) {
- mTotalTouchDelta += delta;
- } else {
- mTotalTouchDelta = delta;
- }
- }
- mPreviousTouchTranslation = touchTranslation;
-
- // Apply a haptic on drag slop passed
- if (!mDragSlopPassed && touchTranslation > mSwipeTriggerThreshold) {
- mDragSlopPassed = true;
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
- mVibrationTime = SystemClock.uptimeMillis();
-
- // Let's show the arrow and animate it in!
- mDisappearAmount = 0.0f;
- setAlpha(1f);
- // And animate it go to back by default!
- if (DEBUG_MISSING_GESTURE) {
- Log.d(DEBUG_MISSING_GESTURE_TAG, "set mTriggerBack=true");
- }
- setTriggerBack(true /* triggerBack */, true /* animated */);
- }
-
- // Let's make sure we only go to the baseextend and apply rubberbanding afterwards
- if (touchTranslation > mBaseTranslation) {
- float diff = touchTranslation - mBaseTranslation;
- float progress = MathUtils.saturate(diff / (mScreenSize - mBaseTranslation));
- progress = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
- * (mMaxTranslation - mBaseTranslation);
- touchTranslation = mBaseTranslation + progress;
- } else {
- float diff = mBaseTranslation - touchTranslation;
- float progress = MathUtils.saturate(diff / mBaseTranslation);
- progress = RUBBER_BAND_INTERPOLATOR_APPEAR.getInterpolation(progress)
- * (mBaseTranslation / RUBBER_BAND_AMOUNT_APPEAR);
- touchTranslation = mBaseTranslation - progress;
- }
- // By default we just assume the current direction is kept
- boolean triggerBack = mTriggerBack;
-
- // First lets see if we had continuous motion in one direction for a while
- if (Math.abs(mTotalTouchDelta) > mMinDeltaForSwitch) {
- triggerBack = mTotalTouchDelta > 0;
- }
-
- // Then, let's see if our velocity tells us to change direction
- mVelocityTracker.computeCurrentVelocity(1000);
- float xVelocity = mVelocityTracker.getXVelocity();
- float yVelocity = mVelocityTracker.getYVelocity();
- float velocity = MathUtils.mag(xVelocity, yVelocity);
- mAngleOffset = Math.min(velocity / 1000 * ARROW_ANGLE_ADDED_PER_1000_SPEED,
- ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES) * Math.signum(xVelocity);
- if (mIsLeftPanel && mArrowsPointLeft || !mIsLeftPanel && !mArrowsPointLeft) {
- mAngleOffset *= -1;
- }
-
- // Last if the direction in Y is bigger than X * 2 we also abort
- if (Math.abs(yOffset) > Math.abs(x - mStartX) * 2) {
- triggerBack = false;
- }
- if (DEBUG_MISSING_GESTURE && mTriggerBack != triggerBack) {
- Log.d(DEBUG_MISSING_GESTURE_TAG, "set mTriggerBack=" + triggerBack
- + ", mTotalTouchDelta=" + mTotalTouchDelta
- + ", mMinDeltaForSwitch=" + mMinDeltaForSwitch
- + ", yOffset=" + yOffset
- + ", x=" + x
- + ", mStartX=" + mStartX);
- }
- setTriggerBack(triggerBack, true /* animated */);
-
- if (!mTriggerBack) {
- touchTranslation = 0;
- } else if (mIsLeftPanel && mArrowsPointLeft
- || (!mIsLeftPanel && !mArrowsPointLeft)) {
- // If we're on the left we should move less, because the arrow is facing the other
- // direction
- touchTranslation -= getStaticArrowWidth();
- }
- setDesiredTranslation(touchTranslation, true /* animated */);
- updateAngle(true /* animated */);
-
- float maxYOffset = getHeight() / 2.0f - mArrowLength;
- float progress = MathUtils.constrain(
- Math.abs(yOffset) / (maxYOffset * RUBBER_BAND_AMOUNT),
- 0, 1);
- float verticalTranslation = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
- * maxYOffset * Math.signum(yOffset);
- setDesiredVerticalTransition(verticalTranslation, true /* animated */);
- updateSamplingRect();
- }
-
- private void updatePosition(float touchY) {
- float position = touchY - mFingerOffset;
- position = Math.max(position, mMinArrowPosition);
- position -= mLayoutParams.height / 2.0f;
- mLayoutParams.y = MathUtils.constrain((int) position, 0, mDisplaySize.y);
- updateSamplingRect();
- }
-
- private void updateSamplingRect() {
- int top = mLayoutParams.y;
- int left = mIsLeftPanel ? mLeftInset : mDisplaySize.x - mRightInset - mLayoutParams.width;
- int right = left + mLayoutParams.width;
- int bottom = top + mLayoutParams.height;
- mSamplingRect.set(left, top, right, bottom);
- adjustSamplingRectToBoundingBox();
- }
-
- private void setDesiredVerticalTransition(float verticalTranslation, boolean animated) {
- if (mDesiredVerticalTranslation != verticalTranslation) {
- mDesiredVerticalTranslation = verticalTranslation;
- if (!animated) {
- setVerticalTranslation(verticalTranslation);
- } else {
- mVerticalTranslationAnimation.animateToFinalPosition(verticalTranslation);
- }
- invalidate();
- }
- }
-
- private void setVerticalTranslation(float verticalTranslation) {
- mVerticalTranslation = verticalTranslation;
- invalidate();
- }
-
- private float getVerticalTranslation() {
- return mVerticalTranslation;
- }
-
- private void setDesiredTranslation(float desiredTranslation, boolean animated) {
- if (mDesiredTranslation != desiredTranslation) {
- mDesiredTranslation = desiredTranslation;
- if (!animated) {
- setCurrentTranslation(desiredTranslation);
- } else {
- mTranslationAnimation.animateToFinalPosition(desiredTranslation);
- }
- }
- }
-
- private void setCurrentTranslation(float currentTranslation) {
- mCurrentTranslation = currentTranslation;
- invalidate();
- }
-
- private void setTriggerBack(boolean triggerBack, boolean animated) {
- if (mTriggerBack != triggerBack) {
- mTriggerBack = triggerBack;
- mAngleAnimation.cancel();
- updateAngle(animated);
- // Whenever the trigger back state changes the existing translation animation should be
- // cancelled
- mTranslationAnimation.cancel();
- mBackCallback.setTriggerBack(mTriggerBack);
- }
- }
-
- private void updateAngle(boolean animated) {
- float newAngle = mTriggerBack ? ARROW_ANGLE_WHEN_EXTENDED_DEGREES + mAngleOffset : 90;
- if (newAngle != mDesiredAngle) {
- if (!animated) {
- setCurrentAngle(newAngle);
- } else {
- mAngleAnimation.setSpring(mTriggerBack ? mAngleAppearForce : mAngleDisappearForce);
- mAngleAnimation.animateToFinalPosition(newAngle);
- }
- mDesiredAngle = newAngle;
- }
- }
-
- private void setCurrentAngle(float currentAngle) {
- mCurrentAngle = currentAngle;
- invalidate();
- }
-
- private void scheduleFailsafe() {
- if (!ENABLE_FAILSAFE) {
- return;
- }
- cancelFailsafe();
- mHandler.postDelayed(mFailsafeRunnable, FAILSAFE_DELAY_MS);
- }
-
- private void cancelFailsafe() {
- mHandler.removeCallbacks(mFailsafeRunnable);
- }
-
- private void onFailsafe() {
- setVisibility(GONE);
- }
-
- private float dp(float dp) {
- return mDensity * dp;
- }
-
- @Override
- public void dump(PrintWriter pw) {
- pw.println("NavigationBarEdgePanel:");
- pw.println(" mIsLeftPanel=" + mIsLeftPanel);
- pw.println(" mTriggerBack=" + mTriggerBack);
- pw.println(" mDragSlopPassed=" + mDragSlopPassed);
- pw.println(" mCurrentAngle=" + mCurrentAngle);
- pw.println(" mDesiredAngle=" + mDesiredAngle);
- pw.println(" mCurrentTranslation=" + mCurrentTranslation);
- pw.println(" mDesiredTranslation=" + mDesiredTranslation);
- pw.println(" mTranslationAnimation running=" + mTranslationAnimation.isRunning());
- mRegionSamplingHelper.dump(pw);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
index 10a88c8..b46f2d2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
@@ -24,16 +24,12 @@
public final class Utilities {
- public static boolean isTrackpadScroll(boolean isTrackpadGestureFeaturesEnabled,
- MotionEvent event) {
- return isTrackpadGestureFeaturesEnabled
- && event.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE;
+ public static boolean isTrackpadScroll(MotionEvent event) {
+ return event.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE;
}
- public static boolean isTrackpadThreeFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
- MotionEvent event) {
- return isTrackpadGestureFeaturesEnabled
- && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
+ public static boolean isTrackpadThreeFingerSwipe(MotionEvent event) {
+ return event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
&& event.getAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 9698548d..54a59f30 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -38,7 +38,7 @@
import javax.inject.Inject
/** Class responsible to "glue" all note task dependencies. */
-internal class NoteTaskInitializer
+class NoteTaskInitializer
@Inject
constructor(
private val controller: NoteTaskController,
@@ -138,11 +138,12 @@
* Returns a [NoteTaskEntryPoint] if an action should be taken, and null otherwise.
*/
private fun KeyEvent.toNoteTaskEntryPointOrNull(): NoteTaskEntryPoint? {
- val entryPoint = when {
- keyCode == KEYCODE_STYLUS_BUTTON_TAIL && isTailButtonNotesGesture() -> TAIL_BUTTON
- keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT
- else -> null
- }
+ val entryPoint =
+ when {
+ keyCode == KEYCODE_STYLUS_BUTTON_TAIL && isTailButtonNotesGesture() -> TAIL_BUTTON
+ keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT
+ else -> null
+ }
debugLog { "toNoteTaskEntryPointOrNull: entryPoint=$entryPoint" }
return entryPoint
}
@@ -164,7 +165,9 @@
// For now, trigger action immediately on UP of a single press, without waiting for
// the multi-press timeout to expire.
- debugLog { "isTailButtonNotesGesture: isMultiPress=$isMultiPress, isLongPress=$isLongPress" }
+ debugLog {
+ "isTailButtonNotesGesture: isMultiPress=$isMultiPress, isLongPress=$isLongPress"
+ }
return !isMultiPress && !isLongPress
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index ffbc560..7ccdf0a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -24,7 +24,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.dagger.QSScope;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.ViewController;
@@ -66,15 +66,14 @@
QSPanelController qsPanelController,
QuickStatusBarHeaderController quickStatusBarHeaderController,
ConfigurationController configurationController,
- FalsingManager falsingManager,
- SceneContainerFlags sceneContainerFlags) {
+ FalsingManager falsingManager) {
super(view);
mQsPanelController = qsPanelController;
mQuickStatusBarHeaderController = quickStatusBarHeaderController;
mConfigurationController = configurationController;
mFalsingManager = falsingManager;
mQSPanelContainer = mView.getQSPanelContainer();
- mSceneContainerEnabled = sceneContainerFlags.isEnabled();
+ mSceneContainerEnabled = SceneContainerFlag.isEnabled();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index a0607e9..1f4838e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -60,7 +60,7 @@
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.brightness.MirrorController;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
@@ -174,8 +174,6 @@
@Nullable
private View mFooterActionsView;
- private final SceneContainerFlags mSceneContainerFlags;
-
@Inject
public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue,
@@ -188,8 +186,7 @@
FooterActionsViewModel.Factory footerActionsViewModelFactory,
FooterActionsViewBinder footerActionsViewBinder,
LargeScreenShadeInterpolator largeScreenShadeInterpolator,
- FeatureFlags featureFlags,
- SceneContainerFlags sceneContainerFlags) {
+ FeatureFlags featureFlags) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
mQsMediaHost = qsMediaHost;
mQqsMediaHost = qqsMediaHost;
@@ -205,8 +202,7 @@
mFooterActionsViewModelFactory = footerActionsViewModelFactory;
mFooterActionsViewBinder = footerActionsViewBinder;
mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
- mSceneContainerFlags = sceneContainerFlags;
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
mStatusBarState = StatusBarState.SHADE;
}
}
@@ -224,7 +220,7 @@
mQSPanelController.init();
mQuickQSPanelController.init();
- if (!mSceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
mQSFooterActionsViewModel = mFooterActionsViewModelFactory
.create(mListeningAndVisibilityLifecycleOwner);
bindFooterActionsView(mRootView);
@@ -249,7 +245,7 @@
mScrollListener.onQsPanelScrollChanged(scrollY);
}
});
- mQSPanelScrollView.setScrollingEnabled(!mSceneContainerFlags.isEnabled());
+ mQSPanelScrollView.setScrollingEnabled(!SceneContainerFlag.isEnabled());
mHeader = mRootView.findViewById(R.id.header);
mFooter = qsComponent.getQSFooter();
@@ -509,7 +505,7 @@
@VisibleForTesting
boolean isKeyguardState() {
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
return false;
} else {
// We want the freshest state here since otherwise we'll have some weirdness if earlier
@@ -573,7 +569,7 @@
}
private void setKeyguardShowing(boolean keyguardShowing) {
- if (!mSceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
mLastQSExpansion = -1;
@@ -651,7 +647,7 @@
@Override
public int getHeightDiff() {
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
return mQSPanelController.getViewBottom() - mHeader.getBottom()
+ mHeader.getPaddingBottom();
} else {
@@ -720,7 +716,7 @@
mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
- if (!mSceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
float qsScrollViewTranslation =
onKeyguard && !mShowCollapsedOnKeyguard ? panelTranslationY : 0;
mQSPanelScrollView.setTranslationY(qsScrollViewTranslation);
@@ -824,7 +820,7 @@
mQsBounds.set(-sideMargin, 0, mQSPanelScrollView.getWidth() + sideMargin,
mQSPanelScrollView.getHeight());
}
- if (!mSceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
mQSPanelScrollView.setClipBounds(mQsBounds);
mQSPanelScrollView.getLocationOnScreen(mLocationTemp);
@@ -907,7 +903,7 @@
// The customize state changed, so our height changed.
mContainer.updateExpansion();
boolean customizing = isCustomizing();
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
mQSPanelController.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
} else {
mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
@@ -984,7 +980,7 @@
@Override
public void onStateChanged(int newState) {
- if (mSceneContainerFlags.isEnabled() || newState == mStatusBarState) {
+ if (SceneContainerFlag.isEnabled() || newState == mStatusBarState) {
return;
}
mStatusBarState = newState;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index b8c3c1a..e24caf1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -37,7 +37,7 @@
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -94,7 +94,6 @@
FalsingManager falsingManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
SplitShadeStateController splitShadeStateController,
- SceneContainerFlags sceneContainerFlags,
Provider<QSLongPressEffect> longPRessEffectProvider) {
super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost,
metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController,
@@ -113,7 +112,7 @@
mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mLastDensity = view.getResources().getConfiguration().densityDpi;
- mSceneContainerEnabled = sceneContainerFlags.isEnabled();
+ mSceneContainerEnabled = SceneContainerFlag.isEnabled();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index 1d92d78..bb40d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -17,7 +17,7 @@
package com.android.systemui.qs;
import com.android.systemui.qs.dagger.QSScope;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
@@ -34,12 +34,11 @@
@Inject
QuickStatusBarHeaderController(QuickStatusBarHeader view,
- QuickQSPanelController quickQSPanelController,
- SceneContainerFlags sceneContainerFlags
+ QuickQSPanelController quickQSPanelController
) {
super(view);
mQuickQSPanelController = quickQSPanelController;
- mSceneContainerEnabled = sceneContainerFlags.isEnabled();
+ mSceneContainerEnabled = SceneContainerFlag.isEnabled();
}
@Override
protected void onViewAttached() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
index a01d658..10c8e53 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,124 +16,21 @@
package com.android.systemui.qs;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.hardware.display.ColorDisplayManager;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.provider.Settings;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.CallbackController;
-import com.android.systemui.util.settings.SecureSettings;
-import java.util.ArrayList;
-
-import javax.inject.Inject;
-
-/**
- * @hide
- */
-@SysUISingleton
-public class ReduceBrightColorsController implements
+public interface ReduceBrightColorsController extends
CallbackController<ReduceBrightColorsController.Listener> {
- private final ColorDisplayManager mManager;
- private final UserTracker mUserTracker;
- private UserTracker.Callback mCurrentUserTrackerCallback;
- private final Handler mHandler;
- private final ContentObserver mContentObserver;
- private final SecureSettings mSecureSettings;
- private final ArrayList<ReduceBrightColorsController.Listener> mListeners = new ArrayList<>();
-
- @Inject
- public ReduceBrightColorsController(UserTracker userTracker,
- @Background Handler handler,
- ColorDisplayManager colorDisplayManager,
- SecureSettings secureSettings) {
- mManager = colorDisplayManager;
- mUserTracker = userTracker;
- mHandler = handler;
- mSecureSettings = secureSettings;
- mContentObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- super.onChange(selfChange, uri);
- final String setting = uri == null ? null : uri.getLastPathSegment();
- synchronized (mListeners) {
- if (setting != null && mListeners.size() != 0) {
- if (setting.equals(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED)) {
- dispatchOnActivated(mManager.isReduceBrightColorsActivated());
- }
- }
- }
- }
- };
-
- mCurrentUserTrackerCallback = new UserTracker.Callback() {
- @Override
- public void onUserChanged(int newUser, Context userContext) {
- synchronized (mListeners) {
- if (mListeners.size() > 0) {
- mSecureSettings.unregisterContentObserver(mContentObserver);
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
- false, mContentObserver, newUser);
- }
- }
- }
- };
- mUserTracker.addCallback(mCurrentUserTrackerCallback, new HandlerExecutor(handler));
- }
-
- @Override
- public void addCallback(@NonNull Listener listener) {
- synchronized (mListeners) {
- if (!mListeners.contains(listener)) {
- mListeners.add(listener);
- if (mListeners.size() == 1) {
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
- false, mContentObserver, mUserTracker.getUserId());
- }
- }
- }
- }
-
- @Override
- public void removeCallback(@androidx.annotation.NonNull Listener listener) {
- synchronized (mListeners) {
- if (mListeners.remove(listener) && mListeners.size() == 0) {
- mSecureSettings.unregisterContentObserver(mContentObserver);
- }
- }
- }
/** Returns {@code true} if Reduce Bright Colors is activated */
- public boolean isReduceBrightColorsActivated() {
- return mManager.isReduceBrightColorsActivated();
- }
+ boolean isReduceBrightColorsActivated();
/** Sets the activation state of Reduce Bright Colors */
- public void setReduceBrightColorsActivated(boolean activated) {
- mManager.setReduceBrightColorsActivated(activated);
- }
-
- private void dispatchOnActivated(boolean activated) {
- ArrayList<Listener> copy = new ArrayList<>(mListeners);
- for (Listener l : copy) {
- l.onActivated(activated);
- }
- }
+ void setReduceBrightColorsActivated(boolean activated);
/**
* Listener invoked whenever the Reduce Bright Colors settings are changed.
*/
- public interface Listener {
+ interface Listener {
/**
* Listener invoked when the activated state changes.
*
@@ -142,4 +39,4 @@
default void onActivated(boolean activated) {
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
new file mode 100644
index 0000000..4fc6609
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+/**
+ * @hide
+ */
+package com.android.systemui.qs;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.hardware.display.ColorDisplayManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+
+@SysUISingleton
+public class ReduceBrightColorsControllerImpl implements
+ ReduceBrightColorsController {
+ private final ColorDisplayManager mManager;
+ private final UserTracker mUserTracker;
+ private UserTracker.Callback mCurrentUserTrackerCallback;
+ private final Handler mHandler;
+ private final ContentObserver mContentObserver;
+ private final SecureSettings mSecureSettings;
+ private final ArrayList<ReduceBrightColorsController.Listener> mListeners = new ArrayList<>();
+
+ @Inject
+ public ReduceBrightColorsControllerImpl(UserTracker userTracker,
+ @Background Handler handler,
+ ColorDisplayManager colorDisplayManager,
+ SecureSettings secureSettings) {
+ mManager = colorDisplayManager;
+ mUserTracker = userTracker;
+ mHandler = handler;
+ mSecureSettings = secureSettings;
+ mContentObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ final String setting = uri == null ? null : uri.getLastPathSegment();
+ synchronized (mListeners) {
+ if (setting != null && mListeners.size() != 0) {
+ if (setting.equals(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED)) {
+ dispatchOnActivated(mManager.isReduceBrightColorsActivated());
+ }
+ }
+ }
+ }
+ };
+
+ mCurrentUserTrackerCallback = new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, Context userContext) {
+ synchronized (mListeners) {
+ if (mListeners.size() > 0) {
+ mSecureSettings.unregisterContentObserver(mContentObserver);
+ mSecureSettings.registerContentObserverForUser(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, newUser);
+ }
+ }
+ }
+ };
+ mUserTracker.addCallback(mCurrentUserTrackerCallback, new HandlerExecutor(handler));
+ }
+
+ @Override
+ public void addCallback(@NonNull Listener listener) {
+ synchronized (mListeners) {
+ if (!mListeners.contains(listener)) {
+ mListeners.add(listener);
+ if (mListeners.size() == 1) {
+ mSecureSettings.registerContentObserverForUser(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, mUserTracker.getUserId());
+ }
+ }
+ }
+ }
+
+ @Override
+ public void removeCallback(@androidx.annotation.NonNull Listener listener) {
+ synchronized (mListeners) {
+ if (mListeners.remove(listener) && mListeners.size() == 0) {
+ mSecureSettings.unregisterContentObserver(mContentObserver);
+ }
+ }
+ }
+
+ @Override
+ public boolean isReduceBrightColorsActivated() {
+ return mManager.isReduceBrightColorsActivated();
+ }
+
+ @Override
+ public void setReduceBrightColorsActivated(boolean activated) {
+ mManager.setReduceBrightColorsActivated(activated);
+ }
+
+ private void dispatchOnActivated(boolean activated) {
+ ArrayList<Listener> copy = new ArrayList<>(mListeners);
+ for (Listener l : copy) {
+ l.onActivated(activated);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index 34b1b2d..a222b3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -42,7 +42,7 @@
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -107,8 +107,7 @@
protected QSCustomizerController(QSCustomizer view, TileQueryHelper tileQueryHelper,
QSHost qsHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle,
KeyguardStateController keyguardStateController, LightBarController lightBarController,
- ConfigurationController configurationController, UiEventLogger uiEventLogger,
- SceneContainerFlags sceneContainerFlags) {
+ ConfigurationController configurationController, UiEventLogger uiEventLogger) {
super(view);
mTileQueryHelper = tileQueryHelper;
mQsHost = qsHost;
@@ -118,7 +117,7 @@
mLightBarController = lightBarController;
mConfigurationController = configurationController;
mUiEventLogger = uiEventLogger;
- view.setSceneContainerEnabled(sceneContainerFlags.isEnabled());
+ view.setSceneContainerEnabled(SceneContainerFlag.isEnabled());
mToolbar = mView.findViewById(com.android.internal.R.id.action_bar);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 8d3500a..b705a03 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -28,6 +28,7 @@
import com.android.systemui.qs.AutoAddTracker;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.ReduceBrightColorsController;
+import com.android.systemui.qs.ReduceBrightColorsControllerImpl;
import com.android.systemui.qs.external.QSExternalModule;
import com.android.systemui.qs.panels.dagger.PanelsModule;
import com.android.systemui.qs.pipeline.dagger.QSPipelineModule;
@@ -118,4 +119,11 @@
@Binds
QSSceneAdapter bindsQsSceneInteractor(QSSceneAdapterImpl impl);
+
+ /**
+ * Dims the screen
+ */
+ @Binds
+ ReduceBrightColorsController bindReduceBrightColorsController(
+ ReduceBrightColorsControllerImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index ca71870..40cf4a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -185,8 +185,9 @@
private val colorEvaluator = ArgbEvaluator.getInstance()
val isLongPressEffectInitialized: Boolean
get() = longPressEffect?.hasInitialized == true
- @VisibleForTesting
- var longPressEffectHandle: DisposableHandle? = null
+ private var longPressEffectHandle: DisposableHandle? = null
+ val isLongPressEffectBound: Boolean
+ get() = longPressEffectHandle != null
init {
val typedValue = TypedValue()
@@ -621,11 +622,14 @@
// Long-press effects
if (state.handlesLongClick &&
longPressEffect?.initializeEffect(longPressEffectDuration) == true) {
- // set the valid long-press effect as the touch listener
- if (longPressEffectHandle == null) {
+ // bind the long-press effect and set it as the touch listener
+ if (!isLongPressEffectBound) {
longPressEffectHandle =
- QSLongPressEffectViewBinder.bind(this, longPressEffect, state.spec)
- setOnTouchListener(longPressEffect)
+ QSLongPressEffectViewBinder.bind(
+ this,
+ longPressEffect,
+ state.spec,
+ )
}
showRippleEffect = false
initializeLongPressProperties()
@@ -634,8 +638,7 @@
// handle a long-press. In this case, we go back to the behaviour of a regular tile
// and clean-up the resources
setOnTouchListener(null)
- longPressEffectHandle?.dispose()
- longPressEffectHandle = null
+ unbindLongPressEffect()
showRippleEffect = isClickable
initialLongPressProperties = null
finalLongPressProperties = null
@@ -827,6 +830,11 @@
changeCornerRadius(newRadius)
}
+ private fun unbindLongPressEffect() {
+ longPressEffectHandle?.dispose()
+ longPressEffectHandle = null
+ }
+
private fun interpolateFloat(fraction: Float, start: Float, end: Float): Float =
start + fraction * (end - start)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
index 6c9a8a4..5122e1f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
@@ -28,6 +28,7 @@
import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter
+import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel
import javax.inject.Inject
import javax.inject.Provider
@@ -57,7 +58,9 @@
val viewModel: QSTileViewModel =
when (val spec = TileSpec.create(tileSpec)) {
is TileSpec.CustomTileSpec -> createCustomTileViewModel(spec)
- is TileSpec.PlatformTileSpec -> tileMap[tileSpec]?.get()
+ // when using the stub, we default to old tile rather than adding the stub
+ is TileSpec.PlatformTileSpec ->
+ tileMap[tileSpec]?.get()?.takeIf { it !is StubQSTileViewModel }
is TileSpec.Invalid -> null
}
?: return null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt
new file mode 100644
index 0000000..98fd561
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.ReduceBrightColorsController
+import com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.util.kotlin.isEnabled
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/** Observes reduce bright colors state changes providing the [ReduceBrightColorsTileModel]. */
+class ReduceBrightColorsTileDataInteractor
+@Inject
+constructor(
+ @Background private val bgCoroutineContext: CoroutineContext,
+ @Named(RBC_AVAILABLE) private val isAvailable: Boolean,
+ private val reduceBrightColorsController: ReduceBrightColorsController,
+) : QSTileDataInteractor<ReduceBrightColorsTileModel> {
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<ReduceBrightColorsTileModel> {
+ return reduceBrightColorsController
+ .isEnabled()
+ .distinctUntilChanged()
+ .map { ReduceBrightColorsTileModel(it) }
+ .flowOn(bgCoroutineContext)
+ }
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(isAvailable)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
new file mode 100644
index 0000000..762f863
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.qs.ReduceBrightColorsController
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles reduce bright colors tile clicks. */
+class ReduceBrightColorsTileUserActionInteractor
+@Inject
+constructor(
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val reduceBrightColorsController: ReduceBrightColorsController,
+) : QSTileUserActionInteractor<ReduceBrightColorsTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<ReduceBrightColorsTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ reduceBrightColorsController.setReduceBrightColorsActivated(
+ !input.data.isEnabled
+ )
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.view,
+ Intent(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS)
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/model/ReduceBrightColorsTileModel.kt
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt
rename to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/model/ReduceBrightColorsTileModel.kt
index 7ca7030..05e0f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/model/ReduceBrightColorsTileModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.dagger
+package com.android.systemui.qs.tiles.impl.reducebrightness.domain.model
-import javax.inject.Qualifier
-
-/** Wifi logs from [WifiRepositoryViaTrackerLib] in table format. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class WifiTrackerLibTableLog
+/**
+ * Reduce bright colors tile model.
+ *
+ * @param isEnabled is true when the reduce bright colors is enabled;
+ */
+@JvmInline value class ReduceBrightColorsTileModel(val isEnabled: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
new file mode 100644
index 0000000..fca93df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.reducebrightness.ui
+
+import android.content.res.Resources
+import android.service.quicksettings.Tile
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [ReduceBrightColorsTileModel] to [QSTileState]. */
+class ReduceBrightColorsTileMapper
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<ReduceBrightColorsTileModel> {
+
+ override fun map(config: QSTileConfig, data: ReduceBrightColorsTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ if (data.isEnabled) {
+ activationState = QSTileState.ActivationState.ACTIVE
+ icon = {
+ Icon.Loaded(
+ drawable = resources.getDrawable(R.drawable.qs_extra_dim_icon_on, theme),
+ contentDescription = null
+ )
+ }
+
+ secondaryLabel =
+ resources
+ .getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_ACTIVE]
+ } else {
+ activationState = QSTileState.ActivationState.INACTIVE
+ icon = {
+ Icon.Loaded(
+ drawable = resources.getDrawable(R.drawable.qs_extra_dim_icon_off, theme),
+ contentDescription = null
+ )
+ }
+ secondaryLabel =
+ resources
+ .getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_INACTIVE]
+ }
+ label =
+ resources.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
+ contentDescription = label
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileDataInteractor.kt
new file mode 100644
index 0000000..85d2e3b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileDataInteractor.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.screenrecord.domain.model.ScreenRecordTileModel
+import com.android.systemui.screenrecord.RecordingController
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+
+/** Observes screen record state changes providing the [ScreenRecordTileModel]. */
+class ScreenRecordTileDataInteractor
+@Inject
+constructor(
+ @Background private val bgCoroutineContext: CoroutineContext,
+ private val recordingController: RecordingController,
+) : QSTileDataInteractor<ScreenRecordTileModel> {
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<ScreenRecordTileModel> =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val callback =
+ object : RecordingController.RecordingStateChangeCallback {
+ override fun onRecordingStart() {
+ trySend(ScreenRecordTileModel.Recording)
+ }
+ override fun onRecordingEnd() {
+ trySend(ScreenRecordTileModel.DoingNothing)
+ }
+ override fun onCountdown(millisUntilFinished: Long) {
+ trySend(ScreenRecordTileModel.Starting(millisUntilFinished))
+ }
+ override fun onCountdownEnd() {
+ if (
+ !recordingController.isRecording && !recordingController.isStarting
+ ) {
+ // The tile was in Starting state and got canceled before recording
+ trySend(ScreenRecordTileModel.DoingNothing)
+ }
+ }
+ }
+ recordingController.addCallback(callback)
+ awaitClose { recordingController.removeCallback(callback) }
+ }
+ .onStart { emit(generateModel()) }
+ .distinctUntilChanged()
+ .flowOn(bgCoroutineContext)
+
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+
+ private fun generateModel(): ScreenRecordTileModel {
+ if (recordingController.isRecording) {
+ return ScreenRecordTileModel.Recording
+ } else if (recordingController.isStarting) {
+ return ScreenRecordTileModel.Starting(0)
+ } else {
+ return ScreenRecordTileModel.DoingNothing
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
new file mode 100644
index 0000000..d2bd09f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor
+
+import android.content.Context
+import android.util.Log
+import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.screenrecord.domain.model.ScreenRecordTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.screenrecord.RecordingController
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles screen recorder tile clicks. */
+class ScreenRecordTileUserActionInteractor
+@Inject
+constructor(
+ @Application private val context: Context,
+ @Main private val mainContext: CoroutineContext,
+ @Background private val backgroundContext: CoroutineContext,
+ private val recordingController: RecordingController,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardDismissUtil: KeyguardDismissUtil,
+ private val dialogTransitionAnimator: DialogTransitionAnimator,
+ private val panelInteractor: PanelInteractor,
+ private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
+ private val featureFlags: FeatureFlagsClassic,
+ private val activityStarter: ActivityStarter,
+) : QSTileUserActionInteractor<ScreenRecordTileModel> {
+ override suspend fun handleInput(input: QSTileInput<ScreenRecordTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ when (data) {
+ is ScreenRecordTileModel.Starting -> {
+ Log.d(TAG, "Cancelling countdown")
+ withContext(backgroundContext) { recordingController.cancelCountdown() }
+ }
+ is ScreenRecordTileModel.Recording ->
+ withContext(backgroundContext) { recordingController.stopRecording() }
+ is ScreenRecordTileModel.DoingNothing ->
+ withContext(mainContext) { showPrompt(action.view, user.identifier) }
+ }
+ }
+ is QSTileUserAction.LongClick -> {} // no-op
+ }
+ }
+
+ private fun showPrompt(view: View?, userId: Int) {
+ // Create the recording dialog that will collapse the shade only if we start the recording.
+ val onStartRecordingClicked = Runnable {
+ // We dismiss the shade. Since starting the recording will also dismiss the dialog, we
+ // disable the exit animation which looks weird when it happens at the same time as the
+ // shade collapsing.
+ dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
+ panelInteractor.collapsePanels()
+ }
+
+ val dialog =
+ recordingController.createScreenRecordDialog(
+ context,
+ featureFlags,
+ dialogTransitionAnimator,
+ activityStarter,
+ onStartRecordingClicked
+ )
+
+ if (dialog == null) {
+ Log.w(TAG, "showPrompt: dialog was null")
+ return
+ }
+
+ // We animate from the touched view only if we are not on the keyguard, given that if we
+ // are we will dismiss it which will also collapse the shade.
+ val shouldAnimateFromView = view != null && !keyguardInteractor.isKeyguardShowing()
+ val dismissAction =
+ ActivityStarter.OnDismissAction {
+ if (shouldAnimateFromView) {
+ dialogTransitionAnimator.showFromView(
+ dialog,
+ view!!,
+ DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG
+ ),
+ animateBackgroundBoundsChange = true
+ )
+ } else {
+ dialog.show()
+ }
+ mediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(userId)
+ false
+ }
+
+ keyguardDismissUtil.executeWhenUnlocked(
+ dismissAction,
+ false /* requiresShadeOpen */,
+ true /* afterKeyguardDone */
+ )
+ }
+
+ private companion object {
+ const val TAG = "ScreenRecordTileUserActionInteractor"
+ const val INTERACTION_JANK_TAG = "screen_record"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/model/ScreenRecordTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/model/ScreenRecordTileModel.kt
new file mode 100644
index 0000000..26b0b01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/model/ScreenRecordTileModel.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.screenrecord.domain.model
+
+/** Data model for screen record tile */
+sealed interface ScreenRecordTileModel {
+ data object Recording : ScreenRecordTileModel
+ data class Starting(val millisUntilStarted: Long) : ScreenRecordTileModel
+ data object DoingNothing : ScreenRecordTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
new file mode 100644
index 0000000..c09b0e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.screenrecord.domain.ui
+
+import android.content.res.Resources
+import android.text.TextUtils
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.screenrecord.domain.model.ScreenRecordTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [ScreenRecordTileModel] to [QSTileState]. */
+class ScreenRecordTileMapper
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<ScreenRecordTileModel> {
+ override fun map(config: QSTileConfig, data: ScreenRecordTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ label = resources.getString(R.string.quick_settings_screen_record_label)
+ supportedActions = setOf(QSTileState.UserAction.CLICK)
+
+ when (data) {
+ is ScreenRecordTileModel.Recording -> {
+ activationState = QSTileState.ActivationState.ACTIVE
+ val loadedIcon =
+ Icon.Loaded(
+ resources.getDrawable(R.drawable.qs_screen_record_icon_on, theme),
+ contentDescription = null
+ )
+ icon = { loadedIcon }
+ sideViewIcon = QSTileState.SideViewIcon.None
+ secondaryLabel = resources.getString(R.string.quick_settings_screen_record_stop)
+ }
+ is ScreenRecordTileModel.Starting -> {
+ activationState = QSTileState.ActivationState.ACTIVE
+ val loadedIcon =
+ Icon.Loaded(
+ resources.getDrawable(R.drawable.qs_screen_record_icon_on, theme),
+ contentDescription = null
+ )
+ icon = { loadedIcon }
+ val countDown = Math.floorDiv(data.millisUntilStarted + 500, 1000)
+ sideViewIcon = QSTileState.SideViewIcon.None
+ secondaryLabel = String.format("%d...", countDown)
+ }
+ is ScreenRecordTileModel.DoingNothing -> {
+ activationState = QSTileState.ActivationState.INACTIVE
+ val loadedIcon =
+ Icon.Loaded(
+ resources.getDrawable(R.drawable.qs_screen_record_icon_off, theme),
+ contentDescription = null
+ )
+ icon = { loadedIcon }
+ sideViewIcon = QSTileState.SideViewIcon.Chevron // tapping will open dialog
+ secondaryLabel =
+ resources.getString(R.string.quick_settings_screen_record_start)
+ }
+ }
+ contentDescription =
+ if (TextUtils.isEmpty(secondaryLabel)) label
+ else TextUtils.concat(label, ", ", secondaryLabel)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt
new file mode 100644
index 0000000..64f1fd3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.viewmodel
+
+import android.os.UserHandle
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
+
+object StubQSTileViewModel : QSTileViewModel {
+
+ override val state: SharedFlow<QSTileState>
+ get() = error("Don't call stubs")
+
+ override val config: QSTileConfig
+ get() = error("Don't call stubs")
+
+ override val isAvailable: StateFlow<Boolean>
+ get() = error("Don't call stubs")
+
+ override fun onUserChanged(user: UserHandle) = error("Don't call stubs")
+
+ override fun forceUpdate() = error("Don't call stubs")
+
+ override fun onActionPerformed(userAction: QSTileUserAction) = error("Don't call stubs")
+
+ override fun destroy() = error("Don't call stubs")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 4ece7b6..1ddc094 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -98,7 +98,7 @@
import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
@@ -146,7 +146,6 @@
private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
private final Context mContext;
- private final SceneContainerFlags mSceneContainerFlags;
private final Executor mMainExecutor;
private final ShellInterface mShellInterface;
private final Lazy<ShadeViewController> mShadeViewControllerLazy;
@@ -208,7 +207,7 @@
@Override
public void onStatusBarTouchEvent(MotionEvent event) {
verifyCallerAndClearCallingIdentity("onStatusBarTouchEvent", () -> {
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
//TODO(b/329863123) implement latency tracking for shade scene
Log.i(TAG_OPS, "Scene container enabled. Latency tracking not started.");
} else if (event.getActionMasked() == ACTION_DOWN) {
@@ -223,7 +222,7 @@
// If scene framework is enabled, set the scene container window to
// visible and let the touch "slip" into that window.
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
mSceneInteractor.get().onRemoteUserInteractionStarted("launcher swipe");
} else {
mShadeViewControllerLazy.get().startInputFocusTransfer();
@@ -232,7 +231,7 @@
if (action == ACTION_UP || action == ACTION_CANCEL) {
mInputFocusTransferStarted = false;
- if (!mSceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
float velocity = (event.getY() - mInputFocusTransferStartY)
/ (event.getEventTime() - mInputFocusTransferStartMillis);
if (action == ACTION_CANCEL) {
@@ -612,7 +611,6 @@
KeyguardUnlockAnimationController sysuiUnlockAnimationController,
InWindowLauncherUnlockAnimationManager inWindowLauncherUnlockAnimationManager,
AssistUtils assistUtils,
- SceneContainerFlags sceneContainerFlags,
DumpManager dumpManager,
Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder,
BroadcastDispatcher broadcastDispatcher
@@ -624,7 +622,6 @@
}
mContext = context;
- mSceneContainerFlags = sceneContainerFlags;
mMainExecutor = mainExecutor;
mShellInterface = shellInterface;
mShadeViewControllerLazy = shadeViewControllerLazy;
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 4d34a86..4e290e6 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -47,6 +47,7 @@
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.inject.Inject
+import kotlin.jvm.optionals.getOrElse
class IssueRecordingService
@Inject
@@ -140,15 +141,25 @@
}
private fun shareRecording(screenRecording: Uri?) {
- val sharableUri: Uri =
- zipAndPackageRecordings(
- TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get(),
- screenRecording
- )
- ?: return
+ val traces =
+ TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).getOrElse {
+ Log.v(
+ TAG,
+ "Traces were not present. This can happen if users double" +
+ "click on share notification. Traces are cleaned up after sharing" +
+ "so they won't be present for the 2nd share attempt."
+ )
+ return
+ }
+ val perfetto = FileProvider.getUriForFile(this, AUTHORITY, traces.first())
+ val urisToShare = mutableListOf(perfetto)
+ traces.removeFirst()
+
+ getZipWinscopeFileUri(traces)?.let { urisToShare.add(it) }
+ screenRecording?.let { urisToShare.add(it) }
+
val sendIntent =
- FileSender.buildSendIntent(this, listOf(sharableUri))
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ FileSender.buildSendIntent(this, urisToShare).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
mKeyguardDismissUtil.executeWhenUnlocked(
@@ -161,7 +172,7 @@
)
}
- private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecording: Uri?): Uri? {
+ private fun getZipWinscopeFileUri(traceFiles: List<File>): Uri? {
try {
externalCacheDir?.mkdirs()
val outZip: File = File.createTempFile(TEMP_FILE_PREFIX, ZIP_SUFFIX, externalCacheDir)
@@ -171,13 +182,6 @@
Files.copy(file.toPath(), os)
os.closeEntry()
}
- if (screenRecording != null) {
- contentResolver.openInputStream(screenRecording)?.use {
- os.putNextEntry(ZipEntry(SCREEN_RECORDING_ZIP_LABEL))
- it.transferTo(os)
- os.closeEntry()
- }
- }
}
return FileProvider.getUriForFile(this, AUTHORITY, outZip)
} catch (e: Exception) {
@@ -192,8 +196,7 @@
private const val EXTRA_SCREEN_RECORD = "extra_screenRecord"
private const val EXTRA_WINSCOPE_TRACING = "extra_winscopeTracing"
private const val ZIP_SUFFIX = ".zip"
- private const val TEMP_FILE_PREFIX = "issue_recording"
- private const val SCREEN_RECORDING_ZIP_LABEL = "screen-recording.mp4"
+ private const val TEMP_FILE_PREFIX = "winscope_recordings"
private val DEFAULT_TRACE_TAGS = listOf<String>()
private const val DEFAULT_BUFFER_SIZE = 16384
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index afd0746..8277c73 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -16,7 +16,6 @@
package com.android.systemui.scene
-import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import dagger.Module
@@ -29,7 +28,6 @@
EmptySceneModule::class,
GoneSceneModule::class,
QuickSettingsSceneModule::class,
- SceneContainerFlagsModule::class,
ShadeSceneModule::class,
],
)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 62b0914..69f9443 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -20,7 +20,6 @@
import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlagsModule
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.scene.domain.startable.SceneContainerStartable
-import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import dagger.Binds
@@ -40,7 +39,6 @@
GoneSceneModule::class,
LockscreenSceneModule::class,
QuickSettingsSceneModule::class,
- SceneContainerFlagsModule::class,
ShadeSceneModule::class,
],
)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
index 0665c9e..d202c24 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
@@ -16,7 +16,6 @@
package com.android.systemui.scene
-import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import dagger.Module
@@ -30,7 +29,6 @@
EmptySceneModule::class,
GoneSceneModule::class,
LockscreenSceneModule::class,
- SceneContainerFlagsModule::class,
],
)
object ShadelessSceneContainerFrameworkModule {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index c736707..1cf1c18 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -24,7 +24,7 @@
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
@@ -53,7 +53,6 @@
private val headsUpManager: HeadsUpManager,
private val powerInteractor: PowerInteractor,
private val activeNotificationsInteractor: ActiveNotificationsInteractor,
- sceneContainerFlags: SceneContainerFlags,
sceneInteractorProvider: Provider<SceneInteractor>,
) : CoreStartable {
@@ -68,7 +67,7 @@
* false if the bouncer is visible.
*/
val isLockscreenOrShadeVisible: StateFlow<Boolean> =
- if (!sceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
} else {
sceneInteractorProvider
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 0e66c28..4774eb3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -44,7 +44,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -92,7 +92,6 @@
private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val bouncerInteractor: BouncerInteractor,
private val keyguardInteractor: KeyguardInteractor,
- private val flags: SceneContainerFlags,
private val sysUiState: SysUiState,
@DisplayId private val displayId: Int,
private val sceneLogger: SceneLogger,
@@ -111,7 +110,7 @@
) : CoreStartable {
override fun start() {
- if (flags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
sceneLogger.logFrameworkEnabled(isEnabled = true)
hydrateVisibility()
automaticallySwitchScenes()
@@ -124,16 +123,18 @@
} else {
sceneLogger.logFrameworkEnabled(
isEnabled = false,
- reason = flags.requirementDescription(),
+ reason = SceneContainerFlag.requirementDescription(),
)
}
}
override fun dump(pw: PrintWriter, args: Array<out String>) =
pw.asIndenting().run {
- printSection("SceneContainerFlags") {
- println("isEnabled", flags.isEnabled())
- printSection("requirementDescription") { println(flags.requirementDescription()) }
+ printSection("SceneContainerFlag") {
+ println("isEnabled", SceneContainerFlag.isEnabled)
+ printSection("requirementDescription") {
+ println(SceneContainerFlag.requirementDescription())
+ }
}
}
@@ -288,7 +289,8 @@
Scenes.Gone to "device was unlocked in Bouncer scene"
} else {
val prevScene = previousScene.value
- (prevScene ?: Scenes.Gone) to
+ (prevScene
+ ?: Scenes.Gone) to
"device was unlocked in Bouncer scene, from sceneKey=$prevScene"
}
isOnLockscreen ->
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
rename to packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
index cff11a7..234eda8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
@@ -20,7 +20,6 @@
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.Flags.sceneContainer
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
@@ -32,8 +31,6 @@
import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag
-import dagger.Module
-import dagger.Provides
/** Helper for reading or using the scene container flag state. */
object SceneContainerFlag {
@@ -99,30 +96,12 @@
*/
@JvmStatic
inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, DESCRIPTION)
-}
-
-/**
- * Defines interface for classes that can check whether the scene container framework feature is
- * enabled.
- */
-interface SceneContainerFlags {
-
- /** Returns `true` if the Scene Container Framework is enabled; `false` otherwise. */
- fun isEnabled(): Boolean
/** Returns a developer-readable string that describes the current requirement list. */
- fun requirementDescription(): String
-}
-
-class SceneContainerFlagsImpl : SceneContainerFlags {
-
- override fun isEnabled(): Boolean {
- return SceneContainerFlag.isEnabled
- }
-
- override fun requirementDescription(): String {
+ @JvmStatic
+ fun requirementDescription(): String {
return buildString {
- SceneContainerFlag.getAllRequirements().forEach { requirement ->
+ getAllRequirements().forEach { requirement ->
append('\n')
append(if (requirement.isEnabled) " [MET]" else "[NOT MET]")
append(" ${requirement.name}")
@@ -130,9 +109,3 @@
}
}
}
-
-@Module
-object SceneContainerFlagsModule {
-
- @Provides @SysUISingleton fun impl(): SceneContainerFlags = SceneContainerFlagsImpl()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index 67dc0cc..259a8bf 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -4,7 +4,6 @@
import android.util.AttributeSet
import android.view.View
import android.view.WindowInsets
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
@@ -31,7 +30,6 @@
viewModel: SceneContainerViewModel,
containerConfig: SceneContainerConfig,
sharedNotificationContainer: SharedNotificationContainer,
- flags: SceneContainerFlags,
scenes: Set<Scene>,
layoutInsetController: LayoutInsetsController,
sceneDataSourceDelegator: SceneDataSourceDelegator,
@@ -44,7 +42,6 @@
windowInsets = windowInsets,
containerConfig = containerConfig,
sharedNotificationContainer = sharedNotificationContainer,
- flags = flags,
scenes = scenes,
onVisibilityChangedInternal = { isVisible ->
super.setVisibility(if (isVisible) View.VISIBLE else View.INVISIBLE)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 809ac2e..2ef9b73 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -39,7 +39,7 @@
import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
@@ -63,7 +63,6 @@
windowInsets: StateFlow<WindowInsets?>,
containerConfig: SceneContainerConfig,
sharedNotificationContainer: SharedNotificationContainer,
- flags: SceneContainerFlags,
scenes: Set<Scene>,
onVisibilityChangedInternal: (isVisible: Boolean) -> Unit,
dataSourceDelegator: SceneDataSourceDelegator,
@@ -115,7 +114,7 @@
// the SceneContainerView. This SharedNotificationContainer should contain NSSL
// due to the NotificationStackScrollLayoutSection (legacy) or
// NotificationSection (scene container) moving it there.
- if (flags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
(sharedNotificationContainer.parent as? ViewGroup)?.removeView(
sharedNotificationContainer
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt
index 0bc02ed..8c675e3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt
@@ -16,10 +16,22 @@
package com.android.systemui.screenrecord
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tiles.ScreenRecordTile
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor.ScreenRecordTileDataInteractor
+import com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor.ScreenRecordTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.screenrecord.domain.model.ScreenRecordTileModel
+import com.android.systemui.qs.tiles.impl.screenrecord.domain.ui.ScreenRecordTileMapper
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
+import dagger.Provides
import dagger.multibindings.IntoMap
import dagger.multibindings.StringKey
@@ -30,4 +42,39 @@
@IntoMap
@StringKey(ScreenRecordTile.TILE_SPEC)
fun bindScreenRecordTile(screenRecordTile: ScreenRecordTile): QSTileImpl<*>
+
+ companion object {
+ private const val SCREEN_RECORD_TILE_SPEC = "screenrecord"
+
+ @Provides
+ @IntoMap
+ @StringKey(SCREEN_RECORD_TILE_SPEC)
+ fun provideScreenRecordTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(SCREEN_RECORD_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_screen_record_icon_off,
+ labelRes = R.string.quick_settings_screen_record_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /** Inject ScreenRecord Tile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(SCREEN_RECORD_TILE_SPEC)
+ fun provideScreenRecordTileViewModel(
+ factory: QSTileViewModelFactory.Static<ScreenRecordTileModel>,
+ mapper: ScreenRecordTileMapper,
+ stateInteractor: ScreenRecordTileDataInteractor,
+ userActionInteractor: ScreenRecordTileUserActionInteractor
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(SCREEN_RECORD_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index ec7707c..e56a4f4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -22,7 +22,7 @@
interface TakeScreenshotExecutor {
suspend fun executeScreenshots(
screenshotRequest: ScreenshotRequest,
- onSaved: (Uri) -> Unit,
+ onSaved: (Uri?) -> Unit,
requestCallback: RequestCallback
)
fun onCloseSystemDialogsReceived()
@@ -30,7 +30,7 @@
fun onDestroy()
fun executeScreenshotsAsync(
screenshotRequest: ScreenshotRequest,
- onSaved: Consumer<Uri>,
+ onSaved: Consumer<Uri?>,
requestCallback: RequestCallback
)
}
@@ -65,7 +65,7 @@
*/
override suspend fun executeScreenshots(
screenshotRequest: ScreenshotRequest,
- onSaved: (Uri) -> Unit,
+ onSaved: (Uri?) -> Unit,
requestCallback: RequestCallback
) {
val displayIds = getDisplaysToScreenshot(screenshotRequest.type)
@@ -86,7 +86,7 @@
/** All logging should be triggered only by this method. */
private suspend fun dispatchToController(
rawScreenshotData: ScreenshotData,
- onSaved: (Uri) -> Unit,
+ onSaved: (Uri?) -> Unit,
callback: RequestCallback
) {
// Let's wait before logging "screenshot requested", as we should log the processed
@@ -185,7 +185,7 @@
/** For java compatibility only. see [executeScreenshots] */
override fun executeScreenshotsAsync(
screenshotRequest: ScreenshotRequest,
- onSaved: Consumer<Uri>,
+ onSaved: Consumer<Uri?>,
requestCallback: RequestCallback
) {
mainScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
index d62ab85..1945c25 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
@@ -39,11 +39,11 @@
override suspend fun check(content: DisplayContentModel): PolicyResult {
// The systemUI notification shade isn't a private profile app, skip.
if (content.systemUiState.shadeExpanded) {
- return NotMatched(policy = NAME, reason = "Notification shade is expanded")
+ return NotMatched(policy = NAME, reason = SHADE_EXPANDED)
}
// Find the first visible rootTaskInfo with a child task owned by a private user
- val (rootTask, childTask) =
+ val childTask =
content.rootTasks
.filter { it.isVisible }
.firstNotNullOfOrNull { root ->
@@ -52,22 +52,24 @@
.firstOrNull {
profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE
}
- ?.let { root to it }
}
- ?: return NotMatched(policy = NAME, reason = "No private profile tasks are visible")
+ ?: return NotMatched(policy = NAME, reason = NO_VISIBLE_TASKS)
// If matched, return parameters needed to modify the request.
return Matched(
policy = NAME,
- reason = "At least one private profile task is visible",
+ reason = PRIVATE_TASK_VISIBLE,
CaptureParameters(
type = FullScreen(content.displayId),
- component = childTask.componentName ?: rootTask.topActivity,
+ component = content.rootTasks.first { it.isVisible }.topActivity,
owner = UserHandle.of(childTask.userId),
)
)
}
companion object {
const val NAME = "PrivateProfile"
+ const val SHADE_EXPANDED = "Notification shade is expanded"
+ const val NO_VISIBLE_TASKS = "No private profile tasks are visible"
+ const val PRIVATE_TASK_VISIBLE = "At least one private profile task is visible"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
index 3789371..f768cfb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
@@ -30,3 +30,5 @@
)
}
}
+
+internal fun RootTaskInfo.hasChildTasks() = childTaskUserIds.isNotEmpty()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
index b781ae9..fdf16aa 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -16,6 +16,7 @@
package com.android.systemui.screenshot.policy
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.os.UserHandle
import com.android.systemui.screenshot.data.model.DisplayContentModel
@@ -24,6 +25,7 @@
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
+import com.android.window.flags.Flags
import javax.inject.Inject
import kotlinx.coroutines.flow.first
@@ -41,26 +43,36 @@
override suspend fun check(content: DisplayContentModel): PolicyResult {
// The systemUI notification shade isn't a work app, skip.
if (content.systemUiState.shadeExpanded) {
- return NotMatched(policy = NAME, reason = "Notification shade is expanded")
+ return NotMatched(policy = NAME, reason = SHADE_EXPANDED)
+ }
+
+ if (Flags.enableDesktopWindowingMode()) {
+ content.rootTasks.firstOrNull()?.also {
+ if (it.windowingMode == WINDOWING_MODE_FREEFORM) {
+ return NotMatched(policy = NAME, reason = DESKTOP_MODE_ENABLED)
+ }
+ }
}
// Find the first non PiP rootTask with a top child task owned by a work user
val (rootTask, childTask) =
content.rootTasks
- .filter { it.isVisible && it.windowingMode != WINDOWING_MODE_PINNED }
+ .filter {
+ it.isVisible && it.windowingMode != WINDOWING_MODE_PINNED && it.hasChildTasks()
+ }
.map { it to it.childTasksTopDown().first() }
.firstOrNull { (_, child) ->
profileTypes.getProfileType(child.userId) == ProfileType.WORK
}
?: return NotMatched(
policy = NAME,
- reason = "The top-most non-PINNED task does not belong to a work profile user"
+ reason = WORK_TASK_NOT_TOP,
)
// If matched, return parameters needed to modify the request.
return PolicyResult.Matched(
policy = NAME,
- reason = "The top-most non-PINNED task ($childTask) belongs to a work profile user",
+ reason = WORK_TASK_IS_TOP,
CaptureParameters(
type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds),
component = childTask.componentName ?: rootTask.topActivity,
@@ -70,6 +82,13 @@
}
companion object {
- val NAME = "WorkProfile"
+ const val NAME = "WorkProfile"
+ const val SHADE_EXPANDED = "Notification shade is expanded"
+ const val WORK_TASK_NOT_TOP =
+ "The top-most non-PINNED task does not belong to a work profile user"
+ const val WORK_TASK_IS_TOP = "The top-most non-PINNED task belongs to a work profile user"
+ const val DESKTOP_MODE_ENABLED =
+ "enable_desktop_windowing_mode is enabled and top " +
+ "RootTask has WINDOWING_MODE_FREEFORM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
index 706ac9c..b43a1d2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.HardwareRenderer;
+import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
@@ -38,9 +39,11 @@
import android.view.Display;
import android.view.ScrollCaptureResponse;
import android.view.View;
+import android.view.WindowInsets;
import android.widget.ImageView;
import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.core.view.WindowCompat;
import com.android.internal.app.ChooserActivity;
import com.android.internal.logging.UiEventLogger;
@@ -127,6 +130,10 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ // Enable edge-to-edge explicitly.
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
+
setContentView(R.layout.long_screenshot);
mPreview = requireViewById(R.id.preview);
@@ -149,6 +156,13 @@
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
updateImageDimensions());
+ requireViewById(R.id.root).setOnApplyWindowInsetsListener(
+ (view, windowInsets) -> {
+ Insets insets = windowInsets.getInsets(WindowInsets.Type.systemBars());
+ view.setPadding(insets.left, insets.top, insets.right, insets.bottom);
+ return WindowInsets.CONSUMED;
+ });
+
Intent intent = getIntent();
mScrollCaptureResponse = intent.getParcelableExtra(EXTRA_CAPTURE_RESPONSE);
mScreenshotUserHandle = intent.getParcelableExtra(EXTRA_SCREENSHOT_USER_HANDLE,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index d9a5102..5f835b3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -95,7 +95,7 @@
// mean that the new action must be inserted here.
val actionButton =
layoutInflater.inflate(
- R.layout.overlay_action_chip,
+ R.layout.shelf_action_chip,
actionsContainer,
false
)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index f418e7e..8f6b9d0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -36,6 +36,7 @@
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -64,6 +65,7 @@
private val keyguardInteractor: KeyguardInteractor,
private val shadeInteractor: ShadeInteractor,
private val powerManager: PowerManager,
+ private val communalColors: CommunalColors,
@Communal private val dataSourceDelegator: SceneDataSourceDelegator,
) {
/** The container view for the hub. This will not be initialized until [initView] is called. */
@@ -168,6 +170,7 @@
PlatformTheme {
CommunalContainer(
viewModel = communalViewModel,
+ colors = communalColors,
dataSourceDelegator = dataSourceDelegator,
dialogFactory = dialogFactory,
)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b8512f2..aa915e3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -191,6 +191,7 @@
import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -291,7 +292,6 @@
*/
public final boolean mAnimateBack;
- private final boolean mTrackpadGestureFeaturesEnabled;
/**
* The minimum scale to "squish" the Shade and associated elements down to, for Back gesture
*/
@@ -438,6 +438,7 @@
private boolean mExpandingFromHeadsUp;
private boolean mCollapsedOnDown;
private boolean mClosingWithAlphaFadeOut;
+ private boolean mHeadsUpVisible;
private boolean mHeadsUpAnimatingAway;
private final FalsingManager mFalsingManager;
private final FalsingCollector mFalsingCollector;
@@ -605,6 +606,7 @@
private final PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
private final SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
private final ActiveNotificationsInteractor mActiveNotificationsInteractor;
+ private final HeadsUpNotificationInteractor mHeadsUpNotificationInteractor;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private final KeyguardInteractor mKeyguardInteractor;
private final PowerInteractor mPowerInteractor;
@@ -770,6 +772,7 @@
ActivityStarter activityStarter,
SharedNotificationContainerInteractor sharedNotificationContainerInteractor,
ActiveNotificationsInteractor activeNotificationsInteractor,
+ HeadsUpNotificationInteractor headsUpNotificationInteractor,
ShadeAnimationInteractor shadeAnimationInteractor,
KeyguardViewConfigurator keyguardViewConfigurator,
DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
@@ -804,6 +807,7 @@
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mSharedNotificationContainerInteractor = sharedNotificationContainerInteractor;
mActiveNotificationsInteractor = activeNotificationsInteractor;
+ mHeadsUpNotificationInteractor = headsUpNotificationInteractor;
mKeyguardInteractor = keyguardInteractor;
mPowerInteractor = powerInteractor;
mKeyguardViewConfigurator = keyguardViewConfigurator;
@@ -886,7 +890,6 @@
mLayoutInflater = layoutInflater;
mFeatureFlags = featureFlags;
mAnimateBack = predictiveBackAnimateShade();
- mTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
mFalsingCollector = falsingCollector;
mWakeUpCoordinator = coordinator;
mMainDispatcher = mainDispatcher;
@@ -1216,6 +1219,11 @@
}
},
mMainDispatcher);
+
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ collectFlow(mView, mHeadsUpNotificationInteractor.isHeadsUpOrAnimatingAway(),
+ setHeadsUpVisible(), mMainDispatcher);
+ }
}
@VisibleForTesting
@@ -3055,7 +3063,21 @@
mPanelAlphaEndAction = r;
}
+ private Consumer<Boolean> setHeadsUpVisible() {
+ return (Boolean isHeadsUpVisible) -> {
+ mHeadsUpVisible = isHeadsUpVisible;
+
+ if (isHeadsUpVisible) {
+ updateNotificationTranslucency();
+ }
+ updateExpansionAndVisibility();
+ updateGestureExclusionRect();
+ mKeyguardStatusBarViewController.updateForHeadsUp();
+ };
+ }
+
private void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
mHeadsUpAnimatingAway = headsUpAnimatingAway;
mNotificationStackScrollLayoutController.setHeadsUpAnimatingAway(headsUpAnimatingAway);
updateVisibility();
@@ -3071,13 +3093,16 @@
}
private boolean shouldPanelBeVisible() {
- boolean headsUpVisible = mHeadsUpAnimatingAway || mHeadsUpPinnedMode;
+ boolean headsUpVisible = NotificationsHeadsUpRefactor.isEnabled() ? mHeadsUpVisible
+ : (mHeadsUpAnimatingAway || mHeadsUpPinnedMode);
return headsUpVisible || isExpanded() || mBouncerShowing;
}
private void setHeadsUpManager(HeadsUpManager headsUpManager) {
mHeadsUpManager = headsUpManager;
- mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
+ if (!NotificationsHeadsUpRefactor.isEnabled()) {
+ mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
+ }
mHeadsUpTouchHelper = new HeadsUpTouchHelper(
headsUpManager,
mStatusBarService,
@@ -3165,8 +3190,9 @@
}
private boolean isPanelVisibleBecauseOfHeadsUp() {
- return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway)
- && mBarState == StatusBarState.SHADE;
+ boolean headsUpVisible = NotificationsHeadsUpRefactor.isEnabled() ? mHeadsUpVisible
+ : (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway);
+ return headsUpVisible && mBarState == StatusBarState.SHADE;
}
private boolean isPanelVisibleBecauseScrimIsAnimatingOff() {
@@ -3479,6 +3505,7 @@
ipw.print("mExpandingFromHeadsUp="); ipw.println(mExpandingFromHeadsUp);
ipw.print("mCollapsedOnDown="); ipw.println(mCollapsedOnDown);
ipw.print("mClosingWithAlphaFadeOut="); ipw.println(mClosingWithAlphaFadeOut);
+ ipw.print("mHeadsUpVisible="); ipw.println(mHeadsUpVisible);
ipw.print("mHeadsUpAnimatingAway="); ipw.println(mHeadsUpAnimatingAway);
ipw.print("mShowIconsWhenExpanded="); ipw.println(mShowIconsWhenExpanded);
ipw.print("mIndicationBottomPadding="); ipw.println(mIndicationBottomPadding);
@@ -4384,6 +4411,8 @@
private final class ShadeHeadsUpChangedListener implements OnHeadsUpChangedListener {
@Override
public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
+
if (inPinnedMode) {
mHeadsUpExistenceChangedRunnable.run();
updateNotificationTranslucency();
@@ -4400,9 +4429,7 @@
@Override
public void onHeadsUpPinned(NotificationEntry entry) {
- if (NotificationsHeadsUpRefactor.isEnabled()) {
- return;
- }
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
if (!isKeyguardShowing()) {
mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, true);
@@ -4411,9 +4438,7 @@
@Override
public void onHeadsUpUnPinned(NotificationEntry entry) {
- if (NotificationsHeadsUpRefactor.isEnabled()) {
- return;
- }
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
// When we're unpinning the notification via active edge they remain heads-upped,
// we need to make sure that an animation happens in this case, otherwise the
@@ -4898,9 +4923,8 @@
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
boolean canCollapsePanel = canCollapsePanelOnTouch();
- final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(
- mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe(
- mTrackpadGestureFeaturesEnabled, event);
+ final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(event)
+ || isTrackpadThreeFingerSwipe(event);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
@@ -4920,7 +4944,7 @@
mIsTrackpadReverseScroll =
!mNaturalScrollingSettingObserver.isNaturalScrollingEnabled()
- && isTrackpadScroll(mTrackpadGestureFeaturesEnabled, event);
+ && isTrackpadScroll(event);
if (!isTracking() || isFullyCollapsed()) {
mInitialExpandY = y;
mInitialExpandX = x;
@@ -5143,9 +5167,8 @@
mIgnoreXTouchSlop = true;
}
- final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(
- mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe(
- mTrackpadGestureFeaturesEnabled, event);
+ final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(event)
+ || isTrackpadThreeFingerSwipe(event);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index fb32b9f..adcb14a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -59,7 +59,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.scene.ui.view.WindowRootViewComponent;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -117,7 +117,6 @@
private final AuthController mAuthController;
private final Lazy<SelectedUserInteractor> mUserInteractor;
private final Lazy<ShadeInteractor> mShadeInteractorLazy;
- private final SceneContainerFlags mSceneContainerFlags;
private final Lazy<CommunalInteractor> mCommunalInteractor;
private ViewGroup mWindowRootView;
private LayoutParams mLp;
@@ -166,7 +165,6 @@
ShadeWindowLogger logger,
Lazy<SelectedUserInteractor> userInteractor,
UserTracker userTracker,
- SceneContainerFlags sceneContainerFlags,
Lazy<CommunalInteractor> communalInteractor) {
mContext = context;
mWindowRootViewComponentFactory = windowRootViewComponentFactory;
@@ -186,7 +184,6 @@
dumpManager.registerCriticalDumpable("{slow}NotificationShadeWindowControllerImpl", this);
mAuthController = authController;
mUserInteractor = userInteractor;
- mSceneContainerFlags = sceneContainerFlags;
mCommunalInteractor = communalInteractor;
mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
mLockScreenDisplayTimeout = context.getResources()
@@ -289,7 +286,7 @@
mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mLp.privateFlags |= PRIVATE_FLAG_OPTIMIZE_MEASURE;
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
// This prevents the appearance and disappearance of the software keyboard (also known
// as the "IME") from scrolling/panning the window to make room for the keyboard.
//
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 00bc752..907cf5e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -17,7 +17,6 @@
package com.android.systemui.shade;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
-import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON;
import static com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING;
import static com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -104,7 +103,6 @@
private final PulsingGestureListener mPulsingGestureListener;
private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
private final NotificationInsetsController mNotificationInsetsController;
- private final boolean mIsTrackpadCommonEnabled;
private final FeatureFlagsClassic mFeatureFlagsClassic;
private final SysUIKeyEventHandler mSysUIKeyEventHandler;
private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@@ -211,7 +209,6 @@
mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
mNotificationInsetsController = notificationInsetsController;
mGlanceableHubContainerController = glanceableHubContainerController;
- mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON);
mFeatureFlagsClassic = featureFlagsClassic;
mSysUIKeyEventHandler = sysUIKeyEventHandler;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
@@ -643,22 +640,19 @@
if (mTouchActive) {
final long now = mClock.uptimeMillis();
final MotionEvent event;
- if (mIsTrackpadCommonEnabled) {
- event = MotionEvent.obtain(mDownEvent);
- event.setDownTime(now);
- event.setAction(MotionEvent.ACTION_CANCEL);
- event.setLocation(0.0f, 0.0f);
- } else {
- event = MotionEvent.obtain(now, now,
- MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
- event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
- }
+ event = MotionEvent.obtain(mDownEvent);
+ event.setDownTime(now);
+ event.setAction(MotionEvent.ACTION_CANCEL);
+ event.setLocation(0.0f, 0.0f);
Log.w(TAG, "Canceling current touch event (should be very rare)");
mView.dispatchTouchEvent(event);
event.recycle();
mTouchCancelled = true;
}
mAmbientState.setSwipingUp(false);
+ if (MigrateClocksToBlueprint.isEnabled()) {
+ mDragDownHelper.stopDragging();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 648d4b5..a0c9391 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -19,7 +19,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.qs.QSContainerController
import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.data.repository.PrivacyChipRepository
import com.android.systemui.shade.data.repository.PrivacyChipRepositoryImpl
import com.android.systemui.shade.data.repository.ShadeRepository
@@ -50,11 +50,10 @@
@Provides
@SysUISingleton
fun provideBaseShadeInteractor(
- sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>,
sceneContainerOff: Provider<ShadeInteractorLegacyImpl>
): BaseShadeInteractor {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
} else {
sceneContainerOff.get()
@@ -64,11 +63,10 @@
@Provides
@SysUISingleton
fun provideShadeController(
- sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<ShadeControllerSceneImpl>,
sceneContainerOff: Provider<ShadeControllerImpl>
): ShadeController {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
} else {
sceneContainerOff.get()
@@ -78,11 +76,10 @@
@Provides
@SysUISingleton
fun provideShadeAnimationInteractor(
- sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<ShadeAnimationInteractorSceneContainerImpl>,
sceneContainerOff: Provider<ShadeAnimationInteractorLegacyImpl>
): ShadeAnimationInteractor {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
} else {
sceneContainerOff.get()
@@ -92,11 +89,10 @@
@Provides
@SysUISingleton
fun provideShadeBackActionInteractor(
- sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<ShadeBackActionInteractorImpl>,
sceneContainerOff: Provider<NotificationPanelViewController>
): ShadeBackActionInteractor {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
} else {
sceneContainerOff.get()
@@ -106,11 +102,10 @@
@Provides
@SysUISingleton
fun provideShadeLockscreenInteractor(
- sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<ShadeLockscreenInteractorImpl>,
sceneContainerOff: Provider<NotificationPanelViewController>
): ShadeLockscreenInteractor {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
} else {
sceneContainerOff.get()
@@ -120,11 +115,10 @@
@Provides
@SysUISingleton
fun providePanelExpansionInteractor(
- sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<PanelExpansionInteractorImpl>,
sceneContainerOff: Provider<NotificationPanelViewController>
): PanelExpansionInteractor {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
} else {
sceneContainerOff.get()
@@ -134,11 +128,10 @@
@Provides
@SysUISingleton
fun provideQuickSettingsController(
- sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<QuickSettingsControllerSceneImpl>,
sceneContainerOff: Provider<QuickSettingsControllerImpl>,
): QuickSettingsController {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
} else {
sceneContainerOff.get()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index f5dd5e4..bc23778 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -33,7 +33,7 @@
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
@@ -78,15 +78,13 @@
@SysUISingleton
fun providesWindowRootView(
layoutInflater: LayoutInflater,
- sceneContainerFlags: SceneContainerFlags,
viewModelProvider: Provider<SceneContainerViewModel>,
containerConfigProvider: Provider<SceneContainerConfig>,
- flagsProvider: Provider<SceneContainerFlags>,
scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
layoutInsetController: NotificationInsetsController,
sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>,
): WindowRootView {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
checkNoSceneDuplicates(scenesProvider.get())
val sceneWindowRootView =
layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView
@@ -95,7 +93,6 @@
containerConfig = containerConfigProvider.get(),
sharedNotificationContainer =
sceneWindowRootView.requireViewById(R.id.shared_notification_container),
- flags = flagsProvider.get(),
scenes = scenesProvider.get(),
layoutInsetController = layoutInsetController,
sceneDataSourceDelegator = sceneDataSourceDelegator.get(),
@@ -115,9 +112,8 @@
@SysUISingleton
fun providesNotificationShadeWindowView(
root: WindowRootView,
- sceneContainerFlags: SceneContainerFlags,
): NotificationShadeWindowView {
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
return root.requireViewById(R.id.legacy_window_root)
}
return root as NotificationShadeWindowView?
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 72a9c8d..6c76061 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -22,9 +22,11 @@
import android.icu.text.DateFormat
import android.icu.text.DisplayContext
import android.os.UserHandle
+import android.provider.Settings
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyItem
import com.android.systemui.res.R
@@ -54,6 +56,7 @@
constructor(
@Application private val applicationScope: CoroutineScope,
context: Context,
+ private val activityStarter: ActivityStarter,
shadeInteractor: ShadeInteractor,
mobileIconsInteractor: MobileIconsInteractor,
val mobileIconsViewModel: MobileIconsViewModel,
@@ -136,6 +139,14 @@
clockInteractor.launchClockActivity()
}
+ /** Notifies that the shadeCarrierGroup was clicked. */
+ fun onShadeCarrierGroupClicked() {
+ activityStarter.postStartActivityDismissingKeyguard(
+ Intent(Settings.ACTION_WIRELESS_SETTINGS),
+ 0
+ )
+ }
+
private fun updateDateTexts(invalidateFormats: Boolean) {
if (invalidateFormats) {
longerDateFormat.value = getFormatFromPattern(longerPattern)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 980f665a..5b76acb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -37,6 +37,7 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -64,6 +65,7 @@
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
private val sceneInteractor: SceneInteractor,
+ private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
) {
val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
combine(
@@ -106,6 +108,14 @@
val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
+ /**
+ * Amount of X-axis translation to apply to various elements as the unfolded foldable is folded
+ * slightly, in pixels.
+ */
+ fun unfoldTranslationX(isOnStartSide: Boolean): Flow<Float> {
+ return unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide)
+ }
+
/** Notifies that some content in the shade was clicked. */
fun onContentClicked() {
if (!isClickable.value) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 519d719..222b070 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -33,9 +33,9 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.res.R
-import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -813,7 +813,7 @@
initialTouchX = x
isTrackpadReverseScroll =
!naturalScrollingSettingObserver.isNaturalScrollingEnabled &&
- isTrackpadScroll(true, event)
+ isTrackpadScroll(event)
}
MotionEvent.ACTION_MOVE -> {
val h = (if (isTrackpadReverseScroll) -1 else 1) * (y - initialTouchY)
@@ -959,7 +959,7 @@
anim.start()
}
- private fun stopDragging() {
+ fun stopDragging() {
if (startingChild != null) {
cancelChildExpansion(startingChild!!)
startingChild = null
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 e5b6497..594c191 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -35,7 +35,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
import com.android.systemui.power.domain.interactor.PowerInteractor;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeSurface;
@@ -61,8 +61,6 @@
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import javax.inject.Provider;
-
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
@@ -70,6 +68,8 @@
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
+import javax.inject.Provider;
+
/**
* This module provides instances needed to construct {@link CentralSurfacesImpl}. These are moved to
* this separate from {@link CentralSurfacesModule} module so that components that wish to build
@@ -185,10 +185,9 @@
@Provides
@SysUISingleton
static ShadeSurface provideShadeSurface(
- SceneContainerFlags sceneContainerFlags,
Provider<ShadeSurfaceImpl> sceneContainerOn,
Provider<NotificationPanelViewController> sceneContainerOff) {
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
return sceneContainerOn.get();
} else {
return sceneContainerOff.get();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
index 77660eb7..e9306a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.data.repository
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/**
* A repository of currently displayed heads up notifications.
@@ -31,11 +32,13 @@
* True if we are exiting the headsUp pinned mode, and some notifications might still be
* animating out. This is used to keep their view container visible.
*/
- val isHeadsUpAnimatingAway: Flow<Boolean>
+ val isHeadsUpAnimatingAway: StateFlow<Boolean>
/** The heads up row that should be displayed on top. */
val topHeadsUpRow: Flow<HeadsUpRowRepository?>
/** Set of currently active top-level heads up rows to be displayed. */
val activeHeadsUpRows: Flow<Set<HeadsUpRowRepository>>
+
+ fun setHeadsUpAnimatingAway(animatingAway: Boolean)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index 7f94da3..98b52ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -29,7 +29,7 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-class HeadsUpNotificationInteractor @Inject constructor(repository: HeadsUpRepository) {
+class HeadsUpNotificationInteractor @Inject constructor(private val repository: HeadsUpRepository) {
val topHeadsUpRow: Flow<HeadsUpRowKey?> = repository.topHeadsUpRow
@@ -67,6 +67,9 @@
fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor =
HeadsUpRowInteractor(key as HeadsUpRowRepository)
fun elementKeyFor(key: HeadsUpRowKey) = (key as HeadsUpRowRepository).elementKey
+ fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
+ repository.setHeadsUpAnimatingAway(animatingAway)
+ }
}
class HeadsUpRowInteractor(repository: HeadsUpRowRepository)
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 9e0b16c..bdeaabf 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
@@ -1767,7 +1767,8 @@
*/
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
this(context, attrs, context);
- Log.e(TAG, "This constructor shouldn't be called");
+ // NOTE(b/317503801): Always crash when using the insecure constructor.
+ throw new UnsupportedOperationException("Insecure constructor");
}
/**
@@ -2068,6 +2069,8 @@
// Remove views that don't translate
mTranslateableViews.remove(mChildrenContainerStub);
mTranslateableViews.remove(mGutsStub);
+ // We don't handle focus highlight in this view, it's done in background drawable instead
+ setDefaultFocusHighlightEnabled(false);
}
/**
@@ -2804,12 +2807,7 @@
}
public boolean isExpanded(boolean allowOnKeyguard) {
- if (DEBUG) {
- if (!mShowingPublicInitialized && !allowOnKeyguard) {
- Log.d(TAG, "mShowingPublic is not initialized.");
- }
- }
- return !mShowingPublic && (!mOnKeyguard || allowOnKeyguard)
+ return (!shouldShowPublic()) && (!mOnKeyguard || allowOnKeyguard)
&& (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
|| isUserExpanded());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index 5fbcebd..35afda7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -33,6 +33,8 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.util.time.SystemClock;
+import java.util.concurrent.Executor;
+
import javax.inject.Inject;
/**
@@ -58,10 +60,22 @@
}
/**
- * Inflates a new notificationView. This should not be called twice on this object
+ * Inflates a new notificationView asynchronously, calling the {@code listener} on the main
+ * thread when done. This should not be called twice on this object.
*/
public void inflate(Context context, ViewGroup parent, NotificationEntry entry,
RowInflationFinishedListener listener) {
+ inflate(context, parent, entry, null, listener);
+ }
+
+ /**
+ * Inflates a new notificationView asynchronously, calling the {@code listener} on the supplied
+ * {@code listenerExecutor} (or the main thread if null) when done. This should not be called
+ * twice on this object.
+ */
+ @VisibleForTesting
+ public void inflate(Context context, ViewGroup parent, NotificationEntry entry,
+ @Nullable Executor listenerExecutor, RowInflationFinishedListener listener) {
if (TRACE_ORIGIN) {
mInflateOrigin = new Throwable("inflate requested here");
}
@@ -72,7 +86,7 @@
mLogger.logInflateStart(entry);
mInflateStartTimeMs = mSystemClock.elapsedRealtime();
- inflater.inflate(R.layout.status_bar_notification_row, parent, this);
+ inflater.inflate(R.layout.status_bar_notification_row, parent, listenerExecutor, this);
}
private RowAsyncLayoutInflater makeRowInflater(NotificationEntry entry) {
@@ -80,12 +94,12 @@
}
@VisibleForTesting
- static class RowAsyncLayoutInflater implements AsyncLayoutFactory {
+ public static class RowAsyncLayoutInflater implements AsyncLayoutFactory {
private final NotificationEntry mEntry;
private final SystemClock mSystemClock;
private final RowInflaterTaskLogger mLogger;
- RowAsyncLayoutInflater(NotificationEntry entry, SystemClock systemClock,
+ public RowAsyncLayoutInflater(NotificationEntry entry, SystemClock systemClock,
RowInflaterTaskLogger logger) {
mEntry = entry;
mSystemClock = systemClock;
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 82559de..8a1a4f1 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
@@ -111,6 +111,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds;
@@ -450,7 +451,9 @@
private boolean mIsClipped;
private Rect mRequestedClipBounds;
private boolean mInHeadsUpPinnedMode;
- private boolean mHeadsUpAnimatingAway;
+ @VisibleForTesting
+ boolean mHeadsUpAnimatingAway;
+ private Consumer<Boolean> mHeadsUpAnimatingAwayListener;
private int mStatusBarState;
private int mUpcomingStatusBarState;
private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
@@ -4084,7 +4087,14 @@
mSwipeHelper.setIsExpanded(isExpanded);
if (changed) {
mWillExpand = false;
- if (!mIsExpanded) {
+ if (mIsExpanded) {
+ // Resetting headsUpAnimatingAway on Shade expansion avoids delays caused by
+ // waiting for all child animations to finish.
+ // TODO(b/328390331) Do we need to reset this on QS expanded as well?
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ setHeadsUpAnimatingAway(false);
+ }
+ } else {
mGroupExpansionManager.collapseGroups();
mExpandHelper.cancelImmediately();
if (!mIsExpansionChanging) {
@@ -4190,6 +4200,9 @@
void onChildAnimationFinished() {
setAnimationRunning(false);
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ setHeadsUpAnimatingAway(false);
+ }
requestChildrenUpdate();
runAnimationFinishedRunnables();
clearTransient();
@@ -4509,18 +4522,18 @@
mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
if (areNotificationsHiddenInShade) {
- updateEmptyShadeView(R.string.dnd_suppressing_shade_text, 0, 0);
+ updateEmptyShadeViewResources(R.string.dnd_suppressing_shade_text, 0, 0);
} else if (hasFilteredOutSeenNotifications) {
- updateEmptyShadeView(
+ updateEmptyShadeViewResources(
R.string.no_unseen_notif_text,
R.string.unlock_to_see_notif_text,
R.drawable.ic_friction_lock_closed);
} else {
- updateEmptyShadeView(R.string.empty_shade_text, 0, 0);
+ updateEmptyShadeViewResources(R.string.empty_shade_text, 0, 0);
}
}
- private void updateEmptyShadeView(
+ private void updateEmptyShadeViewResources(
@StringRes int newTextRes,
@StringRes int newFooterTextRes,
@DrawableRes int newFooterIconRes) {
@@ -4717,6 +4730,7 @@
}
public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
generateHeadsUpAnimation(row, isHeadsUp);
}
@@ -4750,6 +4764,9 @@
mNeedsAnimation = true;
if (!mIsExpanded && !mWillExpand && !isHeadsUp) {
row.setHeadsUpAnimatingAway(true);
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ setHeadsUpAnimatingAway(true);
+ }
}
requestChildrenUpdate();
}
@@ -4939,11 +4956,28 @@
updateClipping();
}
+ /** TODO(b/328390331) make this private, when {@link NotificationsHeadsUpRefactor} is removed */
public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
- mHeadsUpAnimatingAway = headsUpAnimatingAway;
+ if (mHeadsUpAnimatingAway != headsUpAnimatingAway) {
+ mHeadsUpAnimatingAway = headsUpAnimatingAway;
+ if (mHeadsUpAnimatingAwayListener != null) {
+ mHeadsUpAnimatingAwayListener.accept(headsUpAnimatingAway);
+ }
+ }
updateClipping();
}
+ /**
+ * Sets a listener to be notified about the heads up disappear animation state changes. If there
+ * are overlapping animations, it will receive updates when the first disappar animation has
+ * started, and when the last has finished.
+ *
+ * @param headsUpAnimatingAwayListener to be notified about disappear animation state changes.
+ */
+ public void setHeadsUpAnimatingAwayListener(
+ Consumer<Boolean> headsUpAnimatingAwayListener) {
+ mHeadsUpAnimatingAwayListener = headsUpAnimatingAwayListener;
+ }
@VisibleForTesting
public void setStatusBarState(int statusBarState) {
mStatusBarState = statusBarState;
@@ -5338,7 +5372,8 @@
mActivityStarter.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP);
});
setEmptyShadeView(view);
- updateEmptyShadeView(
+ view.setVisible(oldView != null && oldView.isVisible(), /* animate = */ false);
+ updateEmptyShadeViewResources(
oldView == null ? R.string.empty_shade_text : oldView.getTextResource(),
oldView == null ? 0 : oldView.getFooterTextResource(),
oldView == null ? 0 : oldView.getFooterIconResource());
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 06479e5..5e719b1 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
@@ -68,8 +68,6 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
import com.android.systemui.keyguard.shared.model.KeyguardState;
@@ -210,11 +208,9 @@
@Nullable
private Boolean mHistoryEnabled;
private int mBarState;
- private boolean mIsBouncerShowingFromCentralSurfaces;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private boolean mIsInTransitionToAod = false;
- private final FeatureFlagsClassic mFeatureFlags;
private final NotificationTargetsHelper mNotificationTargetsHelper;
private final SecureSettings mSecureSettings;
private final NotificationDismissibilityProvider mDismissibilityProvider;
@@ -745,7 +741,6 @@
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
NotificationStackSizeCalculator notificationStackSizeCalculator,
- FeatureFlagsClassic featureFlags,
NotificationTargetsHelper notificationTargetsHelper,
SecureSettings secureSettings,
NotificationDismissibilityProvider dismissibilityProvider,
@@ -793,7 +788,6 @@
mSeenNotificationsInteractor = seenNotificationsInteractor;
mShadeController = shadeController;
mWindowRootView = windowRootView;
- mFeatureFlags = featureFlags;
mNotificationTargetsHelper = notificationTargetsHelper;
mSecureSettings = secureSettings;
mDismissibilityProvider = dismissibilityProvider;
@@ -1391,14 +1385,6 @@
}
/**
- * Sets whether the bouncer is currently showing. Should only be called from
- * {@link CentralSurfaces}.
- */
- public void setBouncerShowingFromCentralSurfaces(boolean bouncerShowing) {
- mIsBouncerShowingFromCentralSurfaces = bouncerShowing;
- }
-
- /**
* Set the visibility of the view, and propagate it to specific children.
*
* @param visible either the view is visible or not.
@@ -1435,7 +1421,7 @@
// For more details, see: b/228790482
&& !mIsInTransitionToAod
// Don't show any notification content if the bouncer is showing. See b/267060171.
- && !isBouncerShowing();
+ && !mPrimaryBouncerInteractor.isBouncerShowing();
mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade());
@@ -1443,24 +1429,6 @@
}
/**
- * Returns whether the bouncer is currently showing.
- *
- * There's a possible timing difference between when CentralSurfaces marks the bouncer as not
- * showing and when PrimaryBouncerInteractor marks the bouncer as not showing. (CentralSurfaces
- * appears to mark the bouncer as showing for 10-200ms longer than PrimaryBouncerInteractor.)
- *
- * This timing difference could be load bearing, which is why we have a feature flag protecting
- * where we fetch the value from. This flag is intended to be short-lived.
- */
- private boolean isBouncerShowing() {
- if (mFeatureFlags.isEnabled(Flags.USE_REPOS_FOR_BOUNCER_SHOWING)) {
- return mPrimaryBouncerInteractor.isBouncerShowing();
- } else {
- return mIsBouncerShowingFromCentralSurfaces;
- }
- }
-
- /**
* Update the importantForAccessibility of NotificationStackScrollLayout.
* <p>
* We want the NSSL to be unimportant for accessibility when there's no
@@ -1482,6 +1450,7 @@
}
public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
mView.setHeadsUpAnimatingAway(headsUpAnimatingAway);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 741102b..cf5366b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -29,7 +29,6 @@
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
@@ -49,7 +48,6 @@
class SharedNotificationContainerBinder
@Inject
constructor(
- private val sceneContainerFlags: SceneContainerFlags,
private val controller: NotificationStackScrollLayoutController,
private val notificationStackSizeCalculator: NotificationStackSizeCalculator,
private val notificationScrollViewBinder: NotificationScrollViewBinder,
@@ -130,7 +128,7 @@
.collect { controller.setMaxDisplayedNotifications(it) }
}
- if (!sceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
launch {
viewModel.bounds.collect {
val animate =
@@ -166,7 +164,7 @@
}
}
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
disposables += notificationScrollViewBinder.bindWhileAttached()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 5ab5857..3a89630 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
@@ -31,6 +32,7 @@
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor
import com.android.systemui.statusbar.policy.domain.interactor.UserSetupInteractor
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.util.kotlin.FlowDumperImpl
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.ui.AnimatableEvent
import com.android.systemui.util.ui.AnimatedValue
@@ -64,7 +66,8 @@
userSetupInteractor: UserSetupInteractor,
zenModeInteractor: ZenModeInteractor,
@Background bgDispatcher: CoroutineDispatcher,
-) {
+ dumpManager: DumpManager,
+) : FlowDumperImpl(dumpManager) {
/**
* We want the NSSL to be unimportant for accessibility when there are no notifications in it
* while the device is on lock screen, to avoid an unlabelled NSSL view in TalkBack. Otherwise,
@@ -81,8 +84,9 @@
) { hasNotifications, isShowingOnLockscreen ->
hasNotifications || !isShowingOnLockscreen
}
- .flowOn(bgDispatcher)
.distinctUntilChanged()
+ .dumpWhileCollecting("isImportantForAccessibility")
+ .flowOn(bgDispatcher)
}
}
@@ -105,8 +109,9 @@
else -> true
}
}
- .flowOn(bgDispatcher)
.distinctUntilChanged()
+ .dumpWhileCollecting("shouldShowEmptyShadeView")
+ .flowOn(bgDispatcher)
}
}
@@ -125,8 +130,9 @@
// the footer to be counted as part of the shade for measurements.
shadeInteractor.shadeExpansion
.map { it == 0f }
- .flowOn(bgDispatcher)
.distinctUntilChanged()
+ .dumpWhileCollecting("shouldHideFooterView")
+ .flowOn(bgDispatcher)
}
}
@@ -173,7 +179,6 @@
else -> VisibilityChange.APPEAR_WITH_ANIMATION
}
}
- .flowOn(bgDispatcher)
.distinctUntilChanged(
// Equivalent unless visibility changes
areEquivalent = { a: VisibilityChange, b: VisibilityChange ->
@@ -199,6 +204,8 @@
AnimatableEvent(visibilityChange.visible, shouldAnimate)
}
.toAnimatedValueFlow()
+ .dumpWhileCollecting("shouldIncludeFooterView")
+ .flowOn(bgDispatcher)
}
}
@@ -213,7 +220,9 @@
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
- zenModeInteractor.areNotificationsHiddenInShade
+ zenModeInteractor.areNotificationsHiddenInShade.dumpWhileCollecting(
+ "areNotificationsHiddenInShade"
+ )
}
}
@@ -222,7 +231,9 @@
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
- seenNotificationsInteractor.hasFilteredOutSeenNotifications
+ seenNotificationsInteractor.hasFilteredOutSeenNotifications.dumpWhileCollecting(
+ "hasFilteredOutSeenNotifications"
+ )
}
}
@@ -230,7 +241,9 @@
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
- activeNotificationsInteractor.hasClearableAlertingNotifications
+ activeNotificationsInteractor.hasClearableAlertingNotifications.dumpWhileCollecting(
+ "hasClearableAlertingNotifications"
+ )
}
}
@@ -238,7 +251,9 @@
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
- activeNotificationsInteractor.hasNonClearableSilentNotifications
+ activeNotificationsInteractor.hasNonClearableSilentNotifications.dumpWhileCollecting(
+ "hasNonClearableSilentNotifications"
+ )
}
}
@@ -246,7 +261,7 @@
if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(null)
} else {
- headsUpNotificationInteractor.topHeadsUpRow
+ headsUpNotificationInteractor.topHeadsUpRow.dumpWhileCollecting("topHeadsUpRow")
}
}
@@ -254,15 +269,20 @@
if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(emptySet())
} else {
- headsUpNotificationInteractor.pinnedHeadsUpRows
+ headsUpNotificationInteractor.pinnedHeadsUpRows.dumpWhileCollecting("pinnedHeadsUpRows")
}
}
val headsUpAnimationsEnabled: Flow<Boolean> by lazy {
- combine(keyguardInteractor.isKeyguardShowing, shadeInteractor.isShadeFullyExpanded) {
- (isKeyguardShowing, isShadeFullyExpanded) ->
- // TODO(b/325936094) use isShadeFullyCollapsed instead
- !isKeyguardShowing && !isShadeFullyExpanded
+ if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ combine(keyguardInteractor.isKeyguardShowing, shadeInteractor.isShadeFullyExpanded) {
+ (isKeyguardShowing, isShadeFullyExpanded) ->
+ // TODO(b/325936094) use isShadeFullyCollapsed instead
+ !isKeyguardShowing && !isShadeFullyExpanded
+ }
+ .dumpWhileCollecting("headsUpAnimationsEnabled")
}
}
@@ -270,7 +290,7 @@
if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
- headsUpNotificationInteractor.hasPinnedRows
+ headsUpNotificationInteractor.hasPinnedRows.dumpWhileCollecting("hasPinnedHeadsUpRow")
}
}
@@ -279,4 +299,8 @@
HeadsUpRowViewModel(headsUpNotificationInteractor.headsUpRow(key))
fun elementKeyFor(key: HeadsUpRowKey): Any = headsUpNotificationInteractor.elementKeyFor(key)
+
+ fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
+ headsUpNotificationInteractor.setHeadsUpAnimatingAway(animatingAway)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index b284179..ca19da5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -22,7 +22,7 @@
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -43,7 +43,6 @@
dumpManager: DumpManager,
private val interactor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
- flags: SceneContainerFlags,
featureFlags: FeatureFlagsClassic,
private val keyguardInteractor: KeyguardInteractor,
) : FlowDumperImpl(dumpManager) {
@@ -51,7 +50,7 @@
val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES)
/** DEBUG: whether the debug logging should be output. */
- val isDebugLoggingEnabled: Boolean = flags.isEnabled()
+ val isDebugLoggingEnabled: Boolean = SceneContainerFlag.isEnabled
/** Notifies that the bounds of the notification scrim have changed. */
fun onScrimBoundsChanged(bounds: ShadeScrimBounds?) {
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 5099682..37bbbd0 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
@@ -66,6 +66,7 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import com.android.systemui.util.kotlin.BooleanFlowOperators.and
import com.android.systemui.util.kotlin.BooleanFlowOperators.or
import com.android.systemui.util.kotlin.FlowDumperImpl
@@ -80,6 +81,7 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
@@ -128,6 +130,7 @@
private val primaryBouncerToLockscreenTransitionViewModel:
PrimaryBouncerToLockscreenTransitionViewModel,
private val aodBurnInViewModel: AodBurnInViewModel,
+ unfoldTransitionInteractor: UnfoldTransitionInteractor,
) : FlowDumperImpl(dumpManager) {
private val statesForConstrainedNotifications: Set<KeyguardState> =
setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
@@ -577,14 +580,20 @@
.dumpWhileCollecting("translationY")
}
- /**
- * The container may need to be translated in the x direction as the keyguard fades out, such as
- * when swiping open the glanceable hub from the lockscreen.
- */
+ /** Horizontal translation to apply to the container. */
val translationX: Flow<Float> =
merge(
+ // The container may need to be translated along the X axis as the keyguard fades
+ // out, such as when swiping open the glanceable hub from the lockscreen.
lockscreenToGlanceableHubTransitionViewModel.notificationTranslationX,
glanceableHubToLockscreenTransitionViewModel.notificationTranslationX,
+ if (SceneContainerFlag.isEnabled) {
+ // The container may need to be translated along the X axis as the unfolded
+ // foldable is folded slightly.
+ unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false)
+ } else {
+ emptyFlow()
+ }
)
.dumpWhileCollecting("translationX")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
index cb360fe..6acb12a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
@@ -17,18 +17,18 @@
package com.android.systemui.statusbar.notification.ui.viewbinder
import android.util.Log
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
-private const val TAG = "HunBinder"
-private val DEBUG = true // Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG)
-
class HeadsUpNotificationViewBinder
@Inject
constructor(private val viewModel: NotificationListViewModel) {
@@ -39,10 +39,6 @@
viewModel.pinnedHeadsUpRows
.sample(viewModel.headsUpAnimationsEnabled, ::Pair)
.collect { (newKeys, animationsEnabled) ->
- if (DEBUG) {
- Log.d(TAG, "update:$newKeys")
- }
-
val added = newKeys - previousKeys
val removed = previousKeys - newKeys
previousKeys = newKeys
@@ -70,9 +66,19 @@
launch {
viewModel.hasPinnedHeadsUpRow.collect { parentView.setInHeadsUpPinnedMode(it) }
}
+ launch {
+ parentView.isHeadsUpAnimatingAway.collect { viewModel.setHeadsUpAnimatingAway(it) }
+ }
}
private fun obtainView(key: HeadsUpRowKey): ExpandableNotificationRow {
return viewModel.elementKeyFor(key) as ExpandableNotificationRow
}
}
+
+private val NotificationStackScrollLayout.isHeadsUpAnimatingAway: Flow<Boolean>
+ get() =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ setHeadsUpAnimatingAwayListener { animatingAway -> trySend(animatingAway) }
+ awaitClose { setHeadsUpAnimatingAwayListener(null) }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 9268d16..6546db9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -24,7 +24,6 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
@@ -37,16 +36,10 @@
constructor(
private val statusBarStateController: SysuiStatusBarStateController,
@Main private val mainExecutor: DelayableExecutor,
- legacyActivityStarter: Lazy<LegacyActivityStarterInternalImpl>,
- activityStarterInternal: Lazy<ActivityStarterInternalImpl>,
+ legacyActivityStarter: Lazy<LegacyActivityStarterInternalImpl>
) : ActivityStarter {
- private val activityStarterInternal: ActivityStarterInternal =
- if (SceneContainerFlag.isEnabled) {
- activityStarterInternal.get()
- } else {
- legacyActivityStarter.get()
- }
+ private val activityStarterInternal: ActivityStarterInternal = legacyActivityStarter.get()
override fun startPendingIntentDismissingKeyguard(intent: PendingIntent) {
activityStarterInternal.startPendingIntentDismissingKeyguard(intent = intent)
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 1d6b744..e9aa7aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -164,7 +164,6 @@
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -592,9 +591,6 @@
private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
(extractor, which) -> updateTheme();
-
- private final SceneContainerFlags mSceneContainerFlags;
-
private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor;
/**
@@ -708,7 +704,6 @@
UserTracker userTracker,
Provider<FingerprintManager> fingerprintManager,
ActivityStarter activityStarter,
- SceneContainerFlags sceneContainerFlags,
BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor
) {
mContext = context;
@@ -804,7 +799,6 @@
mUserTracker = userTracker;
mFingerprintManager = fingerprintManager;
mActivityStarter = activityStarter;
- mSceneContainerFlags = sceneContainerFlags;
mBrightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
@@ -1088,7 +1082,7 @@
mJavaAdapter.alwaysCollectFlow(
mCommunalInteractor.isIdleOnCommunal(),
mIdleOnCommunalConsumer);
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
mJavaAdapter.alwaysCollectFlow(
mBrightnessMirrorShowingInteractor.isShowing(),
this::setBrightnessMirrorShowing
@@ -1277,7 +1271,7 @@
// Set up the quick settings tile panel
final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame);
- if (container != null && !mSceneContainerFlags.isEnabled()) {
+ if (container != null && !SceneContainerFlag.isEnabled()) {
FragmentHostManager fragmentHostManager =
mFragmentService.getFragmentHostManager(container);
ExtensionFragmentListener.attachExtensonToFragment(
@@ -1545,8 +1539,7 @@
mShadeSurface,
mShadeExpansionStateManager,
mBiometricUnlockController,
- mStackScroller,
- mKeyguardBypassController);
+ mStackScroller);
mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
mKeyguardIndicationController
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
@@ -2423,7 +2416,6 @@
mBouncerShowing = bouncerShowing;
mKeyguardBypassController.setBouncerShowing(bouncerShowing);
mPulseExpansionHandler.setBouncerShowing(bouncerShowing);
- mStackScrollerController.setBouncerShowingFromCentralSurfaces(bouncerShowing);
setBouncerShowingForStatusBarComponents(bouncerShowing);
mStatusBarHideIconsForBouncerManager.setBouncerShowingAndTriggerUpdate(bouncerShowing);
mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 0ddf37d..8ec8d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -168,7 +168,10 @@
updateResources();
}
});
- javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded);
+ if (!NotificationsHeadsUpRefactor.isEnabled()) {
+ javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
+ this::onShadeOrQsExpanded);
+ }
}
public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -262,6 +265,7 @@
}
private void onShadeOrQsExpanded(Boolean isExpanded) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
if (isExpanded != mIsExpanded) {
mIsExpanded = isExpanded;
if (isExpanded) {
@@ -500,7 +504,7 @@
@Override
@NonNull
- public Flow<Boolean> isHeadsUpAnimatingAway() {
+ public StateFlow<Boolean> isHeadsUpAnimatingAway() {
return mHeadsUpAnimatingAway;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index a3d316b..a8941bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.phone
import android.annotation.IntDef
-import android.content.Context
import android.content.pm.PackageManager
+import android.content.res.Resources
import android.hardware.biometrics.BiometricSourceType
import android.provider.Settings
import androidx.annotation.VisibleForTesting
@@ -26,7 +26,10 @@
import com.android.systemui.Dumpable
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.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.ShadeRepository
@@ -46,12 +49,20 @@
import javax.inject.Inject
@SysUISingleton
-open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassController {
+class KeyguardBypassController @Inject constructor(
+ @Main resources: Resources,
+ packageManager: PackageManager,
+ @Application applicationScope: CoroutineScope,
+ tunerService: TunerService,
+ private val statusBarStateController: StatusBarStateController,
+ lockscreenUserManager: NotificationLockscreenUserManager,
+ private val keyguardStateController: KeyguardStateController,
+ private val shadeRepository: ShadeRepository,
+ devicePostureController: DevicePostureController,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ dumpManager: DumpManager
+) : Dumpable, StackScrollAlgorithm.BypassController {
- private val mKeyguardStateController: KeyguardStateController
- private val statusBarStateController: StatusBarStateController
- private val shadeRepository: ShadeRepository
- private val devicePostureController: DevicePostureController
@BypassOverride private val bypassOverride: Int
private var hasFaceFeature: Boolean
@DevicePostureInt private val configFaceAuthSupportedPosture: Int
@@ -99,7 +110,7 @@
FACE_UNLOCK_BYPASS_NEVER -> false
else -> field
}
- return enabled && mKeyguardStateController.isFaceEnrolledAndEnabled &&
+ return enabled && keyguardStateController.isFaceEnrolledAndEnabled &&
isPostureAllowedForFaceAuth()
}
private set(value) {
@@ -108,70 +119,44 @@
}
var bouncerShowing: Boolean = false
- var altBouncerShowing: Boolean = false
var launchingAffordance: Boolean = false
var qsExpanded = false
- @Inject
- constructor(
- context: Context,
- @Application applicationScope: CoroutineScope,
- tunerService: TunerService,
- statusBarStateController: StatusBarStateController,
- lockscreenUserManager: NotificationLockscreenUserManager,
- keyguardStateController: KeyguardStateController,
- shadeRepository: ShadeRepository,
- devicePostureController: DevicePostureController,
- dumpManager: DumpManager
- ) {
- this.mKeyguardStateController = keyguardStateController
- this.statusBarStateController = statusBarStateController
- this.shadeRepository = shadeRepository
- this.devicePostureController = devicePostureController
-
- bypassOverride = context.resources.getInteger(R.integer.config_face_unlock_bypass_override)
+ init {
+ bypassOverride = resources.getInteger(R.integer.config_face_unlock_bypass_override)
configFaceAuthSupportedPosture =
- context.resources.getInteger(R.integer.config_face_auth_supported_posture)
-
- hasFaceFeature = context.packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)
- if (!hasFaceFeature) {
- return
- }
-
-
- if (configFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) {
- devicePostureController.addCallback { posture ->
- if (postureState != posture) {
- postureState = posture
- notifyListeners()
+ resources.getInteger(R.integer.config_face_auth_supported_posture)
+ hasFaceFeature = packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)
+ if (hasFaceFeature) {
+ if (configFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) {
+ devicePostureController.addCallback { posture ->
+ if (postureState != posture) {
+ postureState = posture
+ notifyListeners()
+ }
}
}
- }
-
- dumpManager.registerNormalDumpable("KeyguardBypassController", this)
- statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
- override fun onStateChanged(newState: Int) {
- if (newState != StatusBarState.KEYGUARD) {
- pendingUnlock = null
- }
- }
- })
-
- listenForQsExpandedChange(applicationScope)
-
- val dismissByDefault = if (context.resources.getBoolean(
- com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
-
- tunerService.addTunable({ key, _ ->
- bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
- }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
-
- lockscreenUserManager.addUserChangedListener(
- object : NotificationLockscreenUserManager.UserChangedListener {
- override fun onUserChanged(userId: Int) {
- pendingUnlock = null
+ dumpManager.registerNormalDumpable("KeyguardBypassController", this)
+ statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
+ override fun onStateChanged(newState: Int) {
+ if (newState != StatusBarState.KEYGUARD) {
+ pendingUnlock = null
+ }
}
})
+ listenForQsExpandedChange(applicationScope)
+ val dismissByDefault = if (resources.getBoolean(
+ com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
+ tunerService.addTunable({ key, _ ->
+ bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
+ }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
+ lockscreenUserManager.addUserChangedListener(
+ object : NotificationLockscreenUserManager.UserChangedListener {
+ override fun onUserChanged(userId: Int) {
+ pendingUnlock = null
+ }
+ })
+ }
}
@VisibleForTesting
@@ -228,7 +213,8 @@
if (bypassEnabled) {
return when {
bouncerShowing -> true
- altBouncerShowing -> true
+ keyguardTransitionInteractor.getCurrentState() == KeyguardState.ALTERNATE_BOUNCER ->
+ true
statusBarStateController.state != StatusBarState.KEYGUARD -> false
launchingAffordance -> false
isPulseExpanding || qsExpanded -> false
@@ -260,7 +246,8 @@
pw.println(" bypassEnabled: $bypassEnabled")
pw.println(" canBypass: ${canBypass()}")
pw.println(" bouncerShowing: $bouncerShowing")
- pw.println(" altBouncerShowing: $altBouncerShowing")
+ pw.println(" altBouncerShowing:" +
+ " ${keyguardTransitionInteractor.getCurrentState() == KeyguardState.ALTERNATE_BOUNCER}")
pw.println(" isPulseExpanding: $isPulseExpanding")
pw.println(" launchingAffordance: $launchingAffordance")
pw.println(" qSExpanded: $qsExpanded")
@@ -273,7 +260,7 @@
val start = listeners.isEmpty()
listeners.add(listener)
if (start) {
- mKeyguardStateController.addCallback(faceAuthEnabledChangedCallback)
+ keyguardStateController.addCallback(faceAuthEnabledChangedCallback)
}
}
@@ -284,7 +271,7 @@
fun unregisterOnBypassStateChangedListener(listener: OnBypassStateChangedListener) {
listeners.remove(listener)
if (listeners.isEmpty()) {
- mKeyguardStateController.removeCallback(faceAuthEnabledChangedCallback)
+ keyguardStateController.removeCallback(faceAuthEnabledChangedCallback)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 38b3718..3343779 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -658,6 +658,7 @@
updateForHeadsUp(true);
}
+ // TODO(b/328579846) bind the StatusBar visibility to heads up events
void updateForHeadsUp(boolean animate) {
boolean showingKeyguardHeadsUp =
isKeyguardShowing() && mShadeViewStateProvider.shouldHeadsUpBeVisible();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index ebaeb39..68d54e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -131,7 +131,7 @@
val runnable = Runnable {
assistManagerLazy.get().hideAssist()
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
intent.addFlags(flags)
val result = intArrayOf(ActivityManager.START_CANCELED)
activityTransitionAnimator.startIntentWithAnimation(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 92fd90a..5206e46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -24,10 +24,10 @@
import android.view.ViewGroup
import android.view.ViewTreeObserver
import com.android.systemui.Gefingerpoken
-import com.android.systemui.res.R
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeLogger
@@ -65,7 +65,6 @@
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
private val userChipViewModel: StatusBarUserChipViewModel,
private val viewUtil: ViewUtil,
- private val sceneContainerFlags: SceneContainerFlags,
private val configurationController: ConfigurationController,
private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
) : ViewController<PhoneStatusBarView>(view) {
@@ -205,7 +204,7 @@
// If scene framework is enabled, route the touch to it and
// ignore the rest of the gesture.
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
windowRootView.get().dispatchTouchEvent(event)
return true
}
@@ -267,7 +266,6 @@
@Named(UNFOLD_STATUS_BAR)
private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
private val featureFlags: FeatureFlags,
- private val sceneContainerFlags: SceneContainerFlags,
private val userChipViewModel: StatusBarUserChipViewModel,
private val centralSurfaces: CentralSurfaces,
private val statusBarWindowStateController: StatusBarWindowStateController,
@@ -301,7 +299,6 @@
statusBarMoveFromCenterAnimationController,
userChipViewModel,
viewUtil,
- sceneContainerFlags,
configurationController,
statusOverlayHoverListenerFactory,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 87139ac..da5877b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -24,6 +24,7 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -98,15 +99,21 @@
// we need to keep the panel open artificially, let's wait until the
//animation
// is finished.
- mHeadsUpManager.setHeadsUpAnimatingAway(true);
+ setHeadsAnimatingAway(true);
mNsslController.runAfterAnimationFinished(() -> {
if (!mHeadsUpManager.hasPinnedHeadsUp()) {
mNotificationShadeWindowController.setHeadsUpShowing(false);
- mHeadsUpManager.setHeadsUpAnimatingAway(false);
+ setHeadsAnimatingAway(false);
}
mNotificationRemoteInputManager.onPanelCollapsed();
});
}
}
}
+
+ private void setHeadsAnimatingAway(boolean headsUpAnimatingAway) {
+ if (!NotificationsHeadsUpRefactor.isEnabled()) {
+ mHeadsUpManager.setHeadsUpAnimatingAway(headsUpAnimatingAway);
+ }
+ }
}
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 dd7d282..a141b53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -332,7 +332,6 @@
private final LatencyTracker mLatencyTracker;
private final KeyguardSecurityModel mKeyguardSecurityModel;
private final SelectedUserInteractor mSelectedUserInteractor;
- @Nullable private KeyguardBypassController mBypassController;
@Nullable private OccludingAppBiometricUI mOccludingAppBiometricUI;
@Nullable private TaskbarDelegate mTaskbarDelegate;
@@ -440,8 +439,7 @@
ShadeLockscreenInteractor shadeLockscreenInteractor,
ShadeExpansionStateManager shadeExpansionStateManager,
BiometricUnlockController biometricUnlockController,
- View notificationContainer,
- KeyguardBypassController bypassController) {
+ View notificationContainer) {
mCentralSurfaces = centralSurfaces;
mBiometricUnlockController = biometricUnlockController;
@@ -452,7 +450,6 @@
shadeExpansionStateManager.addExpansionListener(this);
onPanelExpansionChanged(currentState);
}
- mBypassController = bypassController;
mNotificationContainer = notificationContainer;
if (!DeviceEntryUdfpsRefactor.isEnabled()) {
mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create(
@@ -973,7 +970,6 @@
mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer);
mKeyguardMessageAreaController.setMessage("");
}
- mBypassController.setAltBouncerShowing(isShowingAlternateBouncer);
mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer);
if (updateScrim) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index 8e8de46..d1189e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -37,7 +37,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -91,7 +91,6 @@
ShadeInteractor shadeInteractor,
Provider<SceneInteractor> sceneInteractor,
JavaAdapter javaAdapter,
- SceneContainerFlags sceneContainerFlags,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
PrimaryBouncerInteractor primaryBouncerInteractor,
AlternateBouncerInteractor alternateBouncerInteractor
@@ -130,7 +129,7 @@
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
javaAdapter.alwaysCollectFlow(
sceneInteractor.get().isVisible(),
this::onSceneContainerVisibilityChanged);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
index 541ac48..31c2134 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
@@ -15,36 +15,55 @@
*/
package com.android.systemui.statusbar.phone
+import android.annotation.StyleRes
import android.app.Dialog
import android.content.Context
-import android.content.res.Configuration
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.Gravity
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import android.view.WindowInsets
-import android.view.WindowInsets.Type.InsetsType
-import android.view.WindowInsetsAnimation
import android.view.WindowManager.LayoutParams.MATCH_PARENT
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL
+import androidx.activity.ComponentDialog
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.android.systemui.statusbar.policy.onConfigChanged
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
/** A dialog shown as a bottom sheet. */
-open class SystemUIBottomSheetDialog(
+class SystemUIBottomSheetDialog
+@VisibleForTesting
+constructor(
context: Context,
- private val configurationController: ConfigurationController? = null,
- theme: Int = R.style.Theme_SystemUI_Dialog
-) : Dialog(context, theme) {
+ private val coroutineScope: CoroutineScope,
+ private val configurationController: ConfigurationController,
+ private val delegate: DialogDelegate<in Dialog>,
+ private val windowLayout: WindowLayout,
+ theme: Int,
+) : ComponentDialog(context, theme) {
+
+ private var job: Job? = null
+
override fun onCreate(savedInstanceState: Bundle?) {
+ delegate.beforeCreate(this, savedInstanceState)
super.onCreate(savedInstanceState)
setupWindow()
- setupEdgeToEdge()
setCanceledOnTouchOutside(true)
+ delegate.onCreate(this, savedInstanceState)
}
private fun setupWindow() {
@@ -62,60 +81,84 @@
}
}
- private fun setupEdgeToEdge() {
- val edgeToEdgeHorizontally =
- context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
- val width = if (edgeToEdgeHorizontally) MATCH_PARENT else WRAP_CONTENT
- val height = WRAP_CONTENT
- window?.setLayout(width, height)
- }
-
override fun onStart() {
super.onStart()
- configurationController?.addCallback(onConfigChanged)
- window?.decorView?.setWindowInsetsAnimationCallback(insetsAnimationCallback)
+ job?.cancel()
+ job =
+ coroutineScope.launch {
+ windowLayout
+ .calculate()
+ .onEach { window?.apply { setLayout(it.width, it.height) } }
+ .launchIn(this)
+ configurationController.onConfigChanged
+ .onEach { delegate.onConfigurationChanged(this@SystemUIBottomSheetDialog, it) }
+ .launchIn(this)
+ }
+ delegate.onStart(this)
}
override fun onStop() {
+ job?.cancel()
+ delegate.onStop(this)
super.onStop()
- configurationController?.removeCallback(onConfigChanged)
- window?.decorView?.setWindowInsetsAnimationCallback(null)
}
- /** Called after any insets change. */
- open fun onInsetsChanged(@InsetsType changedTypes: Int, insets: WindowInsets) {}
+ override fun onWindowFocusChanged(hasFocus: Boolean) {
+ super.onWindowFocusChanged(hasFocus)
+ delegate.onWindowFocusChanged(this, hasFocus)
+ }
- /** Can be overridden by subclasses to receive config changed events. */
- open fun onConfigurationChanged() {}
+ class Factory
+ @Inject
+ constructor(
+ @Application private val context: Context,
+ @Application private val coroutineScope: CoroutineScope,
+ private val defaultWindowLayout: Lazy<WindowLayout.LimitedEdgeToEdge>,
+ private val configurationController: ConfigurationController,
+ ) {
- private val insetsAnimationCallback =
- object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+ fun create(
+ delegate: DialogDelegate<in Dialog>,
+ windowLayout: WindowLayout = defaultWindowLayout.get(),
+ @StyleRes theme: Int = R.style.Theme_SystemUI_Dialog,
+ ): SystemUIBottomSheetDialog =
+ SystemUIBottomSheetDialog(
+ context = context,
+ configurationController = configurationController,
+ coroutineScope = coroutineScope,
+ delegate = delegate,
+ windowLayout = windowLayout,
+ theme = theme,
+ )
+ }
- private var lastInsets: WindowInsets? = null
+ /** [SystemUIBottomSheetDialog] uses this to determine the [android.view.Window] layout. */
+ interface WindowLayout {
- override fun onEnd(animation: WindowInsetsAnimation) {
- lastInsets?.let { onInsetsChanged(animation.typeMask, it) }
- }
+ /** Returns a [Layout] to apply to [android.view.Window.setLayout]. */
+ fun calculate(): Flow<Layout>
- override fun onProgress(
- insets: WindowInsets,
- animations: MutableList<WindowInsetsAnimation>,
- ): WindowInsets {
- lastInsets = insets
- onInsetsChanged(changedTypes = allAnimationMasks(animations), insets)
- return insets
- }
+ /** Edge to edge with which doesn't fill the whole space on the large screen. */
+ class LimitedEdgeToEdge
+ @Inject
+ constructor(
+ @Application private val context: Context,
+ private val configurationController: ConfigurationController,
+ ) : WindowLayout {
- private fun allAnimationMasks(animations: List<WindowInsetsAnimation>): Int =
- animations.fold(0) { acc: Int, it -> acc or it.typeMask }
- }
+ override fun calculate(): Flow<Layout> {
+ return configurationController.onConfigChanged
+ .onStart { emit(context.resources.configuration) }
+ .map {
+ val edgeToEdgeHorizontally =
+ context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
+ val width = if (edgeToEdgeHorizontally) MATCH_PARENT else WRAP_CONTENT
- private val onConfigChanged =
- object : ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration?) {
- super.onConfigChanged(newConfig)
- setupEdgeToEdge()
- onConfigurationChanged()
+ Layout(width, WRAP_CONTENT)
+ }
}
}
+
+ data class Layout(val width: Int, val height: Int)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 2b90e64..6e24412 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -19,8 +19,6 @@
import android.net.wifi.WifiManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.table.TableLogBuffer
@@ -52,12 +50,9 @@
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.DisabledWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryViaTrackerLib
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.policy.data.repository.UserSetupRepository
@@ -131,19 +126,15 @@
impl: CollapsedStatusBarViewBinderImpl
): CollapsedStatusBarViewBinder
- @Binds
- @IntoMap
- @ClassKey(WifiRepositoryDagger::class)
- abstract fun bindWifiRepositoryDagger(impl: WifiRepositoryDagger): CoreStartable
-
companion object {
+
@Provides
@SysUISingleton
- fun provideWifiRepositoryDagger(
+ fun provideRealWifiRepository(
wifiManager: WifiManager?,
disabledWifiRepository: DisabledWifiRepository,
wifiRepositoryImplFactory: WifiRepositoryImpl.Factory,
- ): WifiRepositoryDagger {
+ ): RealWifiRepository {
// If we have a null [WifiManager], then the wifi repository should be permanently
// disabled.
return if (wifiManager == null) {
@@ -155,36 +146,6 @@
@Provides
@SysUISingleton
- fun provideWifiRepositoryViaTrackerLibDagger(
- wifiManager: WifiManager?,
- disabledWifiRepository: DisabledWifiRepository,
- wifiRepositoryFromTrackerLibFactory: WifiRepositoryViaTrackerLib.Factory,
- ): WifiRepositoryViaTrackerLibDagger {
- // If we have a null [WifiManager], then the wifi repository should be permanently
- // disabled.
- return if (wifiManager == null) {
- disabledWifiRepository
- } else {
- wifiRepositoryFromTrackerLibFactory.create(wifiManager)
- }
- }
-
- @Provides
- @SysUISingleton
- fun provideRealWifiRepository(
- wifiRepository: WifiRepositoryDagger,
- wifiRepositoryFromTrackerLib: WifiRepositoryViaTrackerLibDagger,
- flags: FeatureFlags,
- ): RealWifiRepository {
- return if (flags.isEnabled(Flags.WIFI_TRACKER_LIB_FOR_WIFI_ICON)) {
- wifiRepositoryFromTrackerLib
- } else {
- wifiRepository
- }
- }
-
- @Provides
- @SysUISingleton
@Named(FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON)
fun provideFirstMobileSubShowingNetworkTypeIconProvider(
mobileIconsViewModel: MobileIconsViewModel,
@@ -197,16 +158,8 @@
@Provides
@SysUISingleton
@WifiInputLog
- fun provideWifiInputLogBuffer(factory: LogBufferFactory): LogBuffer {
- return factory.create("WifiInputLog", 50)
- }
-
- @Provides
- @SysUISingleton
- @WifiTrackerLibInputLog
- fun provideWifiTrackerLibInputLogBuffer(factory: LogBufferFactory): LogBuffer {
- // WifiTrackerLib is pretty noisy, so give it more room than WifiInputLog.
- return factory.create("WifiTrackerLibInputLog", 200)
+ fun provideWifiLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("WifiInputLog", 200)
}
@Provides
@@ -218,13 +171,6 @@
@Provides
@SysUISingleton
- @WifiTrackerLibTableLog
- fun provideWifiTrackerLibTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
- return factory.create("WifiTrackerLibTableLog", 100)
- }
-
- @Provides
- @SysUISingleton
@AirplaneTableLog
fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
return factory.create("AirplaneTableLog", 30)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt
index 6db6944..9ba802c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt
@@ -18,8 +18,8 @@
import javax.inject.Qualifier
-/** Wifi logs for inputs into the wifi pipeline. */
-@Qualifier
-@MustBeDocumented
[email protected](AnnotationRetention.RUNTIME)
-annotation class WifiInputLog
+/**
+ * Wifi logs for inputs into
+ * [com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl].
+ */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class WifiInputLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
index 3b2930f..f4e3eab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
@@ -18,7 +18,6 @@
import android.os.PersistableBundle
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
-import android.telephony.CarrierConfigManager.KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT
import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.flow.MutableStateFlow
@@ -43,11 +42,10 @@
* using the default config for logging purposes.
*
* NOTE to add new keys to be tracked:
- * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig] or [IntCarrierConfig]
- * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config] or
- * [IntCarrierConfig.config]
- * 3. Add the new wrapped public flow to the list of tracked configs, so they are properly updated
- * when a new carrier config comes down
+ * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig]
+ * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config]
+ * 3. Add the new [BooleanCarrierConfig] to the list of tracked configs, so they are properly
+ * updated when a new carrier config comes down
*/
class SystemUiCarrierConfig
internal constructor(
@@ -68,16 +66,10 @@
/** Flow tracking the [KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL] config */
val showOperatorNameInStatusBar: StateFlow<Boolean> = showOperatorName.config
- private val satelliteHysteresisSeconds =
- IntCarrierConfig(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, defaultConfig)
- /** Flow tracking the [KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT] config */
- val satelliteConnectionHysteresisSeconds: StateFlow<Int> = satelliteHysteresisSeconds.config
-
private val trackedConfigs =
listOf(
inflateSignalStrength,
showOperatorName,
- satelliteHysteresisSeconds,
)
/** Ingest a new carrier config, and switch all of the tracked keys over to the new values */
@@ -98,19 +90,15 @@
override fun toString(): String = trackedConfigs.joinToString { it.toString() }
}
-interface CarrierConfig {
- fun update(config: PersistableBundle)
-}
-
/** Extracts [key] from the carrier config, and stores it in a flow */
private class BooleanCarrierConfig(
val key: String,
defaultConfig: PersistableBundle,
-) : CarrierConfig {
+) {
private val _configValue = MutableStateFlow(defaultConfig.getBoolean(key))
val config = _configValue.asStateFlow()
- override fun update(config: PersistableBundle) {
+ fun update(config: PersistableBundle) {
_configValue.value = config.getBoolean(key)
}
@@ -118,20 +106,3 @@
return "$key=${config.value}"
}
}
-
-/** Extracts [key] from the carrier config, and stores it in a flow */
-private class IntCarrierConfig(
- val key: String,
- defaultConfig: PersistableBundle,
-) : CarrierConfig {
- private val _configValue = MutableStateFlow(defaultConfig.getInt(key))
- val config = _configValue.asStateFlow()
-
- override fun update(config: PersistableBundle) {
- _configValue.value = config.getInt(key)
- }
-
- override fun toString(): String {
- return "$key=${config.value}"
- }
-}
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 317c063..2278597 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
@@ -144,9 +144,6 @@
*/
val hasPrioritizedNetworkCapabilities: StateFlow<Boolean>
- /** Duration in seconds of the hysteresis to use when losing satellite connection. */
- val satelliteConnectionHysteresisSeconds: StateFlow<Int>
-
/**
* True if this connection is in emergency callback mode.
*
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 90cdfeb..83d5f2b 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
@@ -227,8 +227,6 @@
override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
- override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0)
-
override suspend fun isInEcmMode(): Boolean = false
/**
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 cb99d11..a532e62 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
@@ -187,9 +187,6 @@
*/
override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false).asStateFlow()
- /** Non-applicable to carrier merged connections. */
- override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0).asStateFlow()
-
override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
override suspend fun isInEcmMode(): Boolean =
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 bb0af77..41559b2 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
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.util.IndentingPrintWriter
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
@@ -24,6 +25,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -349,17 +351,29 @@
activeRepo.value.hasPrioritizedNetworkCapabilities.value,
)
- override val satelliteConnectionHysteresisSeconds =
- activeRepo
- .flatMapLatest { it.satelliteConnectionHysteresisSeconds }
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- activeRepo.value.satelliteConnectionHysteresisSeconds.value
- )
-
override suspend fun isInEcmMode(): Boolean = activeRepo.value.isInEcmMode()
+ fun dump(pw: PrintWriter) {
+ val ipw = IndentingPrintWriter(pw, " ")
+
+ ipw.println("MobileConnectionRepository[$subId]")
+ ipw.increaseIndent()
+
+ ipw.println("carrierMerged=${_isCarrierMerged.value}")
+
+ ipw.print("Type (cellular or carrier merged): ")
+ when (activeRepo.value) {
+ is CarrierMergedConnectionRepository -> ipw.println("Carrier merged")
+ is MobileConnectionRepositoryImpl -> ipw.println("Cellular")
+ }
+
+ ipw.increaseIndent()
+ ipw.println("Provider: ${activeRepo.value}")
+ ipw.decreaseIndent()
+
+ ipw.decreaseIndent()
+ }
+
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 2cbe965..b3885d2 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
@@ -450,9 +450,6 @@
.flowOn(bgDispatcher)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val satelliteConnectionHysteresisSeconds: StateFlow<Int> =
- systemUiCarrierConfig.satelliteConnectionHysteresisSeconds
-
class Factory
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index a455db2..5d91ef3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -26,17 +26,20 @@
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyManager
+import android.util.IndentingPrintWriter
import androidx.annotation.VisibleForTesting
import com.android.internal.telephony.PhoneConstants
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.Dumpable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.res.R
@@ -52,6 +55,8 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.util.kotlin.pairwise
+import java.io.PrintWriter
+import java.lang.ref.WeakReference
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -97,8 +102,12 @@
wifiRepository: WifiRepository,
private val fullMobileRepoFactory: FullMobileConnectionRepository.Factory,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-) : MobileConnectionsRepository {
- private var subIdRepositoryCache: MutableMap<Int, FullMobileConnectionRepository> =
+ private val dumpManager: DumpManager,
+) : MobileConnectionsRepository, Dumpable {
+
+ // TODO(b/333912012): for now, we are never invalidating the cache. We can do better though
+ private var subIdRepositoryCache:
+ MutableMap<Int, WeakReference<FullMobileConnectionRepository>> =
mutableMapOf()
private val defaultNetworkName =
@@ -109,6 +118,10 @@
private val networkNameSeparator: String =
context.getString(R.string.status_bar_network_name_separator)
+ init {
+ dumpManager.registerNormalDumpable("MobileConnectionsRepository", this)
+ }
+
private val carrierMergedSubId: StateFlow<Int?> =
combine(
wifiRepository.wifiNetwork,
@@ -283,8 +296,10 @@
getOrCreateRepoForSubId(subId)
private fun getOrCreateRepoForSubId(subId: Int) =
- subIdRepositoryCache[subId]
- ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
+ subIdRepositoryCache[subId]?.get()
+ ?: createRepositoryForSubId(subId).also {
+ subIdRepositoryCache[subId] = WeakReference(it)
+ }
override val mobileIsDefault: StateFlow<Boolean> =
connectivityRepository.defaultConnections
@@ -374,9 +389,8 @@
}
private fun updateRepos(newInfos: List<SubscriptionModel>) {
- dropUnusedReposFromCache(newInfos)
subIdRepositoryCache.forEach { (subId, repo) ->
- repo.setIsCarrierMerged(isCarrierMerged(subId))
+ repo.get()?.setIsCarrierMerged(isCarrierMerged(subId))
}
}
@@ -384,13 +398,6 @@
return subId == carrierMergedSubId.value
}
- private fun dropUnusedReposFromCache(newInfos: List<SubscriptionModel>) {
- // Remove any connection repository from the cache that isn't in the new set of IDs. They
- // will get garbage collected once their subscribers go away
- subIdRepositoryCache =
- subIdRepositoryCache.filter { checkSub(it.key, newInfos) }.toMutableMap()
- }
-
/**
* True if the checked subId is in the list of current subs or the active mobile data subId
*
@@ -422,6 +429,22 @@
profileClass = profileClass,
)
+ override fun dump(pw: PrintWriter, args: Array<String>) {
+ val ipw = IndentingPrintWriter(pw, " ")
+ ipw.println("Connection cache:")
+
+ ipw.increaseIndent()
+ subIdRepositoryCache.entries.forEach { (subId, repo) ->
+ ipw.println("$subId: ${repo.get()}")
+ }
+ ipw.decreaseIndent()
+
+ ipw.println("Connections (${subIdRepositoryCache.size} total):")
+ ipw.increaseIndent()
+ subIdRepositoryCache.values.forEach { it.get()?.dump(ipw) }
+ ipw.decreaseIndent()
+ }
+
companion object {
private const val LOGGING_PREFIX = "Repo"
}
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 cbebfd0..ed9e405 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
@@ -35,25 +35,18 @@
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.util.kotlin.pairwiseBy
-import kotlin.time.DurationUnit
-import kotlin.time.toDuration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
interface MobileIconInteractor {
/** The table log created for this connection */
@@ -269,43 +262,6 @@
MutableStateFlow(false).asStateFlow()
}
- private val hysteresisActive = MutableStateFlow(false)
-
- private val isNonTerrestrialWithHysteresis: StateFlow<Boolean> =
- combine(isNonTerrestrial, hysteresisActive) { isNonTerrestrial, hysteresisActive ->
- if (hysteresisActive) {
- true
- } else {
- isNonTerrestrial
- }
- }
- .logDiffsForTable(
- tableLogBuffer = tableLogBuffer,
- columnName = "isNonTerrestrialWithHysteresis",
- columnPrefix = "",
- initialValue = Flags.carrierEnabledSatelliteFlag(),
- )
- .stateIn(scope, SharingStarted.Eagerly, Flags.carrierEnabledSatelliteFlag())
-
- private val lostSatelliteConnection =
- isNonTerrestrial.pairwiseBy { old, new -> hysteresisActive.value = old && !new }
-
- init {
- scope.launch { lostSatelliteConnection.collect() }
- scope.launch {
- hysteresisActive.collectLatest {
- if (it) {
- delay(
- connectionRepository.satelliteConnectionHysteresisSeconds.value.toDuration(
- DurationUnit.SECONDS
- )
- )
- hysteresisActive.value = false
- }
- }
- }
- }
-
override val isRoaming: StateFlow<Boolean> =
combine(
connectionRepository.carrierNetworkChangeActive,
@@ -407,7 +363,7 @@
showExclamationMark.value,
carrierNetworkChangeActive.value,
)
- isNonTerrestrialWithHysteresis
+ isNonTerrestrial
.flatMapLatest { ntn ->
if (ntn) {
satelliteIcon
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 a0c5618..5f08afd 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
@@ -128,6 +128,8 @@
mobileGroupView,
dotView,
)
+
+ view.requestLayout()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index 08ed030..054116d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -22,6 +22,8 @@
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+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.policy.domain.interactor.DeviceProvisioningInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -41,6 +43,7 @@
val repo: DeviceBasedSatelliteRepository,
iconsInteractor: MobileIconsInteractor,
deviceProvisioningInteractor: DeviceProvisioningInteractor,
+ wifiInteractor: WifiInteractor,
@Application scope: CoroutineScope,
) {
/** Must be observed by any UI showing Satellite iconography */
@@ -73,6 +76,9 @@
val isDeviceProvisioned: Flow<Boolean> = deviceProvisioningInteractor.isDeviceProvisioned
+ val isWifiActive: Flow<Boolean> =
+ wifiInteractor.wifiNetwork.map { it is WifiNetworkModel.Active }
+
/** When all connections are considered OOS, satellite connectivity is potentially valid */
val areAllConnectionsOutOfService =
if (Flags.oemEnabledSatelliteFlag()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 40641be..a0291b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -59,9 +59,10 @@
combine(
interactor.isSatelliteAllowed,
interactor.isDeviceProvisioned,
+ interactor.isWifiActive,
airplaneModeRepository.isAirplaneMode
- ) { isSatelliteAllowed, isDeviceProvisioned, isAirplaneMode ->
- isSatelliteAllowed && isDeviceProvisioned && !isAirplaneMode
+ ) { isSatelliteAllowed, isDeviceProvisioned, isWifiActive, isAirplaneMode ->
+ isSatelliteAllowed && isDeviceProvisioned && !isWifiActive && !isAirplaneMode
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index b22e09e..fc7a672 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
-import com.android.systemui.CoreStartable
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
@@ -80,8 +79,3 @@
* repository.
*/
interface RealWifiRepository : WifiRepository
-
-/** Used only by Dagger to bind [WifiRepositoryImpl]. */
-interface WifiRepositoryDagger : RealWifiRepository, CoreStartable
-/** Used only by Dagger to bind [WifiRepositoryViaTrackerLib]. */
-interface WifiRepositoryViaTrackerLibDagger : RealWifiRepository
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
index ca042e2..af6e8a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -25,7 +25,6 @@
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
index cfdbe4a..b79fb9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
@@ -18,8 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import javax.inject.Inject
@@ -34,10 +33,7 @@
* wifi information.
*/
@SysUISingleton
-class DisabledWifiRepository @Inject constructor() :
- WifiRepositoryDagger, WifiRepositoryViaTrackerLibDagger {
- override fun start() {}
-
+class DisabledWifiRepository @Inject constructor() : RealWifiRepository {
override val isWifiEnabled: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
override val isWifiDefault: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
deleted file mode 100644
index 67dd32f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
+++ /dev/null
@@ -1,114 +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.statusbar.pipeline.wifi.data.repository.prod
-
-import android.annotation.SuppressLint
-import android.net.wifi.ScanResult
-import android.net.wifi.WifiManager
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
-import java.util.concurrent.Executor
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.asExecutor
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.stateIn
-
-/**
- * Object to provide shared helper functions between [WifiRepositoryImpl] and
- * [WifiRepositoryViaTrackerLib].
- */
-object WifiRepositoryHelper {
- /** Creates a flow that fetches the [DataActivityModel] from [WifiManager]. */
- fun createActivityFlow(
- wifiManager: WifiManager,
- @Main mainExecutor: Executor,
- scope: CoroutineScope,
- tableLogBuffer: TableLogBuffer,
- inputLogger: (String) -> Unit,
- ): StateFlow<DataActivityModel> {
- return conflatedCallbackFlow {
- val callback =
- WifiManager.TrafficStateCallback { state ->
- inputLogger.invoke(prettyPrintActivity(state))
- trySend(state.toWifiDataActivityModel())
- }
- wifiManager.registerTrafficStateCallback(mainExecutor, callback)
- awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
- }
- .logDiffsForTable(
- tableLogBuffer,
- columnPrefix = ACTIVITY_PREFIX,
- initialValue = ACTIVITY_DEFAULT,
- )
- .stateIn(
- scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = ACTIVITY_DEFAULT,
- )
- }
-
- /**
- * Creates a flow that listens for new [ScanResult]s from [WifiManager]. Does not request a scan
- */
- fun createNetworkScanFlow(
- wifiManager: WifiManager,
- scope: CoroutineScope,
- @Background dispatcher: CoroutineDispatcher,
- inputLogger: () -> Unit,
- ): StateFlow<List<WifiScanEntry>> {
- return conflatedCallbackFlow {
- val callback =
- object : WifiManager.ScanResultsCallback() {
- @SuppressLint("MissingPermission")
- override fun onScanResultsAvailable() {
- inputLogger.invoke()
- trySend(wifiManager.scanResults.toModel())
- }
- }
-
- wifiManager.registerScanResultsCallback(dispatcher.asExecutor(), callback)
-
- awaitClose { wifiManager.unregisterScanResultsCallback(callback) }
- }
- .stateIn(scope, SharingStarted.Eagerly, emptyList())
- }
-
- private fun List<ScanResult>.toModel(): List<WifiScanEntry> = map { WifiScanEntry(it.SSID) }
-
- // TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer.
- private fun prettyPrintActivity(activity: Int): String {
- return when (activity) {
- WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
- WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
- WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
- WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
- else -> "INVALID"
- }
- }
-
- private const val ACTIVITY_PREFIX = "wifiActivity"
- val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 59ef884..20e44e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.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.
@@ -17,309 +17,447 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
import android.annotation.SuppressLint
-import android.content.IntentFilter
-import android.net.ConnectivityManager
-import android.net.Network
-import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
-import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
-import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
-import android.net.NetworkCapabilities.TRANSPORT_WIFI
-import android.net.NetworkRequest
-import android.net.wifi.WifiInfo
+import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import com.android.systemui.broadcast.BroadcastDispatcher
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.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.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
+import com.android.systemui.statusbar.pipeline.dagger.WifiInputLog
import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
-import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
-import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.wifitrackerlib.HotspotNetworkEntry
+import com.android.wifitrackerlib.MergedCarrierEntry
+import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
+import com.android.wifitrackerlib.WifiPickerTracker
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-/** Real implementation of [WifiRepository]. */
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
+/**
+ * A real implementation of [WifiRepository] that uses [com.android.wifitrackerlib] as the source of
+ * truth for wifi information.
+ */
@SysUISingleton
-@SuppressLint("MissingPermission")
class WifiRepositoryImpl
@Inject
constructor(
- broadcastDispatcher: BroadcastDispatcher,
- connectivityManager: ConnectivityManager,
- connectivityRepository: ConnectivityRepository,
- logger: WifiInputLogger,
- @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
- @Main mainExecutor: Executor,
- @Background private val bgDispatcher: CoroutineDispatcher,
+ featureFlags: FeatureFlags,
@Application private val scope: CoroutineScope,
+ @Main private val mainExecutor: Executor,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
private val wifiManager: WifiManager,
-) : WifiRepositoryDagger {
+ @WifiInputLog private val inputLogger: LogBuffer,
+ @WifiTableLog private val tableLogger: TableLogBuffer,
+) : RealWifiRepository, LifecycleOwner {
- override fun start() {
- // There are two possible [WifiRepository] implementations: This class (old) and
- // [WifiRepositoryFromTrackerLib] (new). While we migrate to the new class, we want this old
- // class to still be running in the background so that we can collect logs and compare
- // discrepancies. This #start method collects on the flows to ensure that the logs are
- // collected.
- scope.launch { isWifiEnabled.collect {} }
- scope.launch { isWifiDefault.collect {} }
- scope.launch { wifiNetwork.collect {} }
- scope.launch { wifiActivity.collect {} }
+ override val lifecycle =
+ LifecycleRegistry(this).also {
+ mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
+ }
+
+ private val isInstantTetherEnabled = featureFlags.isEnabled(Flags.INSTANT_TETHER)
+
+ private var wifiPickerTracker: WifiPickerTracker? = null
+
+ private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run {
+ var current =
+ WifiPickerTrackerInfo(
+ state = WIFI_STATE_DEFAULT,
+ isDefault = false,
+ primaryNetwork = WIFI_NETWORK_DEFAULT,
+ secondaryNetworks = emptyList(),
+ )
+ callbackFlow {
+ val callback =
+ object : WifiPickerTracker.WifiPickerTrackerCallback {
+ override fun onWifiEntriesChanged() {
+ val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
+ logOnWifiEntriesChanged(connectedEntry)
+
+ val secondaryNetworks =
+ if (featureFlags.isEnabled(Flags.WIFI_SECONDARY_NETWORKS)) {
+ val activeNetworks =
+ wifiPickerTracker?.activeWifiEntries ?: emptyList()
+ activeNetworks
+ .filter { it != connectedEntry && !it.isPrimaryNetwork }
+ .map { it.toWifiNetworkModel() }
+ } else {
+ emptyList()
+ }
+
+ // [WifiPickerTracker.connectedWifiEntry] will return the same instance
+ // but with updated internals. For example, when its validation status
+ // changes from false to true, the same instance is re-used but with the
+ // validated field updated.
+ //
+ // Because it's the same instance, the flow won't re-emit the value
+ // (even though the internals have changed). So, we need to transform it
+ // into our internal model immediately. [toWifiNetworkModel] always
+ // returns a new instance, so the flow is guaranteed to emit.
+ send(
+ newPrimaryNetwork = connectedEntry?.toPrimaryWifiNetworkModel()
+ ?: WIFI_NETWORK_DEFAULT,
+ newSecondaryNetworks = secondaryNetworks,
+ newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
+ )
+ }
+
+ override fun onWifiStateChanged() {
+ val state = wifiPickerTracker?.wifiState
+ logOnWifiStateChanged(state)
+ send(newState = state ?: WIFI_STATE_DEFAULT)
+ }
+
+ override fun onNumSavedNetworksChanged() {}
+
+ override fun onNumSavedSubscriptionsChanged() {}
+
+ private fun send(
+ newState: Int = current.state,
+ newIsDefault: Boolean = current.isDefault,
+ newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
+ newSecondaryNetworks: List<WifiNetworkModel> =
+ current.secondaryNetworks,
+ ) {
+ val new =
+ WifiPickerTrackerInfo(
+ newState,
+ newIsDefault,
+ newPrimaryNetwork,
+ newSecondaryNetworks,
+ )
+ current = new
+ trySend(new)
+ }
+ }
+
+ wifiPickerTracker =
+ wifiPickerTrackerFactory.create(lifecycle, callback, "WifiRepository").apply {
+ // By default, [WifiPickerTracker] will scan to see all available wifi
+ // networks in the area. Because SysUI only needs to display the
+ // **connected** network, we don't need scans to be running (and in fact,
+ // running scans is costly and should be avoided whenever possible).
+ this?.disableScanning()
+ }
+ // The lifecycle must be STARTED in order for the callback to receive events.
+ mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
+ awaitClose {
+ mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED }
+ }
+ }
+ .stateIn(scope, SharingStarted.Eagerly, current)
}
- private val wifiStateChangeEvents: Flow<Unit> =
- broadcastDispatcher
- .broadcastFlow(IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION))
- .onEach { logger.logIntent("WIFI_STATE_CHANGED_ACTION") }
-
- private val wifiNetworkChangeEvents: MutableSharedFlow<Unit> =
- MutableSharedFlow(extraBufferCapacity = 1)
-
- // Because [WifiManager] doesn't expose a wifi enabled change listener, we do it
- // internally by fetching [WifiManager.isWifiEnabled] whenever we think the state may
- // have changed.
override val isWifiEnabled: StateFlow<Boolean> =
- merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
- .onStart { emit(Unit) }
- .mapLatest { isWifiEnabled() }
+ wifiPickerTrackerInfo
+ .map { it.state == WifiManager.WIFI_STATE_ENABLED }
.distinctUntilChanged()
.logDiffsForTable(
- wifiTableLogBuffer,
+ tableLogger,
columnPrefix = "",
columnName = COL_NAME_IS_ENABLED,
initialValue = false,
)
- .stateIn(
- scope = scope,
- started = SharingStarted.Eagerly,
- initialValue = false,
- )
+ .stateIn(scope, SharingStarted.Eagerly, false)
- // [WifiManager.isWifiEnabled] is a blocking IPC call, so fetch it in the background.
- private suspend fun isWifiEnabled(): Boolean =
- withContext(bgDispatcher) { wifiManager.isWifiEnabled }
-
- override val isWifiDefault: StateFlow<Boolean> =
- connectivityRepository.defaultConnections
- // TODO(b/274493701): Should wifi be considered default if it's carrier merged?
- .map { it.wifi.isDefault || it.carrierMerged.isDefault }
+ override val wifiNetwork: StateFlow<WifiNetworkModel> =
+ wifiPickerTrackerInfo
+ .map { it.primaryNetwork }
.distinctUntilChanged()
.logDiffsForTable(
- wifiTableLogBuffer,
+ tableLogger,
+ columnPrefix = "",
+ initialValue = WIFI_NETWORK_DEFAULT,
+ )
+ .stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT)
+
+ override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+ wifiPickerTrackerInfo
+ .map { it.secondaryNetworks }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
+ columnPrefix = "",
+ columnName = "secondaryNetworks",
+ initialValue = emptyList(),
+ )
+ .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.
+ */
+ private fun WifiEntry.toPrimaryWifiNetworkModel(): WifiNetworkModel {
+ return if (!this.isPrimaryNetwork) {
+ WIFI_NETWORK_DEFAULT
+ } else {
+ this.toWifiNetworkModel()
+ }
+ }
+
+ /** Converts WifiTrackerLib's [WifiEntry] into our internal model. */
+ private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel {
+ return if (this is MergedCarrierEntry) {
+ this.convertCarrierMergedToModel()
+ } else {
+ this.convertNormalToModel()
+ }
+ }
+
+ private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel {
+ return if (this.subscriptionId == INVALID_SUBSCRIPTION_ID) {
+ WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
+ } else {
+ WifiNetworkModel.CarrierMerged(
+ networkId = NETWORK_ID,
+ subscriptionId = this.subscriptionId,
+ level = this.level,
+ // WifiManager APIs to calculate the signal level start from 0, so
+ // maxSignalLevel + 1 represents the total level buckets count.
+ numberOfLevels = wifiManager.maxSignalLevel + 1,
+ )
+ }
+ }
+
+ private fun WifiEntry.convertNormalToModel(): WifiNetworkModel {
+ if (this.level == WIFI_LEVEL_UNREACHABLE || this.level !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX) {
+ // If our level means the network is unreachable or the level is otherwise invalid, we
+ // don't have an active network.
+ return WifiNetworkModel.Inactive
+ }
+
+ val hotspotDeviceType =
+ if (isInstantTetherEnabled && this is HotspotNetworkEntry) {
+ this.deviceType.toHotspotDeviceType()
+ } else {
+ WifiNetworkModel.HotspotDeviceType.NONE
+ }
+
+ return WifiNetworkModel.Active(
+ networkId = NETWORK_ID,
+ isValidated = this.hasInternetAccess(),
+ level = this.level,
+ ssid = this.title,
+ hotspotDeviceType = hotspotDeviceType,
+ // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the SSID for
+ // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can
+ // always be false/null in this repository.
+ // TODO(b/292534484): Remove these fields from the wifi network model once this
+ // repository is fully enabled.
+ isPasspointAccessPoint = false,
+ isOnlineSignUpForPasspointAccessPoint = false,
+ passpointProviderFriendlyName = null,
+ )
+ }
+
+ override val isWifiDefault: StateFlow<Boolean> =
+ wifiPickerTrackerInfo
+ .map { it.isDefault }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
columnPrefix = "",
columnName = COL_NAME_IS_DEFAULT,
initialValue = false,
)
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+ .stateIn(scope, SharingStarted.Eagerly, false)
- override val wifiNetwork: StateFlow<WifiNetworkModel> =
+ override val wifiActivity: StateFlow<DataActivityModel> =
conflatedCallbackFlow {
- var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
-
val callback =
- object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
- override fun onCapabilitiesChanged(
- network: Network,
- networkCapabilities: NetworkCapabilities
- ) {
- logger.logOnCapabilitiesChanged(
- network,
- networkCapabilities,
- isDefaultNetworkCallback = false,
- )
-
- wifiNetworkChangeEvents.tryEmit(Unit)
-
- val wifiInfo =
- networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
- if (wifiInfo?.isPrimary == true) {
- val wifiNetworkModel =
- createWifiNetworkModel(
- wifiInfo,
- network,
- networkCapabilities,
- wifiManager,
- )
- currentWifi = wifiNetworkModel
- trySend(wifiNetworkModel)
- }
- }
-
- override fun onLost(network: Network) {
- logger.logOnLost(network, isDefaultNetworkCallback = false)
-
- wifiNetworkChangeEvents.tryEmit(Unit)
-
- val wifi = currentWifi
- if (
- (wifi is WifiNetworkModel.Active &&
- wifi.networkId == network.getNetId()) ||
- (wifi is WifiNetworkModel.CarrierMerged &&
- wifi.networkId == network.getNetId())
- ) {
- val newNetworkModel = WifiNetworkModel.Inactive
- currentWifi = newNetworkModel
- trySend(newNetworkModel)
- }
- }
+ WifiManager.TrafficStateCallback { state ->
+ logActivity(state)
+ trySend(state.toWifiDataActivityModel())
}
-
- connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback)
-
- awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ wifiManager.registerTrafficStateCallback(mainExecutor, callback)
+ awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
}
- .distinctUntilChanged()
.logDiffsForTable(
- wifiTableLogBuffer,
- columnPrefix = "",
- initialValue = WIFI_NETWORK_DEFAULT,
+ tableLogger,
+ columnPrefix = ACTIVITY_PREFIX,
+ initialValue = ACTIVITY_DEFAULT,
)
- // There will be multiple wifi icons in different places that will frequently
- // subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures
- // that new subscribes will get the latest value immediately upon subscription.
- // Otherwise, the views could show stale data. See b/244173280.
.stateIn(
scope,
started = SharingStarted.WhileSubscribed(),
- initialValue = WIFI_NETWORK_DEFAULT,
+ initialValue = ACTIVITY_DEFAULT,
)
- // Secondary networks can only be supported by [WifiRepositoryViaTrackerLib].
- override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
- MutableStateFlow(emptyList<WifiNetworkModel>()).asStateFlow()
-
- override val wifiActivity: StateFlow<DataActivityModel> =
- WifiRepositoryHelper.createActivityFlow(
- wifiManager,
- mainExecutor,
- scope,
- wifiTableLogBuffer,
- logger::logActivity,
- )
-
override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
- WifiRepositoryHelper.createNetworkScanFlow(
- wifiManager,
- scope,
- bgDispatcher,
- logger::logScanResults
- )
+ conflatedCallbackFlow {
+ val callback =
+ object : WifiManager.ScanResultsCallback() {
+ @SuppressLint("MissingPermission")
+ override fun onScanResultsAvailable() {
+ logScanResults()
+ trySend(wifiManager.scanResults.toModel())
+ }
+ }
- companion object {
- // Start out with no known wifi network.
- // Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an
- // initial fetch to get a starting wifi network. But, it uses a deprecated API
- // [WifiManager.getConnectionInfo()], and the deprecation doc indicates to just use
- // [ConnectivityManager.NetworkCallback] results instead. So, for now we'll just rely on the
- // NetworkCallback inside [wifiNetwork] for our wifi network information.
- val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
+ wifiManager.registerScanResultsCallback(bgDispatcher.asExecutor(), callback)
- const val WIFI_STATE_DEFAULT = WifiManager.WIFI_STATE_DISABLED
-
- private fun createWifiNetworkModel(
- wifiInfo: WifiInfo,
- network: Network,
- networkCapabilities: NetworkCapabilities,
- wifiManager: WifiManager,
- ): WifiNetworkModel {
- return if (wifiInfo.isCarrierMerged) {
- if (wifiInfo.subscriptionId == INVALID_SUBSCRIPTION_ID) {
- WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
- } else {
- WifiNetworkModel.CarrierMerged(
- networkId = network.getNetId(),
- subscriptionId = wifiInfo.subscriptionId,
- level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
- // The WiFi signal level returned by WifiManager#calculateSignalLevel start
- // from 0, so WifiManager#getMaxSignalLevel + 1 represents the total level
- // buckets count.
- numberOfLevels = wifiManager.maxSignalLevel + 1,
- )
- }
- } else {
- WifiNetworkModel.Active(
- network.getNetId(),
- isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED),
- level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
- wifiInfo.ssid,
- // This repository doesn't support any hotspot information.
- WifiNetworkModel.HotspotDeviceType.NONE,
- wifiInfo.isPasspointAp,
- wifiInfo.isOsuAp,
- wifiInfo.passpointProviderFriendlyName
- )
+ awaitClose { wifiManager.unregisterScanResultsCallback(callback) }
}
- }
+ .stateIn(scope, SharingStarted.Eagerly, emptyList())
- private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest =
- NetworkRequest.Builder()
- .clearCapabilities()
- .addCapability(NET_CAPABILITY_NOT_VPN)
- .addTransportType(TRANSPORT_WIFI)
- .addTransportType(TRANSPORT_CELLULAR)
- .build()
+ private fun List<ScanResult>.toModel(): List<WifiScanEntry> = map { WifiScanEntry(it.SSID) }
+
+ private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) {
+ inputLogger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = connectedEntry.toString() },
+ { "onWifiEntriesChanged. ConnectedEntry=$str1" },
+ )
}
+ private fun logOnWifiStateChanged(state: Int?) {
+ inputLogger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = state ?: -1 },
+ { "onWifiStateChanged. State=${if (int1 == -1) null else int1}" },
+ )
+ }
+
+ private fun logActivity(activity: Int) {
+ inputLogger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = prettyPrintActivity(activity) },
+ { "onActivityChanged: $str1" }
+ )
+ }
+
+ // TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer.
+ private fun prettyPrintActivity(activity: Int): String {
+ return when (activity) {
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
+ else -> "INVALID"
+ }
+ }
+
+ private fun logScanResults() =
+ inputLogger.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" })
+
+ /**
+ * Data class storing all the information fetched from [WifiPickerTracker].
+ *
+ * Used so that we only register a single callback on [WifiPickerTracker].
+ */
+ data class WifiPickerTrackerInfo(
+ /** The current wifi state. See [WifiManager.getWifiState]. */
+ val state: Int,
+ /** True if wifi is currently the default connection and false otherwise. */
+ val isDefault: Boolean,
+ /** The currently primary wifi network. */
+ val primaryNetwork: WifiNetworkModel,
+ /** The current secondary network(s), if any. Specifically excludes the primary network. */
+ val secondaryNetworks: List<WifiNetworkModel>
+ )
+
@SysUISingleton
class Factory
@Inject
constructor(
- private val broadcastDispatcher: BroadcastDispatcher,
- private val connectivityManager: ConnectivityManager,
- private val connectivityRepository: ConnectivityRepository,
- private val logger: WifiInputLogger,
- @WifiTableLog private val wifiTableLogBuffer: TableLogBuffer,
+ private val featureFlags: FeatureFlags,
+ @Application private val scope: CoroutineScope,
@Main private val mainExecutor: Executor,
@Background private val bgDispatcher: CoroutineDispatcher,
- @Application private val scope: CoroutineScope,
+ private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
+ @WifiInputLog private val inputLogger: LogBuffer,
+ @WifiTableLog private val tableLogger: TableLogBuffer,
) {
fun create(wifiManager: WifiManager): WifiRepositoryImpl {
return WifiRepositoryImpl(
- broadcastDispatcher,
- connectivityManager,
- connectivityRepository,
- logger,
- wifiTableLogBuffer,
+ featureFlags,
+ scope,
mainExecutor,
bgDispatcher,
- scope,
+ wifiPickerTrackerFactory,
wifiManager,
+ inputLogger,
+ tableLogger,
)
}
}
+
+ companion object {
+ // Start out with no known wifi network.
+ @VisibleForTesting val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
+
+ private const val WIFI_STATE_DEFAULT = WifiManager.WIFI_STATE_DISABLED
+
+ private const val ACTIVITY_PREFIX = "wifiActivity"
+ val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+
+ private const val TAG = "WifiTrackerLibInputLog"
+
+ /**
+ * [WifiNetworkModel.Active.networkId] is only used at the repository layer. It's used by
+ * [WifiRepositoryImpl], which tracks the ID in order to correctly apply the framework
+ * callbacks within the repository.
+ *
+ * Since this class does not need to manually apply framework callbacks and since the
+ * network ID is not used beyond the repository, it's safe to use an invalid ID in this
+ * repository.
+ *
+ * The [WifiNetworkModel.Active.networkId] field should be deleted once we've fully migrated
+ * to [WifiRepositoryImpl].
+ */
+ private const val NETWORK_ID = -1
+ }
}
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
deleted file mode 100644
index 1670dd3..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
+++ /dev/null
@@ -1,414 +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.statusbar.pipeline.wifi.data.repository.prod
-
-import android.net.wifi.WifiManager
-import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.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.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
-import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
-import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibInputLog
-import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibTableLog
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_STATE_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
-import com.android.wifitrackerlib.HotspotNetworkEntry
-import com.android.wifitrackerlib.MergedCarrierEntry
-import com.android.wifitrackerlib.WifiEntry
-import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX
-import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN
-import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
-import com.android.wifitrackerlib.WifiPickerTracker
-import java.util.concurrent.Executor
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-/**
- * An implementation of [WifiRepository] that uses [com.android.wifitrackerlib] as the source of
- * truth for wifi information.
- *
- * Serves as a possible replacement for [WifiRepositoryImpl]. See b/292534484.
- */
-@SysUISingleton
-class WifiRepositoryViaTrackerLib
-@Inject
-constructor(
- featureFlags: FeatureFlags,
- @Application private val scope: CoroutineScope,
- @Main private val mainExecutor: Executor,
- @Background private val bgDispatcher: CoroutineDispatcher,
- private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
- private val wifiManager: WifiManager,
- @WifiTrackerLibInputLog private val inputLogger: LogBuffer,
- @WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer,
-) : WifiRepositoryViaTrackerLibDagger, LifecycleOwner {
-
- override val lifecycle =
- LifecycleRegistry(this).also {
- mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
- }
-
- private val isInstantTetherEnabled = featureFlags.isEnabled(Flags.INSTANT_TETHER)
-
- private var wifiPickerTracker: WifiPickerTracker? = null
-
- private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run {
- var current =
- WifiPickerTrackerInfo(
- state = WIFI_STATE_DEFAULT,
- isDefault = false,
- primaryNetwork = WIFI_NETWORK_DEFAULT,
- secondaryNetworks = emptyList(),
- )
- callbackFlow {
- val callback =
- object : WifiPickerTracker.WifiPickerTrackerCallback {
- override fun onWifiEntriesChanged() {
- val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
- logOnWifiEntriesChanged(connectedEntry)
-
- val secondaryNetworks =
- if (featureFlags.isEnabled(Flags.WIFI_SECONDARY_NETWORKS)) {
- val activeNetworks =
- wifiPickerTracker?.activeWifiEntries ?: emptyList()
- activeNetworks
- .filter { it != connectedEntry && !it.isPrimaryNetwork }
- .map { it.toWifiNetworkModel() }
- } else {
- emptyList()
- }
-
- // [WifiPickerTracker.connectedWifiEntry] will return the same instance
- // but with updated internals. For example, when its validation status
- // changes from false to true, the same instance is re-used but with the
- // validated field updated.
- //
- // Because it's the same instance, the flow won't re-emit the value
- // (even though the internals have changed). So, we need to transform it
- // into our internal model immediately. [toWifiNetworkModel] always
- // returns a new instance, so the flow is guaranteed to emit.
- send(
- newPrimaryNetwork = connectedEntry?.toPrimaryWifiNetworkModel()
- ?: WIFI_NETWORK_DEFAULT,
- newSecondaryNetworks = secondaryNetworks,
- newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
- )
- }
-
- override fun onWifiStateChanged() {
- val state = wifiPickerTracker?.wifiState
- logOnWifiStateChanged(state)
- send(newState = state ?: WIFI_STATE_DEFAULT)
- }
-
- override fun onNumSavedNetworksChanged() {}
-
- override fun onNumSavedSubscriptionsChanged() {}
-
- private fun send(
- newState: Int = current.state,
- newIsDefault: Boolean = current.isDefault,
- newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
- newSecondaryNetworks: List<WifiNetworkModel> =
- current.secondaryNetworks,
- ) {
- val new =
- WifiPickerTrackerInfo(
- newState,
- newIsDefault,
- newPrimaryNetwork,
- newSecondaryNetworks,
- )
- current = new
- trySend(new)
- }
- }
-
- wifiPickerTracker =
- wifiPickerTrackerFactory.create(lifecycle, callback, "WifiRepository").apply {
- // By default, [WifiPickerTracker] will scan to see all available wifi
- // networks in the area. Because SysUI only needs to display the
- // **connected** network, we don't need scans to be running (and in fact,
- // running scans is costly and should be avoided whenever possible).
- this?.disableScanning()
- }
- // The lifecycle must be STARTED in order for the callback to receive events.
- mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
- awaitClose {
- mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED }
- }
- }
- .stateIn(scope, SharingStarted.Eagerly, current)
- }
-
- override val isWifiEnabled: StateFlow<Boolean> =
- wifiPickerTrackerInfo
- .map { it.state == WifiManager.WIFI_STATE_ENABLED }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTrackerLibTableLogBuffer,
- columnPrefix = "",
- columnName = COL_NAME_IS_ENABLED,
- initialValue = false,
- )
- .stateIn(scope, SharingStarted.Eagerly, false)
-
- override val wifiNetwork: StateFlow<WifiNetworkModel> =
- wifiPickerTrackerInfo
- .map { it.primaryNetwork }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTrackerLibTableLogBuffer,
- columnPrefix = "",
- initialValue = WIFI_NETWORK_DEFAULT,
- )
- .stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT)
-
- override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
- wifiPickerTrackerInfo
- .map { it.secondaryNetworks }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTrackerLibTableLogBuffer,
- columnPrefix = "",
- columnName = "secondaryNetworks",
- initialValue = emptyList(),
- )
- .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.
- */
- private fun WifiEntry.toPrimaryWifiNetworkModel(): WifiNetworkModel {
- return if (!this.isPrimaryNetwork) {
- WIFI_NETWORK_DEFAULT
- } else {
- this.toWifiNetworkModel()
- }
- }
-
- /** Converts WifiTrackerLib's [WifiEntry] into our internal model. */
- private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel {
- return if (this is MergedCarrierEntry) {
- this.convertCarrierMergedToModel()
- } else {
- this.convertNormalToModel()
- }
- }
-
- private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel {
- return if (this.subscriptionId == INVALID_SUBSCRIPTION_ID) {
- WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
- } else {
- WifiNetworkModel.CarrierMerged(
- networkId = NETWORK_ID,
- subscriptionId = this.subscriptionId,
- level = this.level,
- // WifiManager APIs to calculate the signal level start from 0, so
- // maxSignalLevel + 1 represents the total level buckets count.
- numberOfLevels = wifiManager.maxSignalLevel + 1,
- )
- }
- }
-
- private fun WifiEntry.convertNormalToModel(): WifiNetworkModel {
- if (this.level == WIFI_LEVEL_UNREACHABLE || this.level !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX) {
- // If our level means the network is unreachable or the level is otherwise invalid, we
- // don't have an active network.
- return WifiNetworkModel.Inactive
- }
-
- val hotspotDeviceType =
- if (isInstantTetherEnabled && this is HotspotNetworkEntry) {
- this.deviceType.toHotspotDeviceType()
- } else {
- WifiNetworkModel.HotspotDeviceType.NONE
- }
-
- return WifiNetworkModel.Active(
- networkId = NETWORK_ID,
- isValidated = this.hasInternetAccess(),
- level = this.level,
- ssid = this.title,
- hotspotDeviceType = hotspotDeviceType,
- // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the SSID for
- // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can
- // always be false/null in this repository.
- // TODO(b/292534484): Remove these fields from the wifi network model once this
- // repository is fully enabled.
- isPasspointAccessPoint = false,
- isOnlineSignUpForPasspointAccessPoint = false,
- passpointProviderFriendlyName = null,
- )
- }
-
- override val isWifiDefault: StateFlow<Boolean> =
- wifiPickerTrackerInfo
- .map { it.isDefault }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTrackerLibTableLogBuffer,
- columnPrefix = "",
- columnName = COL_NAME_IS_DEFAULT,
- initialValue = false,
- )
- .stateIn(scope, SharingStarted.Eagerly, false)
-
- override val wifiActivity: StateFlow<DataActivityModel> =
- WifiRepositoryHelper.createActivityFlow(
- wifiManager,
- mainExecutor,
- scope,
- wifiTrackerLibTableLogBuffer,
- this::logActivity,
- )
-
- override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
- WifiRepositoryHelper.createNetworkScanFlow(
- wifiManager,
- scope,
- bgDispatcher,
- this::logScanResults,
- )
-
- private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) {
- inputLogger.log(
- TAG,
- LogLevel.DEBUG,
- { str1 = connectedEntry.toString() },
- { "onWifiEntriesChanged. ConnectedEntry=$str1" },
- )
- }
-
- private fun logOnWifiStateChanged(state: Int?) {
- inputLogger.log(
- TAG,
- LogLevel.DEBUG,
- { int1 = state ?: -1 },
- { "onWifiStateChanged. State=${if (int1 == -1) null else int1}" },
- )
- }
-
- private fun logActivity(activity: String) {
- inputLogger.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "onActivityChanged: $str1" })
- }
-
- private fun logScanResults() =
- inputLogger.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" })
-
- /**
- * Data class storing all the information fetched from [WifiPickerTracker].
- *
- * Used so that we only register a single callback on [WifiPickerTracker].
- */
- data class WifiPickerTrackerInfo(
- /** The current wifi state. See [WifiManager.getWifiState]. */
- val state: Int,
- /** True if wifi is currently the default connection and false otherwise. */
- val isDefault: Boolean,
- /** The currently primary wifi network. */
- val primaryNetwork: WifiNetworkModel,
- /** The current secondary network(s), if any. Specifically excludes the primary network. */
- val secondaryNetworks: List<WifiNetworkModel>
- )
-
- @SysUISingleton
- class Factory
- @Inject
- constructor(
- private val featureFlags: FeatureFlags,
- @Application private val scope: CoroutineScope,
- @Main private val mainExecutor: Executor,
- @Background private val bgDispatcher: CoroutineDispatcher,
- private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
- @WifiTrackerLibInputLog private val inputLogger: LogBuffer,
- @WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer,
- ) {
- fun create(wifiManager: WifiManager): WifiRepositoryViaTrackerLib {
- return WifiRepositoryViaTrackerLib(
- featureFlags,
- scope,
- mainExecutor,
- bgDispatcher,
- wifiPickerTrackerFactory,
- wifiManager,
- inputLogger,
- wifiTrackerLibTableLogBuffer,
- )
- }
- }
-
- companion object {
- private const val TAG = "WifiTrackerLibInputLog"
-
- /**
- * [WifiNetworkModel.Active.networkId] is only used at the repository layer. It's used by
- * [WifiRepositoryImpl], which tracks the ID in order to correctly apply the framework
- * callbacks within the repository.
- *
- * Since this class does not need to manually apply framework callbacks and since the
- * network ID is not used beyond the repository, it's safe to use an invalid ID in this
- * repository.
- *
- * The [WifiNetworkModel.Active.networkId] field should be deleted once we've fully migrated
- * to [WifiRepositoryViaTrackerLib].
- */
- private const val NETWORK_ID = -1
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
deleted file mode 100644
index b76bb51..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
+++ /dev/null
@@ -1,66 +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.statusbar.pipeline.wifi.shared
-
-import android.net.Network
-import android.net.NetworkCapabilities
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.LogLevel
-import com.android.systemui.statusbar.pipeline.dagger.WifiInputLog
-import com.android.systemui.statusbar.pipeline.shared.LoggerHelper
-import javax.inject.Inject
-
-/**
- * Logger for all the wifi-related inputs (intents, callbacks, etc.) that the wifi repo receives.
- */
-@SysUISingleton
-class WifiInputLogger
-@Inject
-constructor(
- @WifiInputLog val buffer: LogBuffer,
-) {
- fun logOnCapabilitiesChanged(
- network: Network,
- networkCapabilities: NetworkCapabilities,
- isDefaultNetworkCallback: Boolean,
- ) {
- LoggerHelper.logOnCapabilitiesChanged(
- buffer,
- TAG,
- network,
- networkCapabilities,
- isDefaultNetworkCallback,
- )
- }
-
- fun logOnLost(network: Network, isDefaultNetworkCallback: Boolean) {
- LoggerHelper.logOnLost(buffer, TAG, network, isDefaultNetworkCallback)
- }
-
- fun logIntent(intentName: String) {
- buffer.log(TAG, LogLevel.DEBUG, { str1 = intentName }, { "Intent received: $str1" })
- }
-
- fun logActivity(activity: String) {
- buffer.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "Activity: $str1" })
- }
-
- fun logScanResults() = buffer.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" })
-}
-
-private const val TAG = "WifiInputLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 45078e3..46ca6e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -22,6 +22,7 @@
import static android.os.BatteryManager.EXTRA_PRESENT;
import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+import static com.android.systemui.Flags.registerBatteryControllerReceiversInCorestartable;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
import android.annotation.WorkerThread;
@@ -151,7 +152,9 @@
@Override
public void init() {
mLogger.logBatteryControllerInit(this, mHasReceivedBattery);
- registerReceiver();
+ if (!registerBatteryControllerReceiversInCorestartable()) {
+ registerReceiver();
+ }
if (!mHasReceivedBattery) {
// Get initial state. Relying on Sticky behavior until API for getting info.
Intent intent = mContext.registerReceiver(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerStartable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerStartable.java
new file mode 100644
index 0000000..7f601c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerStartable.java
@@ -0,0 +1,74 @@
+/*
+ * 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.systemui.statusbar.policy;
+
+import static com.android.systemui.Flags.registerBatteryControllerReceiversInCorestartable;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
+import android.os.PowerManager;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/** A {@link CoreStartable} responsible for registering the receivers for
+ * {@link BatteryControllerImpl}.
+ */
+@SysUISingleton
+public class BatteryControllerStartable implements CoreStartable {
+
+ private final BatteryController mBatteryController;
+ private final Executor mBackgroundExecutor;
+
+ private static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
+
+ protected final BroadcastDispatcher mBroadcastDispatcher;
+ @Inject
+ public BatteryControllerStartable(
+ BatteryController batteryController,
+ BroadcastDispatcher broadcastDispatcher,
+ @Background Executor backgroundExecutor) {
+ mBatteryController = batteryController;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mBackgroundExecutor = backgroundExecutor;
+ }
+
+ private void registerReceiver() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+ filter.addAction(ACTION_LEVEL_TEST);
+ filter.addAction(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
+ mBroadcastDispatcher.registerReceiver((BroadcastReceiver) mBatteryController, filter);
+ }
+
+ @Override
+ public void start() {
+ if (registerBatteryControllerReceiversInCorestartable()
+ && mBatteryController instanceof BatteryControllerImpl) {
+ mBackgroundExecutor.execute(() -> registerReceiver());
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index 3522850..37ef1f2 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -32,8 +32,6 @@
import com.android.systemui.unfold.data.repository.FoldStateRepositoryImpl
import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository
import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
-import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
-import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractorImpl
import com.android.systemui.unfold.system.SystemUnfoldSharedModule
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.updates.FoldStateProvider
@@ -186,8 +184,6 @@
interface Bindings {
@Binds fun bindRepository(impl: UnfoldTransitionRepositoryImpl): UnfoldTransitionRepository
- @Binds fun bindInteractor(impl: UnfoldTransitionInteractorImpl): UnfoldTransitionInteractor
-
@Binds fun bindFoldStateRepository(impl: FoldStateRepositoryImpl): FoldStateRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt
index 0d3682c..fbbd2b9 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt
@@ -15,9 +15,11 @@
*/
package com.android.systemui.unfold.data.repository
+import androidx.annotation.FloatRange
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished
+import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionInProgress
import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionStarted
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
@@ -42,6 +44,10 @@
sealed class UnfoldTransitionStatus {
/** Status that is sent when fold or unfold transition is in started state */
data object TransitionStarted : UnfoldTransitionStatus()
+ /** Status that is sent while fold or unfold transition is in progress */
+ data class TransitionInProgress(
+ @FloatRange(from = 0.0, to = 1.0) val progress: Float,
+ ) : UnfoldTransitionStatus()
/** Status that is sent when fold or unfold transition is finished */
data object TransitionFinished : UnfoldTransitionStatus()
}
@@ -66,6 +72,10 @@
trySend(TransitionStarted)
}
+ override fun onTransitionProgress(progress: Float) {
+ trySend(TransitionInProgress(progress))
+ }
+
override fun onTransitionFinished() {
trySend(TransitionFinished)
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
index 3e2e564..03499cb 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
@@ -15,40 +15,79 @@
*/
package com.android.systemui.unfold.domain.interactor
+import android.view.View
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository
import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished
+import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionInProgress
import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionStarted
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
/**
* Contains business-logic related to fold-unfold transitions while interacting with
* [UnfoldTransitionRepository]
*/
-interface UnfoldTransitionInteractor {
+@SysUISingleton
+class UnfoldTransitionInteractor
+@Inject
+constructor(
+ private val repository: UnfoldTransitionRepository,
+ private val configurationInteractor: ConfigurationInteractor,
+) {
/** Returns availability of fold/unfold transitions on the device */
val isAvailable: Boolean
-
- /** Suspends and waits for a fold/unfold transition to finish */
- suspend fun waitForTransitionFinish()
-
- /** Suspends and waits for a fold/unfold transition to start */
- suspend fun waitForTransitionStart()
-}
-
-class UnfoldTransitionInteractorImpl
-@Inject
-constructor(private val repository: UnfoldTransitionRepository) : UnfoldTransitionInteractor {
-
- override val isAvailable: Boolean
get() = repository.isAvailable
- override suspend fun waitForTransitionFinish() {
+ /**
+ * This mapping emits 1 when the device is completely unfolded and 0.0 when the device is
+ * completely folded.
+ */
+ private val unfoldProgress: Flow<Float> =
+ repository.transitionStatus
+ .map { (it as? TransitionInProgress)?.progress ?: 1f }
+ .onStart { emit(1f) }
+ .distinctUntilChanged()
+
+ /**
+ * Amount of X-axis translation to apply to various elements as the unfolded foldable is folded
+ * slightly, in pixels.
+ *
+ * @param isOnStartSide Whether the consumer wishes to get a translation amount that's suitable
+ * for an element that's on the start-side (left hand-side in left-to-right layouts); if
+ * `true`, the values will provide positive translations to push the left-hand-side element
+ * towards the foldable hinge; if `false`, the values will be inverted to provide negative
+ * translations to push the right-hand-side element towards the foldable hinge. Note that this
+ * method already accounts for left-to-right vs. right-to-left layout directions.
+ */
+ fun unfoldTranslationX(isOnStartSide: Boolean): Flow<Float> {
+ return combine(
+ unfoldProgress,
+ configurationInteractor.dimensionPixelSize(R.dimen.notification_side_paddings),
+ configurationInteractor.layoutDirection.map {
+ if (it == View.LAYOUT_DIRECTION_RTL) -1 else 1
+ },
+ ) { unfoldedAmount, max, layoutDirectionMultiplier ->
+ val sideMultiplier = if (isOnStartSide) 1 else -1
+ max * (1 - unfoldedAmount) * sideMultiplier * layoutDirectionMultiplier
+ }
+ }
+
+ /** Suspends and waits for a fold/unfold transition to finish */
+ suspend fun waitForTransitionFinish() {
repository.transitionStatus.filter { it is TransitionFinished }.first()
}
- override suspend fun waitForTransitionStart() {
+ /** Suspends and waits for a fold/unfold transition to start */
+ suspend fun waitForTransitionStart() {
repository.transitionStatus.filter { it is TransitionStarted }.first()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt
new file mode 100644
index 0000000..ee00e8b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.qs.ReduceBrightColorsController
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onStart
+
+fun ReduceBrightColorsController.isEnabled(): Flow<Boolean> {
+ return conflatedCallbackFlow {
+ val callback =
+ object : ReduceBrightColorsController.Listener {
+ override fun onActivated(activated: Boolean) {
+ trySend(activated)
+ }
+ }
+ addCallback(callback)
+ awaitClose { removeCallback(callback) }
+ }
+ .onStart { emit(isReduceBrightColorsActivated) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Util.java b/packages/SystemUI/src/com/android/systemui/volume/Util.java
index 7edb5a5..df19013 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Util.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Util.java
@@ -17,6 +17,7 @@
package com.android.systemui.volume;
import android.media.AudioManager;
+import android.util.MathUtils;
import android.view.View;
/**
@@ -46,4 +47,27 @@
if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return;
v.setVisibility(vis ? View.VISIBLE : View.GONE);
}
+
+ /**
+ * Translates a value from one range to another.
+ *
+ * ```
+ * Given: currentValue=3, currentRange=[0, 8], targetRange=[0, 100]
+ * Result: 37.5
+ * ```
+ */
+ public static float translateToRange(float value,
+ float valueRangeStart,
+ float valueRangeEnd,
+ float targetRangeStart,
+ float targetRangeEnd) {
+ float currentRangeLength = valueRangeEnd - valueRangeStart;
+ float targetRangeLength = targetRangeEnd - targetRangeStart;
+ if (currentRangeLength == 0f || targetRangeLength == 0f) {
+ return targetRangeStart;
+ }
+ float valueFraction = (value - valueRangeStart) / currentRangeLength;
+ return MathUtils.constrain(targetRangeStart + valueFraction * targetRangeLength,
+ targetRangeStart, targetRangeEnd);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 1688b0b..2245541 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -137,13 +137,13 @@
import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
import com.android.systemui.volume.ui.navigation.VolumeNavigator;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
+import dagger.Lazy;
+
/**
* Visual presentation of the volume dialog.
*
@@ -166,6 +166,7 @@
private static final int DRAWER_ANIMATION_DURATION_SHORT = 175;
private static final int DRAWER_ANIMATION_DURATION = 250;
+ private static final int DISPLAY_RANGE_MULTIPLIER = 100;
/** Shows volume dialog show animation. */
private static final String TYPE_SHOW = "show";
@@ -826,12 +827,14 @@
writer.print(" mSilentMode: "); writer.println(mSilentMode);
}
- private static int getImpliedLevel(SeekBar seekBar, int progress) {
- final int m = seekBar.getMax();
- final int n = m / 100 - 1;
- final int level = progress == 0 ? 0
- : progress == m ? (m / 100) : (1 + (int) ((progress / (float) m) * n));
- return level;
+ private static int getVolumeFromProgress(StreamState state, SeekBar seekBar, int progress) {
+ return (int) Util.translateToRange(progress, seekBar.getMin(), seekBar.getMax(),
+ state.levelMin, state.levelMax);
+ }
+
+ private static int getProgressFromVolume(StreamState state, SeekBar seekBar, int volume) {
+ return (int) Util.translateToRange(volume, state.levelMin, state.levelMax, seekBar.getMin(),
+ seekBar.getMax());
}
@SuppressLint("InflateParams")
@@ -854,6 +857,8 @@
addSliderHapticsToRow(row);
row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
row.number = row.view.findViewById(R.id.volume_number);
+ row.slider.setAccessibilityDelegate(
+ new VolumeDialogSeekBarAccessibilityDelegate(DISPLAY_RANGE_MULTIPLIER));
row.anim = null;
@@ -1916,12 +1921,12 @@
: false;
// update slider max
- final int max = ss.levelMax * 100;
+ final int max = ss.levelMax * DISPLAY_RANGE_MULTIPLIER;
if (max != row.slider.getMax()) {
row.slider.setMax(max);
}
// update slider min
- final int min = ss.levelMin * 100;
+ final int min = ss.levelMin * DISPLAY_RANGE_MULTIPLIER;
if (min != row.slider.getMin()) {
row.slider.setMin(min);
}
@@ -2069,7 +2074,7 @@
return; // don't update if user is sliding
}
final int progress = row.slider.getProgress();
- final int level = getImpliedLevel(row.slider, progress);
+ final int level = getVolumeFromProgress(row.ss, row.slider, progress);
final boolean rowVisible = row.view.getVisibility() == VISIBLE;
final boolean inGracePeriod = (SystemClock.uptimeMillis() - row.userAttempt)
< USER_ATTEMPT_GRACE_PERIOD;
@@ -2085,7 +2090,7 @@
return; // don't clamp if visible
}
}
- final int newProgress = vlevel * 100;
+ final int newProgress = getProgressFromVolume(row.ss, row.slider, vlevel);
if (progress != newProgress) {
if (mShowing && rowVisible) {
// animate!
@@ -2530,13 +2535,13 @@
+ " onProgressChanged " + progress + " fromUser=" + fromUser);
if (!fromUser) return;
if (mRow.ss.levelMin > 0) {
- final int minProgress = mRow.ss.levelMin * 100;
+ final int minProgress = getProgressFromVolume(mRow.ss, seekBar, mRow.ss.levelMin);
if (progress < minProgress) {
seekBar.setProgress(minProgress);
progress = minProgress;
}
}
- final int userLevel = getImpliedLevel(seekBar, progress);
+ final int userLevel = getVolumeFromProgress(mRow.ss, seekBar, progress);
if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
mRow.userAttempt = SystemClock.uptimeMillis();
if (mRow.requestedLevel != userLevel) {
@@ -2569,7 +2574,7 @@
}
mRow.tracking = false;
mRow.userAttempt = SystemClock.uptimeMillis();
- final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
+ final int userLevel = getVolumeFromProgress(mRow.ss, seekBar, seekBar.getProgress());
Events.writeEvent(Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel);
if (mRow.ss.level != userLevel) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow),
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogSeekBarAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogSeekBarAccessibilityDelegate.kt
new file mode 100644
index 0000000..cd31a95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogSeekBarAccessibilityDelegate.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume
+
+import android.os.Bundle
+import android.view.View
+import android.view.View.AccessibilityDelegate
+import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.SeekBar
+import com.android.internal.R
+
+class VolumeDialogSeekBarAccessibilityDelegate(
+ private val accessibilityStep: Int,
+) : AccessibilityDelegate() {
+
+ override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
+ require(host is SeekBar) { "This class only works with the SeekBar" }
+ val seekBar: SeekBar = host
+ if (
+ action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD ||
+ action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD
+ ) {
+ var increment = accessibilityStep
+ if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
+ increment = -increment
+ }
+
+ return super.performAccessibilityAction(
+ host,
+ R.id.accessibilityActionSetProgress,
+ Bundle().apply {
+ putFloat(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE,
+ (seekBar.progress + increment).coerceIn(seekBar.min, seekBar.max).toFloat(),
+ )
+ },
+ )
+ }
+ return super.performAccessibilityAction(host, action, args)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ButtonViewModel.kt
index 754d258..4d11f44 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ButtonViewModel.kt
@@ -1,17 +1,17 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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
+ * 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.
+ * 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.volume.panel.component.button.ui.viewmodel
@@ -22,4 +22,5 @@
data class ButtonViewModel(
val icon: Icon,
val label: CharSequence,
+ val isActive: Boolean = true,
)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
deleted file mode 100644
index 6c47aec..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.panel.component.button.ui.viewmodel
-
-import com.android.systemui.common.shared.model.Icon
-
-data class ToggleButtonViewModel(
- val isChecked: Boolean,
- val icon: Icon,
- val label: CharSequence,
-)
-
-fun ToggleButtonViewModel.toButtonViewModel(): ButtonViewModel =
- ButtonViewModel(icon = icon, label = label)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt
index 01421f8..ca5aef8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt
@@ -21,7 +21,7 @@
import com.android.settingslib.view.accessibility.domain.interactor.CaptioningInteractor
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
-import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
@@ -43,11 +43,11 @@
private val uiEventLogger: UiEventLogger,
) {
- val buttonViewModel: StateFlow<ToggleButtonViewModel?> =
+ val buttonViewModel: StateFlow<ButtonViewModel?> =
captioningInteractor.isSystemAudioCaptioningEnabled
.map { isEnabled ->
- ToggleButtonViewModel(
- isChecked = isEnabled,
+ ButtonViewModel(
+ isActive = isEnabled,
icon =
Icon.Resource(
if (isEnabled) R.drawable.ic_volume_odi_captions
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 6b237f8e..f19fa20 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -27,6 +27,7 @@
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlayback
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.Result
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -34,6 +35,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
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
@@ -53,32 +55,40 @@
private val uiEventLogger: UiEventLogger,
) {
- private val sessionWithPlayback: StateFlow<SessionWithPlayback?> =
+ private val sessionWithPlayback: StateFlow<Result<SessionWithPlayback?>> =
interactor.defaultActiveMediaSession
.flatMapLatest { session ->
if (session == null) {
- flowOf(null)
+ flowOf(Result.Data<SessionWithPlayback?>(null))
} else {
- mediaDeviceSessionInteractor.playbackState(session).map { playback ->
- playback?.let { SessionWithPlayback(session, it) }
- }
+ mediaDeviceSessionInteractor
+ .playbackState(session)
+ .map { playback ->
+ playback?.let {
+ Result.Data<SessionWithPlayback?>(SessionWithPlayback(session, it))
+ }
+ }
+ .filterNotNull()
}
}
.stateIn(
coroutineScope,
SharingStarted.Eagerly,
- null,
+ Result.Loading(),
)
val connectedDeviceViewModel: StateFlow<ConnectedDeviceViewModel?> =
combine(sessionWithPlayback, interactor.currentConnectedDevice) {
mediaDeviceSession,
currentConnectedDevice ->
+ if (mediaDeviceSession !is Result.Data) {
+ return@combine null
+ }
ConnectedDeviceViewModel(
- if (mediaDeviceSession?.playback?.isActive == true) {
+ if (mediaDeviceSession.data?.playback?.isActive == true) {
context.getString(
R.string.media_output_label_title,
- mediaDeviceSession.session.appLabel
+ mediaDeviceSession.data.session.appLabel
)
} else {
context.getString(R.string.media_output_title_without_playing)
@@ -96,7 +106,10 @@
combine(sessionWithPlayback, interactor.currentConnectedDevice) {
mediaDeviceSession,
currentConnectedDevice ->
- if (mediaDeviceSession?.playback?.isActive == true) {
+ if (mediaDeviceSession !is Result.Data) {
+ return@combine null
+ }
+ if (mediaDeviceSession.data?.playback?.isActive == true) {
val icon =
currentConnectedDevice?.icon?.let { Icon.Loaded(it, null) }
?: Icon.Resource(
@@ -130,6 +143,7 @@
fun onBarClick(expandable: Expandable) {
uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED)
- actionsInteractor.onBarClick(sessionWithPlayback.value, expandable)
+ val result = sessionWithPlayback.value
+ actionsInteractor.onBarClick((result as? Result.Data)?.data, expandable)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
index 9f9275b..f7a602e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
@@ -16,13 +16,10 @@
package com.android.systemui.volume.panel.component.spatial.ui.viewmodel
-import com.android.systemui.common.shared.model.Color
-import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
data class SpatialAudioButtonViewModel(
val model: SpatialAudioEnabledModel,
- val button: ToggleButtonViewModel,
- val iconColor: Color,
- val labelColor: Color,
+ val button: ButtonViewModel,
)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
index 4ecdd46..4b2d26a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
@@ -18,13 +18,10 @@
import android.content.Context
import com.android.internal.logging.UiEventLogger
-import com.android.systemui.common.shared.model.Color
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
-import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
-import com.android.systemui.volume.panel.component.button.ui.viewmodel.toButtonViewModel
import com.android.systemui.volume.panel.component.spatial.domain.SpatialAudioAvailabilityCriteria
import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
@@ -54,8 +51,8 @@
val spatialAudioButton: StateFlow<ButtonViewModel?> =
interactor.isEnabled
.map {
- it.toViewModel(true)
- .toButtonViewModel()
+ val isChecked = it is SpatialAudioEnabledModel.SpatialAudioEnabled
+ it.toViewModel(isChecked)
.copy(label = context.getString(R.string.volume_panel_spatial_audio_title))
}
.stateIn(scope, SharingStarted.Eagerly, null)
@@ -77,28 +74,8 @@
}
.map { isEnabled ->
val isChecked = isEnabled == currentIsEnabled
- val buttonViewModel: ToggleButtonViewModel =
- isEnabled.toViewModel(isChecked)
- SpatialAudioButtonViewModel(
- button = buttonViewModel,
- model = isEnabled,
- iconColor =
- Color.Attribute(
- if (isChecked) {
- com.android.internal.R.attr.materialColorOnPrimaryContainer
- } else {
- com.android.internal.R.attr.materialColorOnSurfaceVariant
- }
- ),
- labelColor =
- Color.Attribute(
- if (isChecked) {
- com.android.internal.R.attr.materialColorOnSurface
- } else {
- com.android.internal.R.attr.materialColorOnSurfaceVariant
- }
- ),
- )
+ val buttonViewModel: ButtonViewModel = isEnabled.toViewModel(isChecked)
+ SpatialAudioButtonViewModel(button = buttonViewModel, model = isEnabled)
}
}
.stateIn(scope, SharingStarted.Eagerly, emptyList())
@@ -120,26 +97,26 @@
scope.launch { interactor.setEnabled(model) }
}
- private fun SpatialAudioEnabledModel.toViewModel(isChecked: Boolean): ToggleButtonViewModel {
+ private fun SpatialAudioEnabledModel.toViewModel(isChecked: Boolean): ButtonViewModel {
if (this is SpatialAudioEnabledModel.HeadTrackingEnabled) {
- return ToggleButtonViewModel(
- isChecked = isChecked,
+ return ButtonViewModel(
+ isActive = isChecked,
icon = Icon.Resource(R.drawable.ic_head_tracking, contentDescription = null),
label = context.getString(R.string.volume_panel_spatial_audio_tracking)
)
}
if (this is SpatialAudioEnabledModel.SpatialAudioEnabled) {
- return ToggleButtonViewModel(
- isChecked = isChecked,
+ return ButtonViewModel(
+ isActive = isChecked,
icon = Icon.Resource(R.drawable.ic_spatial_audio, contentDescription = null),
label = context.getString(R.string.volume_panel_spatial_audio_fixed)
)
}
if (this is SpatialAudioEnabledModel.Disabled) {
- return ToggleButtonViewModel(
- isChecked = isChecked,
+ return ButtonViewModel(
+ isActive = isChecked,
icon = Icon.Resource(R.drawable.ic_spatial_audio_off, contentDescription = null),
label = context.getString(R.string.volume_panel_spatial_audio_off)
)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
new file mode 100644
index 0000000..ac8092c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.volume.domain.interactor
+
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import com.android.settingslib.volume.data.repository.AudioRepository
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.isTheSameSession
+import com.android.systemui.volume.panel.component.volume.domain.model.SliderType
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combineTransform
+import kotlinx.coroutines.flow.stateIn
+
+/** Provides volume sliders to show in the Volume Panel. */
+@VolumePanelScope
+class AudioSlidersInteractor
+@Inject
+constructor(
+ @VolumePanelScope scope: CoroutineScope,
+ mediaOutputInteractor: MediaOutputInteractor,
+ audioRepository: AudioRepository,
+) {
+
+ val volumePanelSliders: StateFlow<List<SliderType>> =
+ combineTransform(
+ mediaOutputInteractor.activeMediaDeviceSessions,
+ mediaOutputInteractor.defaultActiveMediaSession,
+ audioRepository.communicationDevice,
+ ) { activeSessions, defaultSession, communicationDevice ->
+ coroutineScope {
+ val viewModels = buildList {
+ if (defaultSession?.isTheSameSession(activeSessions.remote) == true) {
+ addSession(activeSessions.remote)
+ addStream(AudioManager.STREAM_MUSIC)
+ } else {
+ addStream(AudioManager.STREAM_MUSIC)
+ addSession(activeSessions.remote)
+ }
+
+ if (communicationDevice?.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
+ addStream(AudioManager.STREAM_BLUETOOTH_SCO)
+ } else {
+ addStream(AudioManager.STREAM_VOICE_CALL)
+ }
+ addStream(AudioManager.STREAM_RING)
+ addStream(AudioManager.STREAM_NOTIFICATION)
+ addStream(AudioManager.STREAM_ALARM)
+ }
+ emit(viewModels)
+ }
+ }
+ .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+ private fun MutableList<SliderType>.addSession(remoteMediaDeviceSession: MediaDeviceSession?) {
+ if (remoteMediaDeviceSession?.canAdjustVolume == true) {
+ add(SliderType.MediaDeviceCast(remoteMediaDeviceSession))
+ }
+ }
+
+ private fun MutableList<SliderType>.addStream(stream: Int) {
+ add(SliderType.Stream(AudioStream(stream)))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
index b97123b..6129ce5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.volume.domain.model
import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
/** The type of volume slider that can be shown at the UI. */
sealed interface SliderType {
@@ -25,5 +26,5 @@
data class Stream(val stream: AudioStream) : SliderType
/** The represents media device casting volume. */
- data object MediaDeviceCast : SliderType
+ data class MediaDeviceCast(val session: MediaDeviceSession) : SliderType
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index c8cd6fd..ee642a6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -53,6 +53,7 @@
mapOf(
AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_music_note,
AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_call,
+ AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to R.drawable.ic_call,
AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_ring_volume,
AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_ringer,
AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_alarm,
@@ -61,6 +62,7 @@
mapOf(
AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_music,
AudioStream(AudioManager.STREAM_VOICE_CALL) to R.string.stream_voice_call,
+ AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to R.string.stream_voice_call,
AudioStream(AudioManager.STREAM_RING) to R.string.stream_ring,
AudioStream(AudioManager.STREAM_NOTIFICATION) to R.string.stream_notification,
AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm,
@@ -78,6 +80,8 @@
VolumePanelUiEvent.VOLUME_PANEL_MUSIC_SLIDER_TOUCHED,
AudioStream(AudioManager.STREAM_VOICE_CALL) to
VolumePanelUiEvent.VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED,
+ AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to
+ VolumePanelUiEvent.VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED,
AudioStream(AudioManager.STREAM_RING) to
VolumePanelUiEvent.VOLUME_PANEL_RING_SLIDER_TOUCHED,
AudioStream(AudioManager.STREAM_NOTIFICATION) to
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index 09e56c1..741f5cf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -16,12 +16,12 @@
package com.android.systemui.volume.panel.component.volume.ui.viewmodel
-import android.media.AudioManager
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
-import com.android.systemui.volume.panel.component.mediaoutput.shared.model.isTheSameSession
+import com.android.systemui.volume.panel.component.volume.domain.interactor.AudioSlidersInteractor
+import com.android.systemui.volume.panel.component.volume.domain.model.SliderType
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
@@ -33,12 +33,12 @@
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
/**
@@ -55,28 +55,21 @@
private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory,
private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory,
+ streamsInteractor: AudioSlidersInteractor,
) {
val sliderViewModels: StateFlow<List<SliderViewModel>> =
- combineTransform(
- mediaOutputInteractor.activeMediaDeviceSessions,
- mediaOutputInteractor.defaultActiveMediaSession,
- ) { activeSessions, defaultSession ->
+ streamsInteractor.volumePanelSliders
+ .transformLatest { sliderTypes ->
coroutineScope {
- val viewModels = buildList {
- if (defaultSession?.isTheSameSession(activeSessions.remote) == true) {
- addRemoteViewModelIfNeeded(this, activeSessions.remote)
- addStreamViewModel(this, AudioManager.STREAM_MUSIC)
- } else {
- addStreamViewModel(this, AudioManager.STREAM_MUSIC)
- addRemoteViewModelIfNeeded(this, activeSessions.remote)
+ val viewModels =
+ sliderTypes.map { type ->
+ when (type) {
+ is SliderType.Stream -> createStreamViewModel(type.stream)
+ is SliderType.MediaDeviceCast ->
+ createSessionViewModel(type.session)
+ }
}
-
- addStreamViewModel(this, AudioManager.STREAM_VOICE_CALL)
- addStreamViewModel(this, AudioManager.STREAM_RING)
- addStreamViewModel(this, AudioManager.STREAM_NOTIFICATION)
- addStreamViewModel(this, AudioManager.STREAM_ALARM)
- }
emit(viewModels)
}
}
@@ -98,29 +91,18 @@
scope.launch { mutableIsExpanded.emit(isExpanded) }
}
- private fun CoroutineScope.addRemoteViewModelIfNeeded(
- list: MutableList<SliderViewModel>,
- remoteMediaDeviceSession: MediaDeviceSession?
- ) {
- if (remoteMediaDeviceSession?.canAdjustVolume == true) {
- val viewModel =
- castVolumeSliderViewModelFactory.create(
- remoteMediaDeviceSession,
- this,
- )
- list.add(viewModel)
- }
+ private fun CoroutineScope.createSessionViewModel(
+ session: MediaDeviceSession
+ ): CastVolumeSliderViewModel {
+ return castVolumeSliderViewModelFactory.create(session, this)
}
- private fun CoroutineScope.addStreamViewModel(
- list: MutableList<SliderViewModel>,
- stream: Int,
- ) {
- val viewModel =
- streamSliderViewModelFactory.create(
- AudioStreamSliderViewModel.FactoryAudioStreamWrapper(AudioStream(stream)),
- this,
- )
- list.add(viewModel)
+ private fun CoroutineScope.createStreamViewModel(
+ stream: AudioStream,
+ ): AudioStreamSliderViewModel {
+ return streamSliderViewModelFactory.create(
+ AudioStreamSliderViewModel.FactoryAudioStreamWrapper(stream),
+ this,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
similarity index 62%
copy from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
copy to packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
index b84b01e..8793538 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.dagger
+package com.android.systemui.volume.panel.shared.model
-import javax.inject.Qualifier
+/** Models a loadable result */
+sealed interface Result<T> {
-/** Wifi logs for inputs into [WifiRepositoryViaTrackerLib]. */
-@Qualifier
-@MustBeDocumented
[email protected](AnnotationRetention.RUNTIME)
-annotation class WifiTrackerLibInputLog
+ /** The data is still loading */
+ class Loading<T> : Result<T>
+
+ /** The data is loaded successfully */
+ data class Data<T>(val data: T) : Result<T>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index e48b639..263ddc1 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -43,6 +43,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.CoreStartable;
+import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.qualifiers.Main;
@@ -55,6 +56,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.onehanded.OneHanded;
@@ -124,6 +126,8 @@
private final UserTracker mUserTracker;
private final DisplayTracker mDisplayTracker;
private final NoteTaskInitializer mNoteTaskInitializer;
+ private final CommunalTransitionViewModel mCommunalTransitionViewModel;
+ private final JavaAdapter mJavaAdapter;
private final Executor mSysUiMainExecutor;
// Listeners and callbacks. Note that we prefer member variable over anonymous class here to
@@ -187,6 +191,8 @@
UserTracker userTracker,
DisplayTracker displayTracker,
NoteTaskInitializer noteTaskInitializer,
+ CommunalTransitionViewModel communalTransitionViewModel,
+ JavaAdapter javaAdapter,
@Main Executor sysUiMainExecutor) {
mContext = context;
mShell = shell;
@@ -205,6 +211,8 @@
mUserTracker = userTracker;
mDisplayTracker = displayTracker;
mNoteTaskInitializer = noteTaskInitializer;
+ mCommunalTransitionViewModel = communalTransitionViewModel;
+ mJavaAdapter = javaAdapter;
mSysUiMainExecutor = sysUiMainExecutor;
}
@@ -381,6 +389,8 @@
void initRecentTasks(RecentTasks recentTasks) {
recentTasks.addAnimationStateListener(mSysUiMainExecutor,
mCommandQueue::onRecentsAnimationStateChanged);
+ mJavaAdapter.alwaysCollectFlow(mCommunalTransitionViewModel.getRecentsBackgroundColor(),
+ recentTasks::setTransitionBackgroundColor);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
index ffedb30..52af8fb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
@@ -18,11 +18,12 @@
import static com.google.common.truth.Truth.assertThat;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
index f8fdd8d..6512e70 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
@@ -16,7 +16,7 @@
package com.android.keyguard
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index 08c1de1..303ae97 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -50,10 +50,11 @@
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.text.TextUtils;
+import androidx.test.filters.SmallTest;
+
import com.android.keyguard.logging.CarrierTextManagerLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index a249961..319b615 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -205,9 +205,9 @@
when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
when(mClockEventController.getClock()).thenReturn(mClockController);
when(mSmallClockController.getConfig())
- .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false));
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false, false));
when(mLargeClockController.getConfig())
- .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false));
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false, false));
mSliceView = new View(getContext());
when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 9d81b96..99b5a4b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -272,9 +272,9 @@
assertEquals(View.VISIBLE, mFakeDateView.getVisibility());
when(mSmallClockController.getConfig())
- .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false));
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false, true));
when(mLargeClockController.getConfig())
- .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false));
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false, true));
verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
listenerArgumentCaptor.getValue().onCurrentClockChanged();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index b4a9d40..e2063d2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -30,7 +30,6 @@
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.AttributeSet;
@@ -40,6 +39,8 @@
import android.widget.FrameLayout;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.clocks.ClockController;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index 93e7602..6228ff8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -27,12 +27,13 @@
import static org.mockito.Mockito.when;
import android.hardware.biometrics.BiometricSourceType;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.text.Editable;
import android.text.TextWatcher;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 9b5364e..7151c42 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -115,6 +115,7 @@
@Test
fun onViewAttached() {
underTest.onViewAttached()
+ verify(keyguardMessageAreaController).setIsVisible(true)
verify(keyguardMessageAreaController)
.setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
verify(keyguardUpdateMonitor)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index e71490c..acae913 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -103,7 +103,9 @@
@Test
fun onViewAttached() {
+ Mockito.reset(keyguardMessageAreaController)
underTest.onViewAttached()
+ Mockito.verify(keyguardMessageAreaController).setIsVisible(true)
Mockito.verify(keyguardUpdateMonitor)
.registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
Mockito.verify(keyguardMessageAreaController)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
index edb910a..c566826 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
@@ -23,12 +23,13 @@
import android.content.pm.PackageManager;
import android.os.Handler;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index 6654a6c..a0e8065 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -17,7 +17,6 @@
import android.graphics.Color;
import android.net.Uri;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
@@ -27,10 +26,11 @@
import androidx.slice.SliceSpecs;
import androidx.slice.builders.ListBuilder;
import androidx.slice.widget.RowContent;
+import androidx.test.filters.SmallTest;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.res.R;
import org.junit.Assert;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
index e6b6964..ed61ee12 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
@@ -1,8 +1,8 @@
package com.android.keyguard
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import org.junit.Assert.assertEquals
import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 11fe862..0696a4b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -31,11 +31,12 @@
import android.animation.AnimatorTestRule;
import android.platform.test.annotations.DisableFlags;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.app.animation.Interpolators;
import com.android.systemui.Flags;
import com.android.systemui.animation.ViewHierarchyAnimator;
@@ -98,7 +99,7 @@
public void updatePosition_primaryClockAnimation() {
ClockController mockClock = mock(ClockController.class);
when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
- when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", false, true));
+ when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", false, true, false));
mController.updatePosition(10, 15, 20f, true);
@@ -113,7 +114,7 @@
public void updatePosition_alternateClockAnimation() {
ClockController mockClock = mock(ClockController.class);
when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
- when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", true, true));
+ when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", true, true, false));
mController.updatePosition(10, 15, 20f, true);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
index 17f77aa..3b57d8f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
@@ -16,9 +16,9 @@
package com.android.keyguard
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
import com.android.systemui.power.shared.model.ScreenPowerState
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.test.runCurrent
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index 86439e5..afd2034 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -1,6 +1,6 @@
package com.android.keyguard
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.LayoutInflater
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 5af0c1f..68b6d9d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -52,7 +52,6 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -107,11 +106,12 @@
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.text.TextUtils;
+import androidx.test.filters.SmallTest;
+
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.jank.InteractionJankMonitor;
@@ -158,6 +158,7 @@
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.internal.util.reflection.FieldSetter;
@@ -809,31 +810,6 @@
}
@Test
- public void whenFaceAuthenticated_biometricAuthenticatedCallback_beforeUpdatingFpState() {
- // GIVEN listening for UDFPS fingerprint
- when(mAuthController.isUdfpsSupported()).thenReturn(true);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
- mTestableLooper.processAllMessages();
- keyguardIsVisible();
- final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
- mKeyguardUpdateMonitor.mFingerprintCancelSignal = fpCancel;
-
- // WHEN face is authenticated
- when(mFaceAuthInteractor.isAuthenticated()).thenReturn(true);
- when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(true);
- when(mFaceAuthInteractor.isLockedOut()).thenReturn(false);
- mKeyguardUpdateMonitor.onFaceAuthenticated(0, true);
- mTestableLooper.processAllMessages();
-
- // THEN verify keyguardUpdateMonitorCallback receives an onAuthenticated callback
- // before cancelling the fingerprint request
- InOrder inOrder = inOrder(mTestCallback, fpCancel);
- inOrder.verify(mTestCallback).onBiometricAuthenticated(
- eq(0), eq(BiometricSourceType.FACE), eq(true));
- inOrder.verify(fpCancel).cancel();
- }
-
- @Test
public void whenDetectFingerprint_biometricDetectCallback() {
ArgumentCaptor<FingerprintManager.FingerprintDetectionCallback> fpDetectCallbackCaptor =
ArgumentCaptor.forClass(FingerprintManager.FingerprintDetectionCallback.class);
@@ -2158,7 +2134,7 @@
null /* trustGrantedMessages */);
// THEN onTrustChanged is called FIRST
- final InOrder inOrder = inOrder(callback);
+ final InOrder inOrder = Mockito.inOrder(callback);
inOrder.verify(callback).onTrustChanged(eq(mSelectedUserInteractor.getSelectedUserId()));
// AND THEN onTrustGrantedForCurrentUser callback called
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
index f924ab4..b09357f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
@@ -36,6 +36,7 @@
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.widget.ImageView;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
@@ -51,6 +52,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -78,6 +80,7 @@
protected final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
protected @Mock DeviceEntryInteractor mDeviceEntryInteractor;
protected @Mock LockIconView mLockIconView;
+ protected @Mock ImageView mLockIcon;
protected @Mock AnimatedStateListDrawable mIconDrawable;
protected @Mock Context mContext;
protected @Mock Resources mResources;
@@ -146,8 +149,10 @@
when(mStatusBarStateController.isDozing()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
- mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR);
- mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
+ if (!SceneContainerFlag.isEnabled()) {
+ mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR);
+ mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
+ }
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
@@ -172,8 +177,7 @@
mFeatureFlags,
mPrimaryBouncerInteractor,
mContext,
- () -> mDeviceEntryInteractor,
- mKosmos.getFakeSceneContainerFlags()
+ () -> mDeviceEntryInteractor
);
}
@@ -225,6 +229,7 @@
protected void setupLockIconViewMocks() {
when(mLockIconView.getResources()).thenReturn(mResources);
when(mLockIconView.getContext()).thenReturn(mContext);
+ when(mLockIconView.getLockIcon()).thenReturn(mLockIcon);
}
protected void resetLockIconView() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java
index 8689842..255c7d9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java
@@ -43,6 +43,7 @@
import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
import com.android.systemui.doze.util.BurnInHelperKt;
+import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.statusbar.StatusBarState;
import org.junit.Test;
@@ -373,7 +374,6 @@
@Test
public void longPress_showBouncer_sceneContainerNotEnabled() {
init(/* useMigrationFlag= */ false);
- mKosmos.getFakeSceneContainerFlags().setEnabled(false);
when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
// WHEN longPress
@@ -385,9 +385,9 @@
}
@Test
+ @EnableSceneContainer
public void longPress_showBouncer() {
init(/* useMigrationFlag= */ false);
- mKosmos.getFakeSceneContainerFlags().setEnabled(true);
when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
// WHEN longPress
@@ -399,9 +399,9 @@
}
@Test
+ @EnableSceneContainer
public void longPress_falsingTriggered_doesNotShowBouncer() {
init(/* useMigrationFlag= */ false);
- mKosmos.getFakeSceneContainerFlags().setEnabled(true);
when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(true);
// WHEN longPress
diff --git a/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java b/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
index 532c59a..d6a5b4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
@@ -28,8 +28,8 @@
import android.content.res.Resources;
import android.graphics.Canvas;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.settingslib.R;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java
index 7c121e1..d7bd59e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java
@@ -31,36 +31,57 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
-import android.testing.AndroidTestingRunner;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import androidx.test.filters.SmallTest;
import com.android.settingslib.SliceBroadcastRelay;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-@RunWith(AndroidTestingRunner.class)
+import java.util.List;
+
+@RunWith(Parameterized.class)
@SmallTest
public class SliceBroadcastRelayHandlerTest extends SysuiTestCase {
+ @Parameterized.Parameters(name = "{0}")
+ public static List<FlagsParameterization> getFlags() {
+ return FlagsParameterization.allCombinationsOf(
+ Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND);
+ }
+
private static final String TEST_ACTION = "com.android.systemui.action.TEST_ACTION";
+ private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
+
private SliceBroadcastRelayHandler mRelayHandler;
private Context mSpyContext;
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
+
+ public SliceBroadcastRelayHandlerTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mSpyContext = spy(mContext);
- mRelayHandler = new SliceBroadcastRelayHandler(mSpyContext, mBroadcastDispatcher);
+ mRelayHandler = new SliceBroadcastRelayHandler(mSpyContext, mBroadcastDispatcher,
+ mBackgroundExecutor);
}
@Test
@@ -80,6 +101,7 @@
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, testUri);
mRelayHandler.handleIntent(intent);
+ mBackgroundExecutor.runAllReady();
verify(mSpyContext).registerReceiver(any(), eq(value), anyInt());
}
@@ -99,12 +121,14 @@
intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value);
mRelayHandler.handleIntent(intent);
+ mBackgroundExecutor.runAllReady();
ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mSpyContext).registerReceiver(relay.capture(), eq(value), anyInt());
intent = new Intent(SliceBroadcastRelay.ACTION_UNREGISTER);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
mRelayHandler.handleIntent(intent);
+ mBackgroundExecutor.runAllReady();
verify(mSpyContext).unregisterReceiver(eq(relay.getValue()));
}
@@ -119,6 +143,7 @@
Intent intent = new Intent(SliceBroadcastRelay.ACTION_UNREGISTER);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
mRelayHandler.handleIntent(intent);
+ mBackgroundExecutor.runAllReady();
// No crash
}
@@ -138,6 +163,7 @@
intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value);
mRelayHandler.handleIntent(intent);
+ mBackgroundExecutor.runAllReady();
ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mSpyContext).registerReceiver(relay.capture(), eq(value), anyInt());
relay.getValue().onReceive(mSpyContext, new Intent(TEST_ACTION));
@@ -146,8 +172,10 @@
}
@Test
- public void testRegisteredWithDispatcher() {
+ @DisableFlags(Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND)
+ public void testRegisteredWithDispatcher_onMainThread() {
mRelayHandler.start();
+ mBackgroundExecutor.runAllReady();
verify(mBroadcastDispatcher)
.registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
@@ -155,6 +183,19 @@
.registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
}
+ @Test
+ @EnableFlags(Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND)
+ public void testRegisteredWithDispatcher_onBackgroundThread() {
+ mRelayHandler.start();
+ mBackgroundExecutor.runAllReady();
+
+ verify(mBroadcastDispatcher)
+ .registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class),
+ eq(mBackgroundExecutor));
+ verify(mSpyContext, never())
+ .registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
+ }
+
public static class Receiver extends BroadcastReceiver {
private static BroadcastReceiver sReceiver;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
index 12f334b..5bc9aa4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
@@ -16,49 +16,84 @@
package com.android.systemui.accessibility;
+import static android.os.Build.HW_TIMEOUT_MULTIPLIER;
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
+import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
import android.window.InputTransferToken;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
public class FullscreenMagnificationControllerTest extends SysuiTestCase {
-
+ private static final long ANIMATION_DURATION_MS = 100L;
+ private static final long WAIT_TIMEOUT_S = 5L * HW_TIMEOUT_MULTIPLIER;
+ private static final long ANIMATION_TIMEOUT_MS =
+ 5L * ANIMATION_DURATION_MS * HW_TIMEOUT_MULTIPLIER;
private FullscreenMagnificationController mFullscreenMagnificationController;
private SurfaceControlViewHost mSurfaceControlViewHost;
+ private ValueAnimator mShowHideBorderAnimator;
+ private SurfaceControl.Transaction mTransaction;
+ private TestableWindowManager mWindowManager;
@Before
public void setUp() {
getInstrumentation().runOnMainSync(() -> mSurfaceControlViewHost =
- new SurfaceControlViewHost(mContext, mContext.getDisplay(),
- new InputTransferToken(), "FullscreenMagnification"));
-
+ spy(new SurfaceControlViewHost(mContext, mContext.getDisplay(),
+ new InputTransferToken(), "FullscreenMagnification")));
Supplier<SurfaceControlViewHost> scvhSupplier = () -> mSurfaceControlViewHost;
+ final WindowManager wm = mContext.getSystemService(WindowManager.class);
+ mWindowManager = new TestableWindowManager(wm);
+ mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+ mTransaction = new SurfaceControl.Transaction();
+ mShowHideBorderAnimator = spy(newNullTargetObjectAnimator());
mFullscreenMagnificationController = new FullscreenMagnificationController(
mContext,
+ mContext.getMainExecutor(),
mContext.getSystemService(AccessibilityManager.class),
mContext.getSystemService(WindowManager.class),
- scvhSupplier);
+ scvhSupplier,
+ mTransaction,
+ mShowHideBorderAnimator);
}
@After
@@ -69,29 +104,143 @@
}
@Test
- public void onFullscreenMagnificationActivationChange_activated_visibleBorder() {
- getInstrumentation().runOnMainSync(
- () -> mFullscreenMagnificationController
- .onFullscreenMagnificationActivationChanged(true)
- );
-
- // Wait for Rects updated.
- waitForIdleSync();
+ public void enableFullscreenMagnification_visibleBorder() throws InterruptedException {
+ CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+ CountDownLatch animationEndLatch = new CountDownLatch(1);
+ mTransaction.addTransactionCommittedListener(
+ Runnable::run, transactionCommittedLatch::countDown);
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animationEndLatch.countDown();
+ }
+ });
+ getInstrumentation().runOnMainSync(() ->
+ //Enable fullscreen magnification
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true));
+ assertTrue("Failed to wait for transaction committed",
+ transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for animation to be finished",
+ animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ verify(mShowHideBorderAnimator).start();
assertThat(mSurfaceControlViewHost.getView().isVisibleToUser()).isTrue();
}
@Test
- public void onFullscreenMagnificationActivationChange_deactivated_invisibleBorder() {
- getInstrumentation().runOnMainSync(
- () -> {
- mFullscreenMagnificationController
- .onFullscreenMagnificationActivationChanged(true);
- mFullscreenMagnificationController
- .onFullscreenMagnificationActivationChanged(false);
+ public void disableFullscreenMagnification_reverseAnimationAndReleaseScvh()
+ throws InterruptedException {
+ CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+ CountDownLatch enableAnimationEndLatch = new CountDownLatch(1);
+ CountDownLatch disableAnimationEndLatch = new CountDownLatch(1);
+ mTransaction.addTransactionCommittedListener(
+ Runnable::run, transactionCommittedLatch::countDown);
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ if (isReverse) {
+ disableAnimationEndLatch.countDown();
+ } else {
+ enableAnimationEndLatch.countDown();
}
- );
+ }
+ });
+ getInstrumentation().runOnMainSync(() ->
+ //Enable fullscreen magnification
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true));
+ assertTrue("Failed to wait for transaction committed",
+ transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for enabling animation to be finished",
+ enableAnimationEndLatch.await(
+ ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ verify(mShowHideBorderAnimator).start();
- assertThat(mSurfaceControlViewHost.getView()).isNull();
+ getInstrumentation().runOnMainSync(() ->
+ // Disable fullscreen magnification
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(false));
+
+ assertTrue("Failed to wait for disabling animation to be finished",
+ disableAnimationEndLatch.await(
+ ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ verify(mShowHideBorderAnimator).reverse();
+ verify(mSurfaceControlViewHost).release();
}
+ @Test
+ public void onFullscreenMagnificationActivationChangeTrue_deactivating_reverseAnimator()
+ throws InterruptedException {
+ // Simulate the hiding border animation is running
+ when(mShowHideBorderAnimator.isRunning()).thenReturn(true);
+ CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+ CountDownLatch animationEndLatch = new CountDownLatch(1);
+ mTransaction.addTransactionCommittedListener(
+ Runnable::run, transactionCommittedLatch::countDown);
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animationEndLatch.countDown();
+ }
+ });
+
+ getInstrumentation().runOnMainSync(
+ () -> mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true));
+
+ assertTrue("Failed to wait for transaction committed",
+ transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for animation to be finished",
+ animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ verify(mShowHideBorderAnimator).reverse();
+ }
+
+ @Test
+ public void onScreenSizeChanged_activated_borderChangedToExpectedSize()
+ throws InterruptedException {
+ CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+ CountDownLatch animationEndLatch = new CountDownLatch(1);
+ mTransaction.addTransactionCommittedListener(
+ Runnable::run, transactionCommittedLatch::countDown);
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animationEndLatch.countDown();
+ }
+ });
+ getInstrumentation().runOnMainSync(() ->
+ //Enable fullscreen magnification
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true));
+ assertTrue("Failed to wait for transaction committed",
+ transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for animation to be finished",
+ animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ final Rect testWindowBounds = new Rect(
+ mWindowManager.getCurrentWindowMetrics().getBounds());
+ testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+ testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+ mWindowManager.setWindowBounds(testWindowBounds);
+
+ getInstrumentation().runOnMainSync(() ->
+ mFullscreenMagnificationController.onConfigurationChanged(
+ ActivityInfo.CONFIG_SCREEN_SIZE));
+
+ int borderOffset = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen_with_offset)
+ - mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen);
+ final int newWidth = testWindowBounds.width() + 2 * borderOffset;
+ final int newHeight = testWindowBounds.height() + 2 * borderOffset;
+ verify(mSurfaceControlViewHost).relayout(newWidth, newHeight);
+ }
+
+ private ValueAnimator newNullTargetObjectAnimator() {
+ final ValueAnimator animator =
+ ObjectAnimator.ofFloat(/* target= */ null, View.ALPHA, 0f, 1f);
+ Interpolator interpolator = new DecelerateInterpolator(2.5f);
+ animator.setInterpolator(interpolator);
+ animator.setDuration(ANIMATION_DURATION_MS);
+ return animator;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 41d5d5d..25e5470 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -101,7 +101,7 @@
}).when(mAccessibilityManager).setMagnificationConnection(
any(IMagnificationConnection.class));
mMagnification = new Magnification(getContext(),
- getContext().getMainThreadHandler(), mCommandQueue,
+ getContext().getMainThreadHandler(), getContext().getMainExecutor(), mCommandQueue,
mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger);
mMagnification.mWindowMagnificationControllerSupplier =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 3b5cbea..6dc5b72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -121,7 +121,8 @@
mCommandQueue = new CommandQueue(getContext(), mDisplayTracker);
mMagnification = new Magnification(getContext(),
- getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
+ getContext().getMainThreadHandler(), getContext().getMainExecutor(),
+ mCommandQueue, mModeSwitchesController,
mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
getContext().getSystemService(DisplayManager.class), mA11yLogger);
mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index e076420..44207a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -477,9 +477,8 @@
});
// Verify the method is called in
- // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
- // {@link Animator.AnimatorListener#onAnimationEnd} once in {@link ValueAnimator#end()}
- verify(mSpyController, times(2)).updateWindowMagnificationInternal(
+ // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once
+ verify(mSpyController).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -594,10 +593,10 @@
final float expectedY = (int) (windowBounds.exactCenterY() + expectedOffset
- defaultMagnificationWindowSize / 2);
- // This is called 5 times when (1) first creating WindowlessMirrorWindow (2) SurfaceView is
+ // This is called 4 times when (1) first creating WindowlessMirrorWindow (2) SurfaceView is
// created and we place the mirrored content as a child of the SurfaceView
- // (3) the animation starts (4) the animation updates (5) the animation ends
- verify(mTransaction, times(5))
+ // (3) the animation starts (4) the animation updates
+ verify(mTransaction, times(4))
.setPosition(any(SurfaceControl.class), eq(expectedX), eq(expectedY));
}
@@ -788,9 +787,8 @@
waitForIdleSync();
// Verify the method is called in
- // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
- // {@link Animator.AnimatorListener#onAnimationEnd} once in {@link ValueAnimator#end()}
- verify(mSpyController, times(2)).updateWindowMagnificationInternal(
+ // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once
+ verify(mSpyController).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -832,10 +830,8 @@
deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback2);
// Verify the method is called in
- // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
- // {@link Animator.AnimatorListener#onAnimationEnd} once when running the animation at
- // the final duration time.
- verify(mSpyController, times(2)).updateWindowMagnificationInternal(
+ // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once
+ verify(mSpyController).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
index a88654b..01e4d58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
@@ -46,6 +46,7 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
@@ -459,6 +460,7 @@
final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10;
final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10;
+ reset(mWindowMagnifierCallback);
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.moveWindowMagnifierToPosition(
targetCenterX, targetCenterY, mAnimationCallback);
@@ -491,6 +493,7 @@
final float centerX = sourceBoundsCaptor.getValue().exactCenterX();
final float centerY = sourceBoundsCaptor.getValue().exactCenterY();
+ reset(mWindowMagnifierCallback);
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.moveWindowMagnifierToPosition(
centerX + 10, centerY + 10, mAnimationCallback);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
index b843fda..516b665 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
@@ -23,11 +23,12 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Size;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.FakeSharedPreferences;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index 1576457..ebb6b48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -39,8 +39,10 @@
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.bluetooth.qsdialog.DeviceItem;
@@ -88,6 +90,10 @@
@Mock
private LocalBluetoothAdapter mLocalBluetoothAdapter;
@Mock
+ private LocalBluetoothProfileManager mProfileManager;
+ @Mock
+ private HapClientProfile mHapClientProfile;
+ @Mock
private CachedBluetoothDeviceManager mCachedDeviceManager;
@Mock
private BluetoothEventManager mBluetoothEventManager;
@@ -106,6 +112,8 @@
public void setUp() {
mTestableLooper = TestableLooper.get(this);
when(mLocalBluetoothManager.getBluetoothAdapter()).thenReturn(mLocalBluetoothAdapter);
+ when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
+ when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
when(mLocalBluetoothAdapter.isEnabled()).thenReturn(true);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
@@ -163,6 +171,7 @@
private void setUpPairNewDeviceDialog() {
mDialogDelegate = new HearingDevicesDialogDelegate(
+ mContext,
true,
mSystemUIDialogFactory,
mActivityStarter,
@@ -185,6 +194,7 @@
private void setUpDeviceListDialog() {
mDialogDelegate = new HearingDevicesDialogDelegate(
+ mContext,
false,
mSystemUIDialogFactory,
mActivityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/InputSessionTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/InputSessionTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/touch/InputSessionTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/ambient/touch/InputSessionTest.java
index 8685384..2f4999b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/InputSessionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/InputSessionTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
index a127631..1e3b556 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
import static com.google.common.truth.Truth.assertThat;
@@ -43,7 +43,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.touch.dagger.InputSessionComponent;
+import com.android.systemui.ambient.touch.dagger.InputSessionComponent;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.display.DisplayHelper;
@@ -67,7 +67,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
+public class TouchMonitorTest extends SysuiTestCase {
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -78,7 +78,7 @@
private final InputSession mInputSession;
private final Lifecycle mLifecycle;
private final LifecycleOwner mLifecycleOwner;
- private final DreamOverlayTouchMonitor mMonitor;
+ private final TouchMonitor mMonitor;
private final DefaultLifecycleObserver mLifecycleObserver;
private final InputChannelCompat.InputEventListener mEventListener;
private final GestureDetector.OnGestureListener mGestureListener;
@@ -88,7 +88,7 @@
private final Rect mDisplayBounds = Mockito.mock(Rect.class);
private final IWindowManager mIWindowManager;
- Environment(Set<DreamTouchHandler> handlers) {
+ Environment(Set<TouchHandler> handlers) {
mLifecycle = Mockito.mock(Lifecycle.class);
mLifecycleOwner = Mockito.mock(LifecycleOwner.class);
mIWindowManager = Mockito.mock(IWindowManager.class);
@@ -104,7 +104,7 @@
mDisplayHelper = Mockito.mock(DisplayHelper.class);
when(mDisplayHelper.getMaxBounds(anyInt(), anyInt()))
.thenReturn(mDisplayBounds);
- mMonitor = new DreamOverlayTouchMonitor(mExecutor, mBackgroundExecutor,
+ mMonitor = new TouchMonitor(mExecutor, mBackgroundExecutor,
mLifecycle, mInputFactory, mDisplayHelper, handlers, mIWindowManager, 0);
mMonitor.init();
@@ -157,7 +157,7 @@
@Test
public void testReportedDisplayBounds() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -169,8 +169,8 @@
// Verify display bounds passed into TouchHandler#getTouchInitiationRegion
verify(touchHandler).getTouchInitiationRegion(
eq(environment.getDisplayBounds()), any(), any());
- final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
- ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+ final ArgumentCaptor<TouchHandler.TouchSession> touchSessionArgumentCaptor =
+ ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
// Verify that display bounds provided from TouchSession#getBounds
@@ -180,7 +180,7 @@
@Test
public void testEntryTouchZone() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Rect touchArea = new Rect(4, 4, 8 , 8);
doAnswer(invocation -> {
@@ -208,10 +208,10 @@
@Test
public void testSessionCount() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Rect touchArea = new Rect(4, 4, 8 , 8);
- final DreamTouchHandler unzonedTouchHandler = createTouchHandler();
+ final TouchHandler unzonedTouchHandler = createTouchHandler();
doAnswer(invocation -> {
final Region region = (Region) invocation.getArguments()[1];
region.set(touchArea);
@@ -227,13 +227,13 @@
when(initialEvent.getY()).thenReturn(1.0f);
environment.publishInputEvent(initialEvent);
- ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionCaptor = ArgumentCaptor.forClass(
- DreamTouchHandler.TouchSession.class);
+ ArgumentCaptor<TouchHandler.TouchSession> touchSessionCaptor = ArgumentCaptor.forClass(
+ TouchHandler.TouchSession.class);
// Make sure only one active session.
{
verify(unzonedTouchHandler).onSessionStart(touchSessionCaptor.capture());
- final DreamTouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
+ final TouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
assertThat(touchSession.getActiveSessionCount()).isEqualTo(1);
touchSession.pop();
environment.executeAll();
@@ -247,7 +247,7 @@
// Make sure there are two active sessions.
{
verify(touchHandler).onSessionStart(touchSessionCaptor.capture());
- final DreamTouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
+ final TouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
assertThat(touchSession.getActiveSessionCount()).isEqualTo(2);
touchSession.pop();
}
@@ -256,7 +256,7 @@
@Test
public void testNoActiveSessionWhenHandlerDisabled() {
- final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+ final TouchHandler touchHandler = Mockito.mock(TouchHandler.class);
// disable the handler
when(touchHandler.isEnabled()).thenReturn(false);
@@ -274,7 +274,7 @@
@Test
public void testInputEventPropagation() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -294,7 +294,7 @@
@Test
public void testInputEventPropagationAfterRemoval() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -303,7 +303,7 @@
environment.publishInputEvent(initialEvent);
// Ensure session started
- final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+ final TouchHandler.TouchSession session = captureSession(touchHandler);
final InputChannelCompat.InputEventListener eventListener =
registerInputEventListener(session);
@@ -318,7 +318,7 @@
@Test
public void testInputGesturePropagation() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -337,7 +337,7 @@
@Test
public void testGestureConsumption() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -360,8 +360,8 @@
@Test
public void testBroadcast() {
- final DreamTouchHandler touchHandler = createTouchHandler();
- final DreamTouchHandler touchHandler2 = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler2 = createTouchHandler();
when(touchHandler2.isEnabled()).thenReturn(true);
final Environment environment = new Environment(Stream.of(touchHandler, touchHandler2)
@@ -386,7 +386,7 @@
@Test
public void testPush() throws InterruptedException, ExecutionException {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -394,13 +394,13 @@
final InputEvent initialEvent = Mockito.mock(InputEvent.class);
environment.publishInputEvent(initialEvent);
- final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+ final TouchHandler.TouchSession session = captureSession(touchHandler);
final InputChannelCompat.InputEventListener eventListener =
registerInputEventListener(session);
- final ListenableFuture<DreamTouchHandler.TouchSession> frontSessionFuture = session.push();
+ final ListenableFuture<TouchHandler.TouchSession> frontSessionFuture = session.push();
environment.executeAll();
- final DreamTouchHandler.TouchSession frontSession = frontSessionFuture.get();
+ final TouchHandler.TouchSession frontSession = frontSessionFuture.get();
final InputChannelCompat.InputEventListener frontEventListener =
registerInputEventListener(frontSession);
@@ -412,10 +412,10 @@
Mockito.clearInvocations(eventListener, frontEventListener);
- ListenableFuture<DreamTouchHandler.TouchSession> sessionFuture = frontSession.pop();
+ ListenableFuture<TouchHandler.TouchSession> sessionFuture = frontSession.pop();
environment.executeAll();
- DreamTouchHandler.TouchSession returnedSession = sessionFuture.get();
+ TouchHandler.TouchSession returnedSession = sessionFuture.get();
assertThat(session == returnedSession).isTrue();
environment.executeAll();
@@ -429,10 +429,10 @@
@Test
public void testPop() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
- final DreamTouchHandler.TouchSession.Callback callback =
- Mockito.mock(DreamTouchHandler.TouchSession.Callback.class);
+ final TouchHandler.TouchSession.Callback callback =
+ Mockito.mock(TouchHandler.TouchSession.Callback.class);
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -440,7 +440,7 @@
final InputEvent initialEvent = Mockito.mock(InputEvent.class);
environment.publishInputEvent(initialEvent);
- final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+ final TouchHandler.TouchSession session = captureSession(touchHandler);
session.registerCallback(callback);
session.pop();
environment.executeAll();
@@ -450,7 +450,7 @@
@Test
public void testPauseWithNoActiveSessions() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -464,7 +464,7 @@
@Test
public void testDeferredPauseWithActiveSessions() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -481,8 +481,8 @@
environment.publishInputEvent(event);
verify(eventListener).onInputEvent(eq(event));
- final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
- ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+ final ArgumentCaptor<TouchHandler.TouchSession> touchSessionArgumentCaptor =
+ ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
@@ -502,7 +502,7 @@
@Test
public void testDestroyWithActiveSessions() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -519,8 +519,8 @@
environment.publishInputEvent(event);
verify(eventListener).onInputEvent(eq(event));
- final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
- ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+ final ArgumentCaptor<TouchHandler.TouchSession> touchSessionArgumentCaptor =
+ ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
@@ -535,19 +535,19 @@
@Test
public void testPilfering() {
- final DreamTouchHandler touchHandler1 = createTouchHandler();
- final DreamTouchHandler touchHandler2 = createTouchHandler();
+ final TouchHandler touchHandler1 = createTouchHandler();
+ final TouchHandler touchHandler2 = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler1, touchHandler2)
.collect(Collectors.toCollection(HashSet::new)));
final InputEvent initialEvent = Mockito.mock(InputEvent.class);
environment.publishInputEvent(initialEvent);
- final DreamTouchHandler.TouchSession session1 = captureSession(touchHandler1);
+ final TouchHandler.TouchSession session1 = captureSession(touchHandler1);
final GestureDetector.OnGestureListener gestureListener1 =
registerGestureListener(session1);
- final DreamTouchHandler.TouchSession session2 = captureSession(touchHandler2);
+ final TouchHandler.TouchSession session2 = captureSession(touchHandler2);
final GestureDetector.OnGestureListener gestureListener2 =
registerGestureListener(session2);
when(gestureListener2.onDown(any())).thenReturn(true);
@@ -568,10 +568,10 @@
@Test
public void testOnRemovedCallbackOnStopMonitoring() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
- final DreamTouchHandler.TouchSession.Callback callback =
- Mockito.mock(DreamTouchHandler.TouchSession.Callback.class);
+ final TouchHandler.TouchSession.Callback callback =
+ Mockito.mock(TouchHandler.TouchSession.Callback.class);
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -579,7 +579,7 @@
final InputEvent initialEvent = Mockito.mock(InputEvent.class);
environment.publishInputEvent(initialEvent);
- final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+ final TouchHandler.TouchSession session = captureSession(touchHandler);
session.registerCallback(callback);
environment.executeAll();
@@ -593,19 +593,19 @@
verify(callback).onRemoved();
}
- public GestureDetector.OnGestureListener registerGestureListener(DreamTouchHandler handler) {
+ private GestureDetector.OnGestureListener registerGestureListener(TouchHandler handler) {
final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
GestureDetector.OnGestureListener.class);
- final ArgumentCaptor<DreamTouchHandler.TouchSession> sessionCaptor =
- ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+ final ArgumentCaptor<TouchHandler.TouchSession> sessionCaptor =
+ ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
verify(handler).onSessionStart(sessionCaptor.capture());
sessionCaptor.getValue().registerGestureListener(gestureListener);
return gestureListener;
}
- public GestureDetector.OnGestureListener registerGestureListener(
- DreamTouchHandler.TouchSession session) {
+ private GestureDetector.OnGestureListener registerGestureListener(
+ TouchHandler.TouchSession session) {
final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
GestureDetector.OnGestureListener.class);
session.registerGestureListener(gestureListener);
@@ -613,8 +613,8 @@
return gestureListener;
}
- public InputChannelCompat.InputEventListener registerInputEventListener(
- DreamTouchHandler.TouchSession session) {
+ private InputChannelCompat.InputEventListener registerInputEventListener(
+ TouchHandler.TouchSession session) {
final InputChannelCompat.InputEventListener eventListener = Mockito.mock(
InputChannelCompat.InputEventListener.class);
session.registerInputListener(eventListener);
@@ -622,20 +622,20 @@
return eventListener;
}
- public DreamTouchHandler.TouchSession captureSession(DreamTouchHandler handler) {
- final ArgumentCaptor<DreamTouchHandler.TouchSession> sessionCaptor =
- ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+ private TouchHandler.TouchSession captureSession(TouchHandler handler) {
+ final ArgumentCaptor<TouchHandler.TouchSession> sessionCaptor =
+ ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
verify(handler).onSessionStart(sessionCaptor.capture());
return sessionCaptor.getValue();
}
- public InputChannelCompat.InputEventListener registerInputEventListener(
- DreamTouchHandler handler) {
+ private InputChannelCompat.InputEventListener registerInputEventListener(
+ TouchHandler handler) {
return registerInputEventListener(captureSession(handler));
}
- private DreamTouchHandler createTouchHandler() {
- final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+ private TouchHandler createTouchHandler() {
+ final TouchHandler touchHandler = Mockito.mock(TouchHandler.class);
// enable the handler by default
when(touchHandler.isEnabled()).thenReturn(true);
return touchHandler;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index f490f3c..cbad133 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -41,7 +41,6 @@
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
@@ -109,7 +108,6 @@
headsUpManager,
powerInteractor,
activeNotificationsInteractor,
- kosmos.sceneContainerFlags,
kosmos::sceneInteractor,
)
}
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 c0e108e..5e7adb7 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
@@ -17,9 +17,9 @@
package com.android.systemui.biometrics.domain.interactor
import android.graphics.Rect
-import android.test.suitebuilder.annotation.SmallTest
import android.view.MotionEvent
import android.view.Surface
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 30c5e6e..d3cc232 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -78,7 +78,6 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
import com.android.systemui.statusbar.phone.dozeServiceHost
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -239,7 +238,6 @@
testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
- kosmos.fakeSceneContainerFlags,
kosmos.sceneInteractor,
primaryBouncerInteractor,
alternateBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 238a76e..415da02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -77,7 +77,6 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
import com.android.systemui.statusbar.phone.dozeServiceHost
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -236,7 +235,6 @@
testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
- kosmos.fakeSceneContainerFlags,
kosmos.sceneInteractor,
primaryBouncerInteractor,
alternateBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
new file mode 100644
index 0000000..8a1a082
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+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.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
[email protected](setAsMainLooper = true)
+class AudioSharingInteractorTest : SysuiTestCase() {
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val bluetoothState = MutableStateFlow(false)
+ private val deviceItemUpdate: MutableSharedFlow<List<DeviceItem>> = MutableSharedFlow()
+ @Mock private lateinit var cachedBluetoothDevice: CachedBluetoothDevice
+ @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+ @Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
+ @Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
+ @Mock private lateinit var deviceItem: DeviceItem
+ private lateinit var mockitoSession: StaticMockitoSession
+ private lateinit var audioSharingInteractor: AudioSharingInteractor
+
+ @Before
+ fun setUp() {
+ mockitoSession =
+ mockitoSession().initMocks(this).mockStatic(BluetoothUtils::class.java).startMocking()
+ whenever(bluetoothStateInteractor.bluetoothStateUpdate).thenReturn(bluetoothState)
+ whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(deviceItemUpdate)
+ audioSharingInteractor =
+ AudioSharingInteractor(
+ localBluetoothManager,
+ bluetoothStateInteractor,
+ deviceItemInteractor,
+ testScope.backgroundScope,
+ testDispatcher,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ fun testButtonStateUpdate_bluetoothOff_returnGone() {
+ testScope.runTest {
+ val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+
+ assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+ }
+ }
+
+ @Test
+ fun testButtonStateUpdate_noDevice_returnGone() {
+ testScope.runTest {
+ val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ bluetoothState.value = true
+ runCurrent()
+
+ assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+ }
+ }
+
+ @Test
+ fun testButtonStateUpdate_isBroadcasting_returnSharingAudio() {
+ testScope.runTest {
+ whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(true)
+
+ val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ bluetoothState.value = true
+ deviceItemUpdate.emit(listOf())
+ runCurrent()
+
+ assertThat(actual)
+ .isEqualTo(
+ AudioSharingButtonState.Visible(
+ R.string.quick_settings_bluetooth_audio_sharing_button_sharing
+ )
+ )
+ }
+ }
+
+ @Test
+ fun testButtonStateUpdate_hasSource_returnGone() {
+ testScope.runTest {
+ whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(false)
+ whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+ whenever(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ cachedBluetoothDevice,
+ localBluetoothManager
+ )
+ )
+ .thenReturn(true)
+
+ val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ bluetoothState.value = true
+ deviceItemUpdate.emit(listOf(deviceItem))
+ runCurrent()
+
+ assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+ }
+ }
+
+ @Test
+ fun testButtonStateUpdate_hasActiveDevice_returnAudioSharing() {
+ testScope.runTest {
+ whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(false)
+ whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+ whenever(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ cachedBluetoothDevice,
+ localBluetoothManager
+ )
+ )
+ .thenReturn(false)
+ whenever(BluetoothUtils.isActiveLeAudioDevice(cachedBluetoothDevice)).thenReturn(true)
+
+ val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ bluetoothState.value = true
+ deviceItemUpdate.emit(listOf(deviceItem))
+ runCurrent()
+
+ assertThat(actual)
+ .isEqualTo(
+ AudioSharingButtonState.Visible(
+ R.string.quick_settings_bluetooth_audio_sharing_button
+ )
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
index a8f82ed..6fe7d86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
@@ -23,6 +23,7 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.SysuiTestCase
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
@@ -41,7 +42,8 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class BluetoothStateInteractorTest : SysuiTestCase() {
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
- private val testScope = TestScope()
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
@@ -52,7 +54,12 @@
@Before
fun setUp() {
bluetoothStateInteractor =
- BluetoothStateInteractor(localBluetoothManager, logger, testScope.backgroundScope)
+ BluetoothStateInteractor(
+ localBluetoothManager,
+ logger,
+ testScope.backgroundScope,
+ testDispatcher
+ )
`when`(localBluetoothManager.bluetoothAdapter).thenReturn(bluetoothAdapter)
}
@@ -61,7 +68,7 @@
testScope.runTest {
`when`(bluetoothAdapter.isEnabled).thenReturn(true)
- assertThat(bluetoothStateInteractor.isBluetoothEnabled).isTrue()
+ assertThat(bluetoothStateInteractor.isBluetoothEnabled()).isTrue()
}
}
@@ -70,7 +77,7 @@
testScope.runTest {
`when`(bluetoothAdapter.isEnabled).thenReturn(false)
- assertThat(bluetoothStateInteractor.isBluetoothEnabled).isFalse()
+ assertThat(bluetoothStateInteractor.isBluetoothEnabled()).isFalse()
}
}
@@ -79,7 +86,7 @@
testScope.runTest {
`when`(bluetoothAdapter.isEnabled).thenReturn(false)
- bluetoothStateInteractor.isBluetoothEnabled = true
+ bluetoothStateInteractor.setBluetoothEnabled(true)
verify(bluetoothAdapter).enable()
verify(logger)
.logBluetoothState(BluetoothStateStage.BLUETOOTH_STATE_VALUE_SET, true.toString())
@@ -91,7 +98,7 @@
testScope.runTest {
`when`(bluetoothAdapter.isEnabled).thenReturn(false)
- bluetoothStateInteractor.isBluetoothEnabled = false
+ bluetoothStateInteractor.setBluetoothEnabled(false)
verify(bluetoothAdapter, never()).enable()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index 12dfe97..62c98b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -110,7 +110,6 @@
BluetoothTileDialogDelegate(
uiProperties,
CONTENT_HEIGHT,
- ENABLED,
bluetoothTileDialogCallback,
{},
dispatcher,
@@ -211,7 +210,6 @@
BluetoothTileDialogDelegate(
uiProperties,
CONTENT_HEIGHT,
- ENABLED,
bluetoothTileDialogCallback,
{},
dispatcher,
@@ -267,7 +265,6 @@
BluetoothTileDialogDelegate(
BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
cachedHeight,
- ENABLED,
bluetoothTileDialogCallback,
{},
dispatcher,
@@ -291,7 +288,6 @@
BluetoothTileDialogDelegate(
BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
MATCH_PARENT,
- ENABLED,
bluetoothTileDialogCallback,
{},
dispatcher,
@@ -315,7 +311,6 @@
BluetoothTileDialogDelegate(
BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
MATCH_PARENT,
- ENABLED,
bluetoothTileDialogCallback,
{},
dispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index 6d99c5b..b05d959 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -52,7 +52,6 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
@@ -74,7 +73,7 @@
@Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
- @Mock private lateinit var bluetoothAutoOnInteractor: BluetoothAutoOnInteractor
+ @Mock private lateinit var audioSharingInteractor: AudioSharingInteractor
@Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
@@ -92,6 +91,8 @@
@Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+ @Mock private lateinit var bluetoothTileDialogLogger: BluetoothTileDialogLogger
+
@Mock
private lateinit var mBluetoothTileDialogDelegateDelegateFactory:
BluetoothTileDialogDelegate.Factory
@@ -115,7 +116,12 @@
bluetoothTileDialogViewModel =
BluetoothTileDialogViewModel(
deviceItemInteractor,
- bluetoothStateInteractor,
+ BluetoothStateInteractor(
+ localBluetoothManager,
+ bluetoothTileDialogLogger,
+ testScope.backgroundScope,
+ dispatcher
+ ),
// TODO(b/316822488): Create FakeBluetoothAutoOnInteractor.
BluetoothAutoOnInteractor(
BluetoothAutoOnRepository(
@@ -125,6 +131,7 @@
dispatcher
)
),
+ audioSharingInteractor,
mDialogTransitionAnimator,
activityStarter,
uiEventLogger,
@@ -135,20 +142,9 @@
mBluetoothTileDialogDelegateDelegateFactory
)
whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
- whenever(bluetoothStateInteractor.bluetoothStateUpdate)
- .thenReturn(MutableStateFlow(null).asStateFlow())
whenever(deviceItemInteractor.deviceItemUpdateRequest)
.thenReturn(MutableStateFlow(Unit).asStateFlow())
- whenever(bluetoothStateInteractor.isBluetoothEnabled).thenReturn(true)
- whenever(
- mBluetoothTileDialogDelegateDelegateFactory.create(
- any(),
- anyInt(),
- ArgumentMatchers.anyBoolean(),
- any(),
- any()
- )
- )
+ whenever(mBluetoothTileDialogDelegateDelegateFactory.create(any(), anyInt(), any(), any()))
.thenReturn(bluetoothTileDialogDelegate)
whenever(bluetoothTileDialogDelegate.createDialog()).thenReturn(sysuiDialog)
whenever(sysuiDialog.context).thenReturn(mContext)
@@ -159,6 +155,8 @@
whenever(bluetoothTileDialogDelegate.contentHeight).thenReturn(getMutableStateFlow(0))
whenever(bluetoothTileDialogDelegate.bluetoothAutoOnToggle)
.thenReturn(getMutableStateFlow(false))
+ whenever(audioSharingInteractor.audioSharingButtonStateUpdate)
+ .thenReturn(getMutableStateFlow(AudioSharingButtonState.Gone))
}
@Test
@@ -201,15 +199,6 @@
}
@Test
- fun testShowDialog_withBluetoothStateValue() {
- testScope.runTest {
- bluetoothTileDialogViewModel.showDialog(null)
-
- verify(bluetoothStateInteractor).bluetoothStateUpdate
- }
- }
-
- @Test
fun testStartSettingsActivity_activityLaunched_dialogDismissed() {
testScope.runTest {
whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
index eb735cb..daf4a3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 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.
@@ -281,7 +281,7 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
) = isFilterMatchFunc(cachedDevice)
override fun create(context: Context, cachedDevice: CachedBluetoothDevice) = deviceItem
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
index f5990be..b7ed27f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
@@ -21,7 +21,7 @@
import android.content.Intent
import android.content.IntentFilter
import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index eb6e517..2c17181 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -24,7 +24,7 @@
import android.os.Looper
import android.os.PatternMatcher
import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
index 39e4467..582f301 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
@@ -21,7 +21,7 @@
import android.content.IntentFilter
import android.os.Handler
import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
index feaedc5..1e522fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
@@ -17,7 +17,7 @@
package com.android.systemui.camera
import android.content.Intent
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import org.junit.Assert.assertFalse
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 6a79ee8..6cc3ef19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.controls.ui
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.view.HapticFeedbackConstants
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegateTest.kt
new file mode 100644
index 0000000..d118cc7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegateTest.kt
@@ -0,0 +1,167 @@
+/*
+ * 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.display.ui.view
+
+import android.app.Dialog
+import android.graphics.Insets
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.Window
+import android.view.WindowInsets
+import android.view.WindowInsetsAnimation
+import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
[email protected](setAsMainLooper = true)
+class MirroringConfirmationDialogDelegateTest : SysuiTestCase() {
+
+ private lateinit var underTest: MirroringConfirmationDialogDelegate
+
+ private val onStartMirroringCallback = mock<View.OnClickListener>()
+ private val onCancelCallback = mock<View.OnClickListener>()
+ private val windowDecorView: View = mock {}
+ private val windowInsetsAnimationCallbackCaptor =
+ ArgumentCaptor.forClass(WindowInsetsAnimation.Callback::class.java)
+ private val dialog: Dialog =
+ mock<Dialog> {
+ var view: View? = null
+ whenever(setContentView(any<Int>())).then {
+ view =
+ LayoutInflater.from([email protected])
+ .inflate(it.arguments[0] as Int, null, false)
+ Unit
+ }
+ whenever(requireViewById<View>(any<Int>())).then {
+ view?.requireViewById(it.arguments[0] as Int)
+ }
+ val window: Window = mock { whenever(decorView).thenReturn(windowDecorView) }
+ whenever(this.window).thenReturn(window)
+ }
+
+ @Before
+ fun setUp() {
+ underTest =
+ MirroringConfirmationDialogDelegate(
+ context = context,
+ showConcurrentDisplayInfo = false,
+ onStartMirroringClickListener = onStartMirroringCallback,
+ onCancelMirroring = onCancelCallback,
+ navbarBottomInsetsProvider = { 0 },
+ )
+ }
+
+ @Test
+ fun startMirroringButton_clicked_callsCorrectCallback() {
+ underTest.onCreate(dialog, null)
+
+ dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+ verify(onStartMirroringCallback).onClick(any())
+ verify(onCancelCallback, never()).onClick(any())
+ }
+
+ @Test
+ fun cancelButton_clicked_callsCorrectCallback() {
+ underTest.onCreate(dialog, null)
+
+ dialog.requireViewById<View>(R.id.cancel).callOnClick()
+
+ verify(onCancelCallback).onClick(any())
+ verify(onStartMirroringCallback, never()).onClick(any())
+ }
+
+ @Test
+ fun onCancel_afterEnablingMirroring_cancelCallbackNotCalled() {
+ underTest.onCreate(dialog, null)
+ dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+ underTest.onStop(dialog)
+
+ verify(onCancelCallback, never()).onClick(any())
+ verify(onStartMirroringCallback).onClick(any())
+ }
+
+ @Test
+ fun onDismiss_afterEnablingMirroring_cancelCallbackNotCalled() {
+ underTest.onCreate(dialog, null)
+ dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+ underTest.onStop(dialog)
+
+ verify(onCancelCallback, never()).onClick(any())
+ verify(onStartMirroringCallback).onClick(any())
+ }
+
+ @Test
+ fun onInsetsChanged_navBarInsets_updatesBottomPadding() {
+ underTest.onCreate(dialog, null)
+ underTest.onStart(dialog)
+
+ val insets = buildInsets(WindowInsets.Type.navigationBars(), TEST_BOTTOM_INSETS)
+
+ triggerInsetsChanged(WindowInsets.Type.navigationBars(), insets)
+
+ assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+ .isEqualTo(TEST_BOTTOM_INSETS)
+ }
+
+ @Test
+ fun onInsetsChanged_otherType_doesNotUpdateBottomPadding() {
+ underTest.onCreate(dialog, null)
+ underTest.onStart(dialog)
+
+ val insets = buildInsets(WindowInsets.Type.ime(), TEST_BOTTOM_INSETS)
+ triggerInsetsChanged(WindowInsets.Type.ime(), insets)
+
+ assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+ .isNotEqualTo(TEST_BOTTOM_INSETS)
+ }
+
+ private fun buildInsets(@WindowInsets.Type.InsetsType type: Int, bottom: Int): WindowInsets {
+ return WindowInsets.Builder().setInsets(type, Insets.of(0, 0, 0, bottom)).build()
+ }
+
+ private fun triggerInsetsChanged(type: Int, insets: WindowInsets) {
+ verify(windowDecorView)
+ .setWindowInsetsAnimationCallback(capture(windowInsetsAnimationCallbackCaptor))
+ windowInsetsAnimationCallbackCaptor.value.onProgress(
+ insets,
+ listOf(WindowInsetsAnimation(type, Interpolators.INSTANT, 0))
+ )
+ }
+
+ private companion object {
+ const val TEST_BOTTOM_INSETS = 1000 // arbitrarily high number
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
deleted file mode 100644
index 30519b0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
+++ /dev/null
@@ -1,139 +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.display.ui.view
-
-import android.graphics.Insets
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.View
-import android.view.WindowInsets
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.res.R
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
[email protected](setAsMainLooper = true)
-class MirroringConfirmationDialogTest : SysuiTestCase() {
-
- private lateinit var dialog: MirroringConfirmationDialog
-
- private val onStartMirroringCallback = mock<View.OnClickListener>()
- private val onCancelCallback = mock<View.OnClickListener>()
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- dialog =
- MirroringConfirmationDialog(
- context,
- onStartMirroringCallback,
- onCancelCallback,
- navbarBottomInsetsProvider = { 0 },
- )
- }
-
- @Test
- fun startMirroringButton_clicked_callsCorrectCallback() {
- dialog.show()
-
- dialog.requireViewById<View>(R.id.enable_display).callOnClick()
-
- verify(onStartMirroringCallback).onClick(any())
- verify(onCancelCallback, never()).onClick(any())
- }
-
- @Test
- fun cancelButton_clicked_callsCorrectCallback() {
- dialog.show()
-
- dialog.requireViewById<View>(R.id.cancel).callOnClick()
-
- verify(onCancelCallback).onClick(any())
- verify(onStartMirroringCallback, never()).onClick(any())
- }
-
- @Test
- fun onCancel_afterEnablingMirroring_cancelCallbackNotCalled() {
- dialog.show()
- dialog.requireViewById<View>(R.id.enable_display).callOnClick()
-
- dialog.cancel()
-
- verify(onCancelCallback, never()).onClick(any())
- verify(onStartMirroringCallback).onClick(any())
- }
-
- @Test
- fun onDismiss_afterEnablingMirroring_cancelCallbackNotCalled() {
- dialog.show()
- dialog.requireViewById<View>(R.id.enable_display).callOnClick()
-
- dialog.dismiss()
-
- verify(onCancelCallback, never()).onClick(any())
- verify(onStartMirroringCallback).onClick(any())
- }
-
- @Test
- fun onInsetsChanged_navBarInsets_updatesBottomPadding() {
- dialog.show()
-
- val insets = buildInsets(WindowInsets.Type.navigationBars(), TEST_BOTTOM_INSETS)
- dialog.onInsetsChanged(WindowInsets.Type.navigationBars(), insets)
-
- assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
- .isEqualTo(TEST_BOTTOM_INSETS)
- }
-
- @Test
- fun onInsetsChanged_otherType_doesNotUpdateBottomPadding() {
- dialog.show()
-
- val insets = buildInsets(WindowInsets.Type.ime(), TEST_BOTTOM_INSETS)
- dialog.onInsetsChanged(WindowInsets.Type.ime(), insets)
-
- assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
- .isNotEqualTo(TEST_BOTTOM_INSETS)
- }
-
- private fun buildInsets(@WindowInsets.Type.InsetsType type: Int, bottom: Int): WindowInsets {
- return WindowInsets.Builder().setInsets(type, Insets.of(0, 0, 0, bottom)).build()
- }
-
- @After
- fun teardown() {
- if (::dialog.isInitialized) {
- dialog.dismiss()
- }
- }
-
- private companion object {
- const val TEST_BOTTOM_INSETS = 1000 // arbitrarily high number
- }
-}
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 523127e0..dbe59e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -23,7 +23,7 @@
import android.content.res.Resources.NotFoundException
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
index 70d6dd9..943e212 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
@@ -17,7 +17,7 @@
import android.content.pm.PackageManager.NameNotFoundException
import android.content.res.Resources
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
index 7c1325e..d500dd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.flags
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import java.io.PrintWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
index 303aaa1..5e87a6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
@@ -19,7 +19,7 @@
import android.database.ContentObserver
import android.net.Uri
import android.os.Handler
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
index db6f85f..755cc46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.flags
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
index 7d7abab..0fdda08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.flags
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.statusbar.policy.BatteryController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
index e287f19..3c965ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
@@ -17,7 +17,7 @@
package com.android.systemui.flags
import android.os.PowerManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.concurrency.FakeExecutor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
index 1f04828..0116e53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.flags
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.power.domain.interactor.PowerInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
index 1d1949d..2daa86b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -16,8 +16,8 @@
package com.android.systemui.flags
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.concurrency.FakeExecutor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
index 4ba1bc6..8a29217 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
@@ -2,7 +2,7 @@
import android.app.Fragment
import android.os.Looper
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.mockito.mock
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 318227f..709f779 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -25,8 +25,8 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER;
import static com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR;
+import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER;
import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION;
import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT;
import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE;
@@ -102,7 +102,6 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -222,7 +221,6 @@
private @Mock DreamViewModel mDreamViewModel;
private @Mock CommunalTransitionViewModel mCommunalTransitionViewModel;
private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
- private @Mock SceneContainerFlags mSceneContainerFlags;
private FakeFeatureFlags mFeatureFlags;
private final int mDefaultUserId = 100;
@@ -270,7 +268,6 @@
mShadeWindowLogger,
() -> mSelectedUserInteractor,
mUserTracker,
- mSceneContainerFlags,
mKosmos::getCommunalInteractor);
mFeatureFlags = new FakeFeatureFlags();
mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER);
@@ -1243,7 +1240,7 @@
mock(WindowManagerOcclusionManager.class));
mViewMediator.start();
- mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
+ mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null);
}
private void captureKeyguardStateControllerCallback() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index b0aace6..b50d248 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -23,7 +23,6 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.utils.GlobalWindowManager
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -63,8 +62,8 @@
val withDeps =
KeyguardInteractorFactory.create(
- repository = keyguardRepository,
featureFlags = featureFlags,
+ repository = keyguardRepository,
)
val keyguardInteractor = withDeps.keyguardInteractor
resourceTrimmer =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index 9266af4..dc7f372 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
@@ -40,7 +41,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -87,7 +87,6 @@
@Before
fun setup() {
mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
- kosmos.fakeSceneContainerFlags.enabled = false
primaryBouncerInteractor =
PrimaryBouncerInteractor(
@@ -127,7 +126,6 @@
testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
- kosmos.fakeSceneContainerFlags,
kosmos.sceneInteractor,
primaryBouncerInteractor,
alternateBouncerInteractor,
@@ -168,15 +166,14 @@
}
@Test
+ @EnableSceneContainer
fun updatesShowIndicatorForDeviceEntry_onBouncerSceneActive() =
testScope.runTest {
- kosmos.fakeSceneContainerFlags.enabled = true
underTest =
DeviceEntrySideFpsOverlayInteractor(
testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
- kosmos.fakeSceneContainerFlags,
kosmos.sceneInteractor,
primaryBouncerInteractor,
alternateBouncerInteractor,
@@ -196,15 +193,14 @@
}
@Test
+ @EnableSceneContainer
fun updatesShowIndicatorForDeviceEntry_onBouncerSceneInactive() =
testScope.runTest {
- kosmos.fakeSceneContainerFlags.enabled = true
underTest =
DeviceEntrySideFpsOverlayInteractor(
testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
- kosmos.fakeSceneContainerFlags,
kosmos.sceneInteractor,
primaryBouncerInteractor,
alternateBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
index 3f05bfa..9ccf212 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -19,6 +19,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -227,4 +228,50 @@
{ it == KeyguardSurfaceBehindModel(alpha = 0f) },
)
}
+
+ @Test
+ fun notificationLaunchFromLockscreen_isAnimatingSurfaceTrue() =
+ testScope.runTest {
+ val isAnimatingSurface by collectLastValue(underTest.isAnimatingSurface)
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+ kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true)
+ runCurrent()
+ assertThat(isAnimatingSurface).isTrue()
+ }
+
+ @Test
+ fun notificationLaunchFromGone_isAnimatingSurfaceFalse() =
+ testScope.runTest {
+ val isAnimatingSurface by collectLastValue(underTest.isAnimatingSurface)
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+ kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true)
+ runCurrent()
+ assertThat(isAnimatingSurface).isFalse()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
index 66aa572..5e3a142 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
@@ -113,7 +113,8 @@
id = "WEATHER_CLOCK",
name = "",
description = "",
- useAlternateSmartspaceAODTransition = true
+ useAlternateSmartspaceAODTransition = true,
+ useCustomClockScene = true
)
whenever(clock.config).thenReturn(clockConfig)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 9c7f254..9aee5b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.view.layout.sections.AccessibilityActionsSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
@@ -58,6 +59,7 @@
class DefaultKeyguardBlueprintTest : SysuiTestCase() {
private lateinit var underTest: DefaultKeyguardBlueprint
private lateinit var rootView: KeyguardRootView
+ @Mock private lateinit var accessibilityActionsSection: AccessibilityActionsSection
@Mock private lateinit var defaultIndicationAreaSection: DefaultIndicationAreaSection
@Mock private lateinit var mDefaultDeviceEntrySection: DefaultDeviceEntrySection
@Mock private lateinit var defaultShortcutsSection: DefaultShortcutsSection
@@ -81,6 +83,7 @@
rootView = KeyguardRootView(context, null)
underTest =
DefaultKeyguardBlueprint(
+ accessibilityActionsSection,
defaultIndicationAreaSection,
mDefaultDeviceEntrySection,
defaultShortcutsSection,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
index d410dac..f1c93c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
@@ -32,7 +32,6 @@
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -67,7 +66,7 @@
fun transitionToAlternateBouncer_scrimAlphaUpdate() =
testScope.runTest {
val scrimAlphas by collectValues(underTest.scrimAlpha)
- runCurrent()
+ assertThat(scrimAlphas.size).isEqualTo(1) // initial value is 0f
transitionRepository.sendTransitionSteps(
listOf(
@@ -79,7 +78,7 @@
testScope,
)
- assertThat(scrimAlphas.size).isEqualTo(4)
+ assertThat(scrimAlphas.size).isEqualTo(5)
scrimAlphas.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
}
@@ -87,7 +86,7 @@
fun transitionFromAlternateBouncer_scrimAlphaUpdate() =
testScope.runTest {
val scrimAlphas by collectValues(underTest.scrimAlpha)
- runCurrent()
+ assertThat(scrimAlphas.size).isEqualTo(1) // initial value is 0f
transitionRepository.sendTransitionSteps(
listOf(
@@ -98,7 +97,7 @@
),
testScope,
)
- assertThat(scrimAlphas.size).isEqualTo(4)
+ assertThat(scrimAlphas.size).isEqualTo(5)
scrimAlphas.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
index fe8fdc0..8f73811 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
@@ -50,6 +50,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
@@ -76,9 +77,10 @@
@TestableLooper.RunWithLooper
class MediaDataFilterImplTest : SysuiTestCase() {
+ @Mock private lateinit var listener: MediaDataProcessor.Listener
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var broadcastSender: BroadcastSender
- @Mock private lateinit var mediaDataManager: MediaDataManager
+ @Mock private lateinit var mediaDataProcessor: MediaDataProcessor
@Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
@Mock private lateinit var executor: Executor
@Mock private lateinit var smartspaceData: SmartspaceMediaData
@@ -101,7 +103,7 @@
MediaPlayerData.clear()
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
testScope = TestScope()
- repository = MediaFilterRepository()
+ repository = MediaFilterRepository(FakeSystemClock())
mediaDataFilter =
MediaDataFilterImpl(
context,
@@ -114,7 +116,8 @@
mediaFlags,
repository,
)
- mediaDataFilter.mediaDataManager = mediaDataManager
+ mediaDataFilter.mediaDataProcessor = mediaDataProcessor
+ mediaDataFilter.addListener(listener)
// Start all tests as main user
setUser(USER_MAIN)
@@ -167,6 +170,8 @@
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), eq(0), eq(false))
assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
}
@@ -178,6 +183,8 @@
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
+ verify(listener, never())
+ .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
assertThat(mediaDataLoadedStates).isNotEqualTo(mediaLoadedStatesModel)
}
@@ -196,6 +203,7 @@
mediaLoadedStatesModel.remove(MediaDataLoadingModel.Loaded(dataMain.instanceId))
mediaDataFilter.onMediaDataRemoved(KEY)
+ verify(listener).onMediaDataRemoved(eq(KEY))
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
}
@@ -208,6 +216,7 @@
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
mediaDataFilter.onMediaDataRemoved(KEY)
+ verify(listener, never()).onMediaDataRemoved(eq(KEY))
assertThat(mediaDataLoadedStates).isEmpty()
}
@@ -226,6 +235,7 @@
setUser(USER_GUEST)
// THEN we should remove the main user's media
+ verify(listener).onMediaDataRemoved(eq(KEY))
assertThat(mediaDataLoadedStates).isEmpty()
}
@@ -243,6 +253,20 @@
// and we switch to guest user
setUser(USER_GUEST)
+ // THEN we should add back the guest user media
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true), eq(0), eq(false))
+
+ // but not the main user's
+ verify(listener, never())
+ .onMediaDataLoaded(
+ eq(KEY),
+ any(),
+ eq(dataMain),
+ anyBoolean(),
+ anyInt(),
+ anyBoolean()
+ )
assertThat(mediaDataLoadedStates).isEqualTo(guestLoadedStatesModel)
assertThat(mediaDataLoadedStates).isNotEqualTo(mainLoadedStatesModel)
}
@@ -261,6 +285,7 @@
val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
// THEN we should remove the private profile media
+ verify(listener).onMediaDataRemoved(eq(KEY_ALT))
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
}
@@ -481,7 +506,7 @@
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
mediaDataFilter.onSwipeToDismiss()
- verify(mediaDataManager).setInactive(eq(KEY), eq(true), eq(true))
+ verify(mediaDataProcessor).setInactive(eq(KEY), eq(true), eq(true))
}
@Test
@@ -507,6 +532,8 @@
)
.isTrue()
assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
verify(logger, never()).logRecommendationActivated(any(), any(), any())
}
@@ -534,6 +561,9 @@
)
.isFalse()
assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
+ verify(listener, never())
+ .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
+ verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
verify(logger, never()).logRecommendationAdded(any(), any())
verify(logger, never()).logRecommendationActivated(any(), any(), any())
}
@@ -563,6 +593,8 @@
)
.isTrue()
assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
verify(logger, never()).logRecommendationActivated(any(), any(), any())
}
@@ -592,6 +624,7 @@
)
.isFalse()
assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
+ verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
verify(logger, never()).logRecommendationAdded(any(), any())
verify(logger, never()).logRecommendationActivated(any(), any(), any())
}
@@ -614,6 +647,8 @@
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
// AND we get a smartspace signal
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -629,6 +664,9 @@
)
.isFalse()
assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
+ verify(listener, never())
+ .onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(), anyInt(), anyBoolean())
+ verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
verify(logger, never()).logRecommendationAdded(any(), any())
verify(logger, never()).logRecommendationActivated(any(), any(), any())
}
@@ -649,12 +687,15 @@
val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
// AND we get a smartspace signal
runCurrent()
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// THEN we should treat the media as active instead
+ val dataCurrentAndActive = dataCurrent.copy(active = true)
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
assertThat(
hasActiveMediaOrRecommendation(
@@ -664,8 +705,18 @@
)
)
.isTrue()
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(KEY),
+ eq(dataCurrentAndActive),
+ eq(true),
+ eq(100),
+ eq(true)
+ )
// Smartspace update shouldn't be propagated for the empty rec list.
assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown)
+ verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
verify(logger, never()).logRecommendationAdded(any(), any())
verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
}
@@ -687,12 +738,24 @@
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
// AND we get a smartspace signal
runCurrent()
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// THEN we should treat the media as active instead
+ val dataCurrentAndActive = dataCurrent.copy(active = true)
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(KEY),
+ eq(dataCurrentAndActive),
+ eq(true),
+ eq(100),
+ eq(true)
+ )
assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
@@ -704,6 +767,8 @@
.isTrue()
// Smartspace update should also be propagated but not prioritized.
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
}
@@ -721,6 +786,7 @@
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+ verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
@@ -746,15 +812,29 @@
val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
runCurrent()
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+ val dataCurrentAndActive = dataCurrent.copy(active = true)
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(KEY),
+ eq(dataCurrentAndActive),
+ eq(true),
+ eq(100),
+ eq(true)
+ )
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+ verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
@@ -781,6 +861,8 @@
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
@@ -813,12 +895,18 @@
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
// And an inactive recommendation is loaded
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// Smartspace is loaded but the media stays inactive
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
+ verify(listener, never())
+ .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
@@ -846,8 +934,8 @@
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, data)
mediaDataFilter.onSwipeToDismiss()
- verify(mediaDataManager).setRecommendationInactive(eq(SMARTSPACE_KEY))
- verify(mediaDataManager, never())
+ verify(mediaDataProcessor).setRecommendationInactive(eq(SMARTSPACE_KEY))
+ verify(mediaDataProcessor, never())
.dismissSmartspaceRecommendation(eq(SMARTSPACE_KEY), anyLong())
}
@@ -866,6 +954,8 @@
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
// AND we get a smartspace signal with extra to trigger resume
@@ -875,6 +965,16 @@
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// THEN we should treat the media as active instead
+ val dataCurrentAndActive = dataCurrent.copy(active = true)
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(KEY),
+ eq(dataCurrentAndActive),
+ eq(true),
+ eq(100),
+ eq(true)
+ )
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
assertThat(
hasActiveMediaOrRecommendation(
@@ -886,6 +986,8 @@
.isTrue()
// And update the smartspace data state, but not prioritized
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
}
@Test
@@ -901,6 +1003,8 @@
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
// AND we get a smartspace signal with extra to not trigger resume
@@ -908,7 +1012,12 @@
whenever(cardAction.extras).thenReturn(extras)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+ // THEN listeners are not updated to show media
+ verify(listener, never())
+ .onMediaDataLoaded(eq(KEY), eq(KEY), any(), eq(true), eq(100), eq(true))
// But the smartspace update is still propagated
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 5c275b4..ffb50c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -210,7 +210,7 @@
)
testDispatcher = UnconfinedTestDispatcher()
testScope = TestScope(testDispatcher)
- mediaFilterRepository = MediaFilterRepository()
+ mediaFilterRepository = MediaFilterRepository(clock)
mediaDataRepository = MediaDataRepository(mediaFlags, dumpManager)
mediaDataProcessor =
MediaDataProcessor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
index eb885fd..4fcd3bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
@@ -18,9 +18,9 @@
import android.graphics.drawable.Animatable2
import android.graphics.drawable.Drawable
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
index 711669e..bb95ba3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
@@ -17,9 +17,9 @@
package com.android.systemui.media.controls.ui.animation
import android.animation.Animator
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import junit.framework.Assert.fail
import org.junit.After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
index 37dea11..791563a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
@@ -16,12 +16,13 @@
package com.android.systemui.media.controls.ui.controller
-import android.test.suitebuilder.annotation.SmallTest
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.controls.ui.view.MediaHost
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
index a73bb2c..e5d3082 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
@@ -16,29 +16,54 @@
package com.android.systemui.media.controls.ui.controller
+import android.animation.AnimatorSet
+import android.content.Context
import android.content.res.Configuration
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.RippleDrawable
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
+import android.view.ViewGroup
+import android.view.animation.Interpolator
+import android.widget.FrameLayout
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.SeekBar
+import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.lifecycle.LiveData
import androidx.test.filters.SmallTest
+import com.android.internal.widget.CachingIconView
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.ui.view.GutsViewHolder
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.res.R
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionViewState
import com.android.systemui.util.animation.WidgetState
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.floatThat
import org.mockito.Mock
+import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@@ -55,6 +80,31 @@
com.android.systemui.statusbar.phone.ConfigurationControllerImpl(context)
private var player = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
private var recommendation = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
+ private val clock = FakeSystemClock()
+ private lateinit var mainExecutor: FakeExecutor
+ private lateinit var seekBar: SeekBar
+ private lateinit var multiRippleView: MultiRippleView
+ private lateinit var turbulenceNoiseView: TurbulenceNoiseView
+ private lateinit var loadingEffectView: LoadingEffectView
+ private lateinit var settings: ImageButton
+ private lateinit var cancel: View
+ private lateinit var cancelText: TextView
+ private lateinit var dismiss: FrameLayout
+ private lateinit var dismissText: TextView
+ private lateinit var titleText: TextView
+ private lateinit var artistText: TextView
+ private lateinit var explicitIndicator: CachingIconView
+ private lateinit var seamless: ViewGroup
+ private lateinit var seamlessButton: View
+ private lateinit var seamlessIcon: ImageView
+ private lateinit var seamlessText: TextView
+ private lateinit var scrubbingElapsedTimeView: TextView
+ private lateinit var scrubbingTotalTimeView: TextView
+ private lateinit var actionPlayPause: ImageButton
+ private lateinit var actionNext: ImageButton
+ private lateinit var actionPrev: ImageButton
+ @Mock private lateinit var seamlessBackground: RippleDrawable
+ @Mock private lateinit var albumView: ImageView
@Mock lateinit var logger: MediaViewLogger
@Mock private lateinit var mockViewState: TransitionViewState
@Mock private lateinit var mockCopiedState: TransitionViewState
@@ -64,6 +114,14 @@
@Mock private lateinit var mediaSubTitleWidgetState: WidgetState
@Mock private lateinit var mediaContainerWidgetState: WidgetState
@Mock private lateinit var mediaFlags: MediaFlags
+ @Mock private lateinit var seekBarViewModel: SeekBarViewModel
+ @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
+ @Mock private lateinit var globalSettings: GlobalSettings
+ @Mock private lateinit var viewHolder: MediaViewHolder
+ @Mock private lateinit var view: TransitionLayout
+ @Mock private lateinit var mockAnimator: AnimatorSet
+ @Mock private lateinit var gutsViewHolder: GutsViewHolder
+ @Mock private lateinit var gutsText: TextView
private val delta = 0.1F
@@ -72,14 +130,30 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ mainExecutor = FakeExecutor(clock)
mediaViewController =
- MediaViewController(
- context,
- configurationController,
- mediaHostStatesManager,
- logger,
- mediaFlags,
- )
+ object :
+ MediaViewController(
+ context,
+ configurationController,
+ mediaHostStatesManager,
+ logger,
+ seekBarViewModel,
+ mainExecutor,
+ mediaFlags,
+ globalSettings,
+ ) {
+ override fun loadAnimator(
+ context: Context,
+ animId: Int,
+ motionInterpolator: Interpolator?,
+ vararg targets: View?
+ ): AnimatorSet {
+ return mockAnimator
+ }
+ }
+ initGutsViewHolderMocks()
+ initMediaViewHolderMocks()
}
@Test
@@ -299,4 +373,270 @@
verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
}
+
+ @Test
+ fun attachPlayer_seekBarDisabled_seekBarVisibilityIsSetToInvisible() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ getEnabledChangeListener().onEnabledChanged(enabled = true)
+ getEnabledChangeListener().onEnabledChanged(enabled = false)
+
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.media_progress_bar))
+ .isEqualTo(ConstraintSet.INVISIBLE)
+ }
+
+ @Test
+ fun attachPlayer_seekBarEnabled_seekBarVisible() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ getEnabledChangeListener().onEnabledChanged(enabled = true)
+
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.media_progress_bar))
+ .isEqualTo(ConstraintSet.VISIBLE)
+ }
+
+ @Test
+ fun attachPlayer_seekBarStatusUpdate_seekBarVisibilityChanges() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ getEnabledChangeListener().onEnabledChanged(enabled = true)
+
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.media_progress_bar))
+ .isEqualTo(ConstraintSet.VISIBLE)
+
+ getEnabledChangeListener().onEnabledChanged(enabled = false)
+
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.media_progress_bar))
+ .isEqualTo(ConstraintSet.INVISIBLE)
+ }
+
+ @Test
+ fun attachPlayer_notScrubbing_scrubbingViewsGone() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ mediaViewController.canShowScrubbingTime = true
+ getScrubbingChangeListener().onScrubbingChanged(true)
+ getScrubbingChangeListener().onScrubbingChanged(false)
+ mainExecutor.runAllReady()
+
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ }
+
+ @Test
+ fun setIsScrubbing_noSemanticActions_scrubbingViewsGone() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ mediaViewController.canShowScrubbingTime = false
+ getScrubbingChangeListener().onScrubbingChanged(true)
+ mainExecutor.runAllReady()
+
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ }
+
+ @Test
+ fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ mediaViewController.setUpNextButtonInfo(true)
+ mediaViewController.setUpPrevButtonInfo(false)
+ getScrubbingChangeListener().onScrubbingChanged(true)
+ mainExecutor.runAllReady()
+
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionNext))
+ .isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ }
+
+ @Test
+ fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ mediaViewController.setUpNextButtonInfo(false)
+ mediaViewController.setUpPrevButtonInfo(true)
+ getScrubbingChangeListener().onScrubbingChanged(true)
+ mainExecutor.runAllReady()
+
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionPrev))
+ .isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ }
+
+ @Test
+ fun setIsScrubbing_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ mediaViewController.setUpNextButtonInfo(true)
+ mediaViewController.setUpPrevButtonInfo(true)
+ mediaViewController.canShowScrubbingTime = true
+ getScrubbingChangeListener().onScrubbingChanged(true)
+ mainExecutor.runAllReady()
+
+ // Only in expanded, we should show the scrubbing times and hide prev+next
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionPrev))
+ .isEqualTo(ConstraintSet.GONE)
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionNext))
+ .isEqualTo(ConstraintSet.GONE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+ )
+ .isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+ )
+ .isEqualTo(ConstraintSet.VISIBLE)
+ }
+
+ @Test
+ fun setIsScrubbing_trueThenFalse_reservePrevAndNextButtons() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ mediaViewController.setUpNextButtonInfo(true, ConstraintSet.INVISIBLE)
+ mediaViewController.setUpPrevButtonInfo(true, ConstraintSet.INVISIBLE)
+ mediaViewController.canShowScrubbingTime = true
+
+ getScrubbingChangeListener().onScrubbingChanged(true)
+ mainExecutor.runAllReady()
+
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionPrev))
+ .isEqualTo(ConstraintSet.INVISIBLE)
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionNext))
+ .isEqualTo(ConstraintSet.INVISIBLE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+ )
+ .isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+ )
+ .isEqualTo(ConstraintSet.VISIBLE)
+
+ getScrubbingChangeListener().onScrubbingChanged(false)
+ mainExecutor.runAllReady()
+
+ // Only in expanded, we should hide the scrubbing times and show prev+next
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionPrev))
+ .isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionNext))
+ .isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ }
+
+ private fun initGutsViewHolderMocks() {
+ settings = ImageButton(context)
+ cancel = View(context)
+ cancelText = TextView(context)
+ dismiss = FrameLayout(context)
+ dismissText = TextView(context)
+ whenever(gutsViewHolder.gutsText).thenReturn(gutsText)
+ whenever(gutsViewHolder.settings).thenReturn(settings)
+ whenever(gutsViewHolder.cancel).thenReturn(cancel)
+ whenever(gutsViewHolder.cancelText).thenReturn(cancelText)
+ whenever(gutsViewHolder.dismiss).thenReturn(dismiss)
+ whenever(gutsViewHolder.dismissText).thenReturn(dismissText)
+ }
+
+ private fun initMediaViewHolderMocks() {
+ titleText = TextView(context)
+ artistText = TextView(context)
+ explicitIndicator = CachingIconView(context).also { it.id = R.id.media_explicit_indicator }
+ seamless = FrameLayout(context)
+ seamless.foreground = seamlessBackground
+ seamlessButton = View(context)
+ seamlessIcon = ImageView(context)
+ seamlessText = TextView(context)
+ seekBar = SeekBar(context).also { it.id = R.id.media_progress_bar }
+
+ actionPlayPause = ImageButton(context).also { it.id = R.id.actionPlayPause }
+ actionPrev = ImageButton(context).also { it.id = R.id.actionPrev }
+ actionNext = ImageButton(context).also { it.id = R.id.actionNext }
+ scrubbingElapsedTimeView =
+ TextView(context).also { it.id = R.id.media_scrubbing_elapsed_time }
+ scrubbingTotalTimeView = TextView(context).also { it.id = R.id.media_scrubbing_total_time }
+
+ multiRippleView = MultiRippleView(context, null)
+ turbulenceNoiseView = TurbulenceNoiseView(context, null)
+ loadingEffectView = LoadingEffectView(context, null)
+
+ whenever(viewHolder.player).thenReturn(view)
+ whenever(view.context).thenReturn(context)
+ whenever(viewHolder.albumView).thenReturn(albumView)
+ whenever(albumView.foreground).thenReturn(Mockito.mock(Drawable::class.java))
+ whenever(viewHolder.titleText).thenReturn(titleText)
+ whenever(viewHolder.artistText).thenReturn(artistText)
+ whenever(viewHolder.explicitIndicator).thenReturn(explicitIndicator)
+ whenever(seamlessBackground.getDrawable(0))
+ .thenReturn(Mockito.mock(GradientDrawable::class.java))
+ whenever(viewHolder.seamless).thenReturn(seamless)
+ whenever(viewHolder.seamlessButton).thenReturn(seamlessButton)
+ whenever(viewHolder.seamlessIcon).thenReturn(seamlessIcon)
+ whenever(viewHolder.seamlessText).thenReturn(seamlessText)
+ whenever(viewHolder.seekBar).thenReturn(seekBar)
+ whenever(viewHolder.scrubbingElapsedTimeView).thenReturn(scrubbingElapsedTimeView)
+ whenever(viewHolder.scrubbingTotalTimeView).thenReturn(scrubbingTotalTimeView)
+ whenever(viewHolder.gutsViewHolder).thenReturn(gutsViewHolder)
+ whenever(seekBarViewModel.progress).thenReturn(seekBarData)
+
+ // Action buttons
+ whenever(viewHolder.actionPlayPause).thenReturn(actionPlayPause)
+ whenever(viewHolder.getAction(R.id.actionNext)).thenReturn(actionNext)
+ whenever(viewHolder.getAction(R.id.actionPrev)).thenReturn(actionPrev)
+ whenever(viewHolder.getAction(R.id.actionPlayPause)).thenReturn(actionPlayPause)
+
+ whenever(viewHolder.multiRippleView).thenReturn(multiRippleView)
+ whenever(viewHolder.turbulenceNoiseView).thenReturn(turbulenceNoiseView)
+ whenever(viewHolder.loadingEffectView).thenReturn(loadingEffectView)
+ }
+
+ private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
+ withArgCaptor {
+ verify(seekBarViewModel).setScrubbingChangeListener(capture())
+ }
+
+ private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener = withArgCaptor {
+ verify(seekBarViewModel).setEnabledChangeListener(capture())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index ca403e0..9bb21f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -123,11 +123,22 @@
mMediaControllers.add(mMediaController);
when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers);
- mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ mMediaOutputController =
+ new MediaOutputController(
+ mContext,
+ TEST_PACKAGE,
+ mContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
// Using a fake package will cause routing operations to fail, so we intercept
// scanning-related operations.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index c9eb67e..2e6388a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -124,11 +124,22 @@
when(mLocalBluetoothLeBroadcast.getBroadcastCode()).thenReturn(
BROADCAST_CODE_TEST.getBytes(StandardCharsets.UTF_8));
- mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ mMediaOutputController =
+ new MediaOutputController(
+ mContext,
+ TEST_PACKAGE,
+ mContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
mBroadcastSender, mMediaOutputController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 980eb59..4eb0038 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -194,11 +194,22 @@
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(
mCachedBluetoothDeviceManager);
- mMediaOutputController = new MediaOutputController(mSpyContext, mPackageName,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ mMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ mPackageName,
+ mContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
@@ -276,11 +287,22 @@
@Test
public void start_withoutPackageName_verifyMediaControllerInit() {
- mMediaOutputController = new MediaOutputController(mSpyContext, null,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ mMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ null,
+ mContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
mMediaOutputController.start(mCb);
@@ -306,11 +328,22 @@
@Test
public void stop_withoutPackageName_verifyMediaControllerDeinit() {
- mMediaOutputController = new MediaOutputController(mSpyContext, null,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ mMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ null,
+ mSpyContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
mMediaOutputController.start(mCb);
@@ -550,12 +583,22 @@
@Test
public void getAppSourceName_packageNameIsNull_returnsNull() {
- MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
- "",
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ MediaOutputController testMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ "",
+ mSpyContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
testMediaOutputController.start(mCb);
reset(mCb);
@@ -573,12 +616,22 @@
@Test
public void getNotificationSmallIcon_packageNameIsNull_returnsNull() {
- MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
- "",
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ MediaOutputController testMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ "",
+ mSpyContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
testMediaOutputController.start(mCb);
reset(mCb);
@@ -609,12 +662,22 @@
@Test
public void addDeviceToPlayMedia_callsLocalMediaManager() {
- MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
- null,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ MediaOutputController testMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ null,
+ mSpyContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class);
testMediaOutputController.mLocalMediaManager = mockLocalMediaManager;
@@ -625,12 +688,22 @@
@Test
public void removeDeviceFromPlayMedia_callsLocalMediaManager() {
- MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
- null,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ MediaOutputController testMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ null,
+ mSpyContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class);
testMediaOutputController.mLocalMediaManager = mockLocalMediaManager;
@@ -894,11 +967,22 @@
@Test
public void getNotificationLargeIcon_withoutPackageName_returnsNull() {
- mMediaOutputController = new MediaOutputController(mSpyContext, null,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ mMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ null,
+ mSpyContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
assertThat(mMediaOutputController.getNotificationIcon()).isNull();
}
@@ -1085,12 +1169,22 @@
@Test
public void setTemporaryAllowListExceptionIfNeeded_packageNameIsNull_NoAction() {
- MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
- null,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ MediaOutputController testMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ null,
+ mSpyContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
testMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 83def8e4..3b6a88a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -18,6 +18,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -61,7 +62,7 @@
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
verify(mMockMediaOutputDialogManager, times(1))
- .createAndShow(getContext().getPackageName(), false, null);
+ .createAndShow(eq(getContext().getPackageName()), eq(false), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -72,7 +73,8 @@
intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -82,7 +84,8 @@
Intent intent = new Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -95,7 +98,8 @@
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -108,9 +112,10 @@
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, times(1))
- .createAndShow(getContext().getPackageName(), true, null);
+ .createAndShow(eq(getContext().getPackageName()), eq(true), any());
}
@Test
@@ -121,7 +126,8 @@
intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -133,7 +139,8 @@
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -145,7 +152,8 @@
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -155,7 +163,8 @@
Intent intent = new Intent("UnKnown Action");
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 84300da..cdef964 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -137,11 +137,22 @@
Mockito.eq(userHandle))).thenReturn(
mMediaControllers);
- mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ mMediaOutputController =
+ new MediaOutputController(
+ mContext,
+ TEST_PACKAGE,
+ mContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputDialog = makeTestDialog(mMediaOutputController);
mMediaOutputDialog.show();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index a702dda..224e755 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.when;
import android.content.ComponentName;
+import android.content.res.Configuration;
import android.view.IWindowManager;
import android.view.accessibility.AccessibilityManager;
@@ -55,6 +56,8 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.FakeConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import dagger.Lazy;
@@ -117,6 +120,7 @@
EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
@Mock
NotificationShadeWindowController mNotificationShadeWindowController;
+ ConfigurationController mConfigurationController = new FakeConfigurationController();
private AccessibilityManager.AccessibilityServicesStateChangeListener
mAccessibilityServicesStateChangeListener;
@@ -144,9 +148,8 @@
mSystemActions, mOverviewProxyService, mAssistManagerLazy,
() -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
mNavigationModeController, mEdgeBackGestureHandlerFactory, mWm, mUserTracker,
- mDisplayTracker, mNotificationShadeWindowController, mDumpManager, mCommandQueue,
- mSynchronousExecutor);
-
+ mDisplayTracker, mNotificationShadeWindowController, mConfigurationController,
+ mDumpManager, mCommandQueue, mSynchronousExecutor);
}
@Test
@@ -335,6 +338,12 @@
assertThat(state2.mWindowState).isNotEqualTo(newState);
}
+ @Test
+ public void configUpdatePropagatesToEdgeBackGestureHandler() {
+ mConfigurationController.onConfigurationChanged(Configuration.EMPTY);
+ verify(mEdgeBackGestureHandler, times(1)).onConfigurationChanged(any());
+ }
+
private List<String> createFakeShortcutTargets() {
return new ArrayList<>(List.of("a", "b", "c", "d"));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
index d405df7..354a87a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -37,11 +37,8 @@
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.verify;
-import static org.mockito.Mockito.when;
-import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
import android.util.SparseArray;
@@ -293,23 +290,6 @@
}
@Test
- public void testConfigurationChange_taskbarNotInitialized() {
- Configuration configuration = mContext.getResources().getConfiguration();
- mNavigationBarController.mIsLargeScreen = true;
- mNavigationBarController.onConfigChanged(configuration);
- verify(mTaskbarDelegate, never()).onConfigurationChanged(configuration);
- }
-
- @Test
- public void testConfigurationChange_taskbarInitialized() {
- Configuration configuration = mContext.getResources().getConfiguration();
- mNavigationBarController.mIsLargeScreen = true;
- when(mTaskbarDelegate.isInitialized()).thenReturn(true);
- mNavigationBarController.onConfigChanged(configuration);
- verify(mTaskbarDelegate, times(1)).onConfigurationChanged(configuration);
- }
-
- @Test
public void testShouldRenderTaskbar_taskbarNotRenderedOnPhone() {
mNavigationBarController.mIsLargeScreen = false;
mNavigationBarController.mIsPhone = true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index b38d5e3..0e7a215 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -111,6 +111,7 @@
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.DeviceConfigProxyFake;
@@ -275,8 +276,8 @@
mKeyguardStateController, mock(NavigationModeController.class),
mEdgeBackGestureHandlerFactory, mock(IWindowManager.class),
mock(UserTracker.class), mock(DisplayTracker.class),
- mNotificationShadeWindowController, mock(DumpManager.class),
- mock(CommandQueue.class), mSynchronousExecutor));
+ mNotificationShadeWindowController, mock(ConfigurationController.class),
+ mock(DumpManager.class), mock(CommandQueue.class), mSynchronousExecutor));
mNavigationBar = createNavBar(mContext);
mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
});
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
index b4f5528..4101c94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.notetask
import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
index e09c804..2c86a8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
@@ -19,7 +19,7 @@
import android.app.role.RoleManager
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.eq
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index ebd34de..231b333 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -25,8 +25,8 @@
import android.hardware.input.InputSettings
import android.os.UserHandle
import android.os.UserManager
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 4547bff..9429725 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -42,11 +42,12 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.UiEventLogger;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 2bdad2b..cae170f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -38,18 +38,19 @@
import android.provider.Settings;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.TestableResources;
+import androidx.test.filters.SmallTest;
+
import com.android.settingslib.fuelgauge.Estimate;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.power.PowerUI.WarningsUI;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
index e4a4836..6956bea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
@@ -57,6 +57,7 @@
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.media.controls.ui.view.MediaHost;
import com.android.systemui.qs.customize.QSCustomizerController;
@@ -66,7 +67,6 @@
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
@@ -115,7 +115,6 @@
@Mock private FooterActionsViewBinder mFooterActionsViewBinder;
@Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
@Mock private FeatureFlagsClassic mFeatureFlags;
- @Mock private SceneContainerFlags mSceneContainerFlags;
private ViewGroup mQsView;
private final CommandQueue mCommandQueue =
@@ -127,7 +126,6 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- when(mSceneContainerFlags.isEnabled()).thenReturn(false);
mUnderTest = instantiate();
@@ -496,8 +494,8 @@
}
@Test
+ @EnableSceneContainer
public void testSceneContainerFlagsEnabled_FooterActionsRemoved_controllerNotStarted() {
- when(mSceneContainerFlags.isEnabled()).thenReturn(true);
clearInvocations(
mFooterActionsViewBinder, mFooterActionsViewModel, mFooterActionsViewModelFactory);
QSImpl other = instantiate();
@@ -513,9 +511,8 @@
}
@Test
+ @EnableSceneContainer
public void testSceneContainerFlagsEnabled_statusBarStateIsShade() {
- when(mSceneContainerFlags.isEnabled()).thenReturn(true);
-
mUnderTest.onStateChanged(KEYGUARD);
assertThat(mUnderTest.getStatusBarState()).isEqualTo(SHADE);
@@ -524,9 +521,8 @@
}
@Test
+ @EnableSceneContainer
public void testSceneContainerFlagsEnabled_isKeyguardState_alwaysFalse() {
- when(mSceneContainerFlags.isEnabled()).thenReturn(true);
-
mUnderTest.onStateChanged(KEYGUARD);
assertThat(mUnderTest.isKeyguardState()).isFalse();
@@ -559,8 +555,8 @@
mFooterActionsViewModelFactory,
mFooterActionsViewBinder,
mLargeScreenShadeInterpolator,
- mFeatureFlags,
- mSceneContainerFlags);
+ mFeatureFlags
+ );
}
private void setUpOther() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index a60494f..e50320d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -1,7 +1,7 @@
package com.android.systemui.qs
import android.content.res.Configuration
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableResources
import android.view.ContextThemeWrapper
@@ -17,13 +17,13 @@
import com.android.systemui.qs.customize.QSCustomizerController
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.settings.brightness.BrightnessController
import com.android.systemui.settings.brightness.BrightnessSliderController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.tuner.TunerService
import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -36,7 +36,6 @@
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import javax.inject.Provider
import org.mockito.Mockito.`when` as whenever
@SmallTest
@@ -65,8 +64,6 @@
@Mock private lateinit var pagedTileLayout: PagedTileLayout
@Mock private lateinit var longPressEffectProvider: Provider<QSLongPressEffect>
- private val sceneContainerFlags = FakeSceneContainerFlags()
-
private lateinit var controller: QSPanelController
private val testableResources: TestableResources = mContext.orCreateTestableResources
@@ -103,7 +100,6 @@
falsingManager,
statusBarKeyguardViewManager,
ResourcesSplitShadeStateController(),
- sceneContainerFlags,
longPressEffectProvider,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 718e302f..0abcc64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -42,7 +42,6 @@
import android.os.Looper;
import android.provider.DeviceConfig;
import android.provider.Settings;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -52,6 +51,7 @@
import android.widget.TextView;
import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 8acde36..4915e55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -20,15 +20,14 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Answers
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -43,8 +42,6 @@
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var context: Context
- private val sceneContainerFlags = FakeSceneContainerFlags()
-
private lateinit var controller: QuickStatusBarHeaderController
@Before
@@ -55,9 +52,8 @@
`when`(view.context).thenReturn(context)
controller = QuickStatusBarHeaderController(
- view,
- quickQSPanelController,
- sceneContainerFlags,
+ view,
+ quickQSPanelController,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 0eae5aa..98adbb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -31,12 +31,12 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableLooper;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java
index c43c3e6..29e2a8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java
@@ -16,9 +16,9 @@
import static junit.framework.Assert.assertEquals;
-import android.test.suitebuilder.annotation.SmallTest;
import android.view.View;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index 33f8f1f..ef979d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -31,7 +31,7 @@
import android.os.Parcel
import android.service.quicksettings.IQSTileService
import android.service.quicksettings.Tile
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.IWindowManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java
index b8e6403..eb013c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java
@@ -19,12 +19,12 @@
import android.content.res.ColorStateList;
import android.service.quicksettings.Tile;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-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;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index 8142456..0a36ae6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -56,9 +56,9 @@
import android.service.quicksettings.IQSService;
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.TileService;
-import android.test.suitebuilder.annotation.SmallTest;
import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 28331bb..0ff29db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -34,8 +34,8 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index d011821..248af1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -34,11 +34,12 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.quicksettings.IQSTileService;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.qs.QSHost;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 512ca53..ecbd0f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -385,7 +385,7 @@
}
@Test
- fun onStateChange_longPressEffectActive_withInvalidDuration_doesNotCreateEffect() {
+ fun onStateChange_longPressEffectActive_withInvalidDuration_doesNotInitializeEffect() {
val state = QSTile.State() // A state that handles longPress
// GIVEN an invalid long-press effect duration
@@ -399,7 +399,7 @@
}
@Test
- fun onStateChange_longPressEffectActive_withValidDuration_createsEffect() {
+ fun onStateChange_longPressEffectActive_withValidDuration_initializesEffect() {
// GIVEN a test state that handles long-press and a valid long-press effect duration
val state = QSTile.State()
@@ -420,7 +420,7 @@
tileView.changeState(state)
// THEN the view binder no longer binds the view to the long-press effect
- assertThat(tileView.longPressEffectHandle).isNull()
+ assertThat(tileView.isLongPressEffectBound).isFalse()
}
@Test
@@ -435,7 +435,7 @@
tileView.changeState(state)
// THEN the view is bounded to the long-press effect
- assertThat(tileView.longPressEffectHandle).isNotNull()
+ assertThat(tileView.isLongPressEffectBound).isTrue()
}
@Test
@@ -451,7 +451,7 @@
tileView.changeState(state)
// THEN the view binder does not bind the view and no effect is initialized
- assertThat(tileView.longPressEffectHandle).isNull()
+ assertThat(tileView.isLongPressEffectBound).isFalse()
assertThat(tileView.isLongPressEffectInitialized).isFalse()
}
@@ -470,7 +470,7 @@
tileView.changeState(state)
// THEN the view binder does not bind the view and no effect is initialized
- assertThat(tileView.longPressEffectHandle).isNull()
+ assertThat(tileView.isLongPressEffectBound).isFalse()
assertThat(tileView.isLongPressEffectInitialized).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index 440270b..c02fca7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -27,13 +27,13 @@
import android.content.pm.PackageManager;
import android.hardware.SensorPrivacyManager;
import android.os.Handler;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableResources;
+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.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
@@ -43,6 +43,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.BatteryController;
import com.android.systemui.statusbar.policy.DeviceStateRotationLockSettingController;
import com.android.systemui.statusbar.policy.RotationLockController;
@@ -57,7 +58,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index 1313227..387f27d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -42,7 +42,6 @@
import com.android.systemui.navigationbar.NavigationBarController
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.recents.OverviewProxyService.ACTION_QUICKSTEP
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeViewController
@@ -249,7 +248,6 @@
sysuiUnlockAnimationController,
inWindowLauncherUnlockAnimationManager,
assistUtils,
- FakeSceneContainerFlags(),
dumpManager,
unfoldTransitionProgressForwarder,
broadcastDispatcher
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
index b051df2..3756ec1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
@@ -185,16 +185,16 @@
}
}
-const val YOUTUBE_HOME_ACTIVITY =
+private const val YOUTUBE_HOME_ACTIVITY =
"com.google.android.youtube/" + "com.google.android.youtube.app.honeycomb.Shell\$HomeActivity"
-const val FILES_HOME_ACTIVITY =
+private const val FILES_HOME_ACTIVITY =
"com.google.android.apps.nbu.files/" + "com.google.android.apps.nbu.files.home.HomeActivity"
-const val YOUTUBE_PIP_ACTIVITY =
+private const val YOUTUBE_PIP_ACTIVITY =
"com.google.android.youtube/" +
"com.google.android.apps.youtube.app.watchwhile.WatchWhileActivity"
-const val LAUNCHER_ACTIVITY =
+private const val LAUNCHER_ACTIVITY =
"com.google.android.apps.nexuslauncher/" +
"com.google.android.apps.nexuslauncher.NexusLauncherActivity"
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 c900463..0f37143 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.lang.IllegalStateException
+import java.util.function.Consumer
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
@@ -78,7 +79,7 @@
fun executeScreenshots_severalDisplays_callsControllerForEachOne() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
verify(controllerFactory).create(eq(0), any())
@@ -107,7 +108,7 @@
fun executeScreenshots_providedImageType_callsOnlyDefaultDisplayController() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(
createScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE),
onSaved,
@@ -136,7 +137,7 @@
fun executeScreenshots_onlyVirtualDisplays_noInteractionsWithControllers() =
testScope.runTest {
setDisplays(display(TYPE_VIRTUAL, id = 0), display(TYPE_VIRTUAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
verifyNoMoreInteractions(controllerFactory)
@@ -154,7 +155,7 @@
display(TYPE_OVERLAY, id = 2),
display(TYPE_WIFI, id = 3)
)
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
verify(controller0, times(4)).handleScreenshot(any(), any(), any())
@@ -165,7 +166,7 @@
fun executeScreenshots_reportsOnFinishedOnlyWhenBothFinished() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
@@ -190,7 +191,7 @@
fun executeScreenshots_oneFinishesOtherFails_reportFailsOnlyAtTheEnd() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
@@ -217,7 +218,7 @@
fun executeScreenshots_allDisplaysFail_reportsFail() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
@@ -244,7 +245,7 @@
fun onDestroy_propagatedToControllers() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.onDestroy()
@@ -256,7 +257,7 @@
fun removeWindows_propagatedToControllers() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.removeWindows()
@@ -270,7 +271,7 @@
fun onCloseSystemDialogsReceived_propagatedToControllers() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.onCloseSystemDialogsReceived()
@@ -286,7 +287,7 @@
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
whenever(controller0.isPendingSharedTransition).thenReturn(true)
whenever(controller1.isPendingSharedTransition).thenReturn(false)
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.onCloseSystemDialogsReceived()
@@ -304,7 +305,7 @@
val toBeReturnedByProcessor = ScreenshotData.forTesting()
requestProcessor.toReturn = toBeReturnedByProcessor
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(screenshotRequest, onSaved, callback)
assertThat(requestProcessor.processed)
@@ -321,7 +322,7 @@
fun executeScreenshots_errorFromProcessor_logsScreenshotRequested() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
requestProcessor.shouldThrowException = true
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -338,7 +339,7 @@
fun executeScreenshots_errorFromProcessor_logsUiError() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
requestProcessor.shouldThrowException = true
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -355,7 +356,7 @@
fun executeScreenshots_errorFromProcessorOnDefaultDisplay_showsErrorNotification() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
requestProcessor.shouldThrowException = true
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -368,7 +369,7 @@
fun executeScreenshots_errorFromProcessorOnSecondaryDisplay_showsErrorNotification() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
requestProcessor.shouldThrowException = true
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -381,7 +382,7 @@
fun executeScreenshots_errorFromScreenshotController_reportsRequested() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
whenever(controller0.handleScreenshot(any(), any(), any()))
.thenThrow(IllegalStateException::class.java)
whenever(controller1.handleScreenshot(any(), any(), any()))
@@ -401,7 +402,7 @@
fun executeScreenshots_errorFromScreenshotController_reportsError() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
whenever(controller0.handleScreenshot(any(), any(), any()))
.thenThrow(IllegalStateException::class.java)
whenever(controller1.handleScreenshot(any(), any(), any()))
@@ -421,7 +422,7 @@
fun executeScreenshots_errorFromScreenshotController_showsErrorNotification() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
whenever(controller0.handleScreenshot(any(), any(), any()))
.thenThrow(IllegalStateException::class.java)
whenever(controller1.handleScreenshot(any(), any(), any()))
@@ -434,6 +435,25 @@
screenshotExecutor.onDestroy()
}
+ @Test
+ fun executeScreenshots_finisherCalledWithNullUri_succeeds() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0))
+ var onSavedCallCount = 0
+ val onSaved: (Uri?) -> Unit = {
+ assertThat(it).isNull()
+ onSavedCallCount += 1
+ }
+ whenever(controller0.handleScreenshot(any(), any(), any())).thenAnswer {
+ (it.getArgument(1) as Consumer<Uri?>).accept(null)
+ }
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+ assertThat(onSavedCallCount).isEqualTo(1)
+
+ screenshotExecutor.onDestroy()
+ }
+
private suspend fun TestScope.setDisplays(vararg displays: Display) {
fakeDisplayRepository.emit(displays.toSet())
runCurrent()
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 0776aa7..77b5c91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -232,7 +232,7 @@
override fun onCloseSystemDialogsReceived() {}
override suspend fun executeScreenshots(
screenshotRequest: ScreenshotRequest,
- onSaved: (Uri) -> Unit,
+ onSaved: (Uri?) -> Unit,
requestCallback: RequestCallback,
) {
requestReceived = screenshotRequest
@@ -248,7 +248,7 @@
override fun executeScreenshotsAsync(
screenshotRequest: ScreenshotRequest,
- onSaved: Consumer<Uri>,
+ onSaved: Consumer<Uri?>,
requestCallback: RequestCallback,
) {
runBlocking {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
new file mode 100644
index 0000000..254f1e1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.data.model
+
+import android.content.ComponentName
+import android.graphics.Rect
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREE_FORM
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FULL_SCREEN
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.PIP
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.SPLIT_BOTTOM
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.SPLIT_TOP
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.emptyRootSplit
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.freeForm
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.fullScreen
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.launcher
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.pictureInPicture
+import com.android.systemui.screenshot.policy.ActivityType
+import com.android.systemui.screenshot.policy.TestUserIds
+import com.android.systemui.screenshot.policy.WindowingMode
+import com.android.systemui.screenshot.policy.newChildTask
+import com.android.systemui.screenshot.policy.newRootTaskInfo
+
+/** Tools for creating a [DisplayContentModel] for different usage scenarios. */
+object DisplayContentScenarios {
+
+ data class TaskSpec(val taskId: Int, val userId: Int, val name: String)
+
+ /** Home screen, with only the launcher visible */
+ fun launcherOnly(shadeExpanded: Boolean = false) =
+ DisplayContentModel(
+ displayId = 0,
+ systemUiState = SystemUiState(shadeExpanded = shadeExpanded),
+ rootTasks =
+ listOf(
+ launcher(visible = true),
+ emptyRootSplit,
+ )
+ )
+
+ /** A Full screen activity for the personal (primary) user, with launcher behind it */
+ fun singleFullScreen(spec: TaskSpec, shadeExpanded: Boolean = false) =
+ DisplayContentModel(
+ displayId = 0,
+ systemUiState = SystemUiState(shadeExpanded = shadeExpanded),
+ rootTasks =
+ listOf(
+ fullScreen(spec, visible = true),
+ launcher(visible = false),
+ emptyRootSplit,
+ )
+ )
+
+ fun splitScreenApps(
+ top: TaskSpec,
+ bottom: TaskSpec,
+ focusedTaskId: Int,
+ shadeExpanded: Boolean = false,
+ ): DisplayContentModel {
+ val topBounds = SPLIT_TOP
+ val bottomBounds = SPLIT_BOTTOM
+ return DisplayContentModel(
+ displayId = 0,
+ systemUiState = SystemUiState(shadeExpanded = shadeExpanded),
+ rootTasks =
+ listOf(
+ newRootTaskInfo(
+ taskId = 2,
+ userId = TestUserIds.PERSONAL,
+ bounds = FULL_SCREEN,
+ topActivity =
+ ComponentName.unflattenFromString(
+ if (top.taskId == focusedTaskId) top.name else bottom.name
+ ),
+ ) {
+ listOf(
+ newChildTask(
+ taskId = top.taskId,
+ bounds = topBounds,
+ userId = top.userId,
+ name = top.name
+ ),
+ newChildTask(
+ taskId = bottom.taskId,
+ bounds = bottomBounds,
+ userId = bottom.userId,
+ name = bottom.name
+ )
+ )
+ // Child tasks are ordered bottom-up in RootTaskInfo.
+ // Sort 'focusedTaskId' last.
+ // Boolean natural ordering: [false, true].
+ .sortedBy { it.id == focusedTaskId }
+ },
+ launcher(visible = false),
+ )
+ )
+ }
+
+ fun pictureInPictureApp(
+ pip: TaskSpec,
+ fullScreen: TaskSpec? = null,
+ shadeExpanded: Boolean = false,
+ ): DisplayContentModel {
+ return DisplayContentModel(
+ displayId = 0,
+ systemUiState = SystemUiState(shadeExpanded = shadeExpanded),
+ rootTasks =
+ buildList {
+ add(pictureInPicture(pip))
+ fullScreen?.also { add(fullScreen(it, visible = true)) }
+ add(launcher(visible = (fullScreen == null)))
+ add(emptyRootSplit)
+ }
+ )
+ }
+
+ fun freeFormApps(
+ vararg tasks: TaskSpec,
+ focusedTaskId: Int,
+ shadeExpanded: Boolean = false,
+ ): DisplayContentModel {
+ val freeFormTasks =
+ tasks
+ .map { freeForm(it) }
+ // Root tasks are ordered top-down in List<RootTaskInfo>.
+ // Sort 'focusedTaskId' last (Boolean natural ordering: [false, true])
+ .sortedBy { it.childTaskIds[0] != focusedTaskId }
+ return DisplayContentModel(
+ displayId = 0,
+ systemUiState = SystemUiState(shadeExpanded = shadeExpanded),
+ rootTasks = freeFormTasks + launcher(visible = true) + emptyRootSplit
+ )
+ }
+
+ /**
+ * All of these are arbitrary dimensions exposed for asserting equality on test data.
+ *
+ * They should not be updated nor compared with any real device usage, except to keep them
+ * somewhat sensible in terms of logical position (Re: PIP, SPLIT, etc).
+ */
+ object Bounds {
+ val FULL_SCREEN = Rect(0, 0, 1080, 2400)
+ val PIP = Rect(440, 1458, 1038, 1794)
+ val SPLIT_TOP = Rect(0, 0, 1080, 1187)
+ val SPLIT_BOTTOM = Rect(0, 1213, 1080, 2400)
+ val FREE_FORM = Rect(119, 332, 1000, 1367)
+ }
+
+ /** A collection of task names used in test scenarios */
+ object ActivityNames {
+ /** The main YouTube activity */
+ const val YOUTUBE =
+ "com.google.android.youtube/" +
+ "com.google.android.youtube.app.honeycomb.Shell\$HomeActivity"
+
+ /** The main Files Activity */
+ const val FILES =
+ "com.google.android.apps.nbu.files/" +
+ "com.google.android.apps.nbu.files.home.HomeActivity"
+
+ /** The YouTube picture-in-picture activity */
+ const val YOUTUBE_PIP =
+ "com.google.android.youtube/" +
+ "com.google.android.apps.youtube.app.watchwhile.WatchWhileActivity"
+
+ /** The NexusLauncher activity */
+ const val LAUNCHER =
+ "com.google.android.apps.nexuslauncher/" +
+ "com.google.android.apps.nexuslauncher.NexusLauncherActivity"
+ }
+
+ /**
+ * A set of predefined RootTaskInfo used in test scenarios, matching as closely as possible
+ * actual values returned by ActivityTaskManager
+ */
+ object RootTasks {
+ /** An empty RootTaskInfo with no child tasks. */
+ val emptyWithNoChildTasks =
+ newRootTaskInfo(
+ taskId = 2,
+ visible = true,
+ running = true,
+ numActivities = 0,
+ bounds = FULL_SCREEN,
+ ) {
+ emptyList()
+ }
+
+ /**
+ * The empty RootTaskInfo that is always at the end of a list from ActivityTaskManager when
+ * no other visible activities are in split mode
+ */
+ val emptyRootSplit =
+ newRootTaskInfo(
+ taskId = 2,
+ visible = false,
+ running = false,
+ numActivities = 0,
+ bounds = FULL_SCREEN,
+ activityType = ActivityType.Undefined,
+ ) {
+ listOf(
+ newChildTask(taskId = 3, bounds = FULL_SCREEN, name = ""),
+ newChildTask(taskId = 4, bounds = Rect(0, 2400, 1080, 3600), name = ""),
+ )
+ }
+
+ /** NexusLauncher on the default display. Usually below all other visible tasks */
+ fun launcher(visible: Boolean) =
+ newRootTaskInfo(
+ taskId = 1,
+ activityType = ActivityType.Home,
+ visible = visible,
+ bounds = FULL_SCREEN,
+ topActivity = ComponentName.unflattenFromString(ActivityNames.LAUNCHER),
+ topActivityType = ActivityType.Home,
+ ) {
+ listOf(newChildTask(taskId = 1002, name = ActivityNames.LAUNCHER))
+ }
+
+ /** A full screen Activity */
+ fun fullScreen(task: TaskSpec, visible: Boolean) =
+ newRootTaskInfo(
+ taskId = task.taskId,
+ userId = task.userId,
+ visible = visible,
+ bounds = FULL_SCREEN,
+ topActivity = ComponentName.unflattenFromString(task.name),
+ ) {
+ listOf(newChildTask(taskId = task.taskId, userId = task.userId, name = task.name))
+ }
+
+ /** An activity in Picture-in-Picture mode */
+ fun pictureInPicture(task: TaskSpec) =
+ newRootTaskInfo(
+ taskId = task.taskId,
+ userId = task.userId,
+ bounds = PIP,
+ windowingMode = WindowingMode.PictureInPicture,
+ topActivity = ComponentName.unflattenFromString(task.name),
+ ) {
+ listOf(newChildTask(taskId = task.taskId, userId = userId, name = task.name))
+ }
+
+ /** An activity in FreeForm mode */
+ fun freeForm(task: TaskSpec) =
+ newRootTaskInfo(
+ taskId = task.taskId,
+ userId = task.userId,
+ bounds = FREE_FORM,
+ windowingMode = WindowingMode.Freeform,
+ topActivity = ComponentName.unflattenFromString(task.name),
+ ) {
+ listOf(newChildTask(taskId = task.taskId, userId = userId, name = task.name))
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryKosmos.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryKosmos.kt
new file mode 100644
index 0000000..9d2b56a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.screenshot.data.model.ProfileType
+import com.android.systemui.screenshot.policy.TestUserIds
+
+val Kosmos.profileTypeRepository by
+ Kosmos.Fixture {
+ ProfileTypeRepository { userId ->
+ when (userId) {
+ TestUserIds.WORK -> ProfileType.WORK
+ TestUserIds.CLONE -> ProfileType.CLONE
+ TestUserIds.PRIVATE -> ProfileType.PRIVATE
+ else -> ProfileType.NONE
+ }
+ }
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
new file mode 100644
index 0000000..9e3ae05
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.content.ComponentName
+import android.os.UserHandle
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE_PIP
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.emptyRootSplit
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.fullScreen
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.launcher
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.TaskSpec
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.pictureInPictureApp
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.splitScreenApps
+import com.android.systemui.screenshot.data.model.SystemUiState
+import com.android.systemui.screenshot.data.repository.profileTypeRepository
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.Matched
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
+import com.android.systemui.screenshot.policy.CaptureType.FullScreen
+import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL
+import com.android.systemui.screenshot.policy.TestUserIds.PRIVATE
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+class PrivateProfilePolicyTest {
+ private val kosmos = Kosmos()
+ private val policy = PrivateProfilePolicy(kosmos.profileTypeRepository)
+
+ // TODO:
+ // private app in PIP
+ // private app below personal PIP app
+ // Freeform windows
+
+ @Test
+ fun shadeExpanded_notMatched() = runTest {
+ val result =
+ policy.check(
+ singleFullScreen(
+ spec = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE),
+ shadeExpanded = true
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(NotMatched(PrivateProfilePolicy.NAME, PrivateProfilePolicy.SHADE_EXPANDED))
+ }
+
+ @Test
+ fun noPrivate_notMatched() = runTest {
+ val result =
+ policy.check(
+ singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL))
+ )
+
+ assertThat(result)
+ .isEqualTo(NotMatched(PrivateProfilePolicy.NAME, PrivateProfilePolicy.NO_VISIBLE_TASKS))
+ }
+
+ @Test
+ fun withPrivateFullScreen_isMatched() = runTest {
+ val result =
+ policy.check(
+ singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE))
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ Matched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ component = ComponentName.unflattenFromString(YOUTUBE),
+ owner = UserHandle.of(PRIVATE)
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withPrivateNotVisible_notMatched() = runTest {
+ val result =
+ policy.check(
+ DisplayContentModel(
+ displayId = 0,
+ systemUiState = SystemUiState(shadeExpanded = false),
+ rootTasks =
+ listOf(
+ fullScreen(
+ TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
+ visible = true
+ ),
+ fullScreen(
+ TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+ visible = false
+ ),
+ launcher(visible = false),
+ emptyRootSplit,
+ )
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.NO_VISIBLE_TASKS,
+ )
+ )
+ }
+
+ @Test
+ fun withPrivateFocusedInSplitScreen_isMatched() = runTest {
+ val result =
+ policy.check(
+ splitScreenApps(
+ top = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
+ bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+ focusedTaskId = 1003
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ Matched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ component = ComponentName.unflattenFromString(YOUTUBE),
+ owner = UserHandle.of(PRIVATE)
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withPrivateNotFocusedInSplitScreen_isMatched() = runTest {
+ val result =
+ policy.check(
+ splitScreenApps(
+ top = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
+ bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+ focusedTaskId = 1002
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ Matched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ component = ComponentName.unflattenFromString(FILES),
+ owner = UserHandle.of(PRIVATE)
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withPrivatePictureInPictureApp_isMatched() = runTest {
+ val result =
+ policy.check(
+ pictureInPictureApp(TaskSpec(taskId = 1002, name = YOUTUBE_PIP, userId = PRIVATE))
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ Matched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ component = ComponentName.unflattenFromString(YOUTUBE_PIP),
+ owner = UserHandle.of(PRIVATE)
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withPrivateAppBelowPictureInPictureApp_isMatched() = runTest {
+ val result =
+ policy.check(
+ pictureInPictureApp(
+ pip = TaskSpec(taskId = 1002, name = YOUTUBE_PIP, userId = PERSONAL),
+ fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = PRIVATE),
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ Matched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ component = ComponentName.unflattenFromString(YOUTUBE_PIP),
+ owner = UserHandle.of(PRIVATE)
+ )
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/TestUserIds.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt
copy to packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/TestUserIds.kt
index 7ca7030..7a6d280 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/TestUserIds.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.dagger
+package com.android.systemui.screenshot.policy
-import javax.inject.Qualifier
+import android.os.UserHandle
-/** Wifi logs from [WifiRepositoryViaTrackerLib] in table format. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class WifiTrackerLibTableLog
+object TestUserIds {
+ val PERSONAL: Int = UserHandle.USER_SYSTEM
+ val WORK: Int = 10
+ val CLONE: Int = 11
+ val PRIVATE: Int = 12
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
new file mode 100644
index 0000000..5d35528
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.content.ComponentName
+import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREE_FORM
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FULL_SCREEN
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.SPLIT_TOP
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.TaskSpec
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.freeFormApps
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.pictureInPictureApp
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.splitScreenApps
+import com.android.systemui.screenshot.data.model.SystemUiState
+import com.android.systemui.screenshot.data.repository.profileTypeRepository
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
+import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
+import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL
+import com.android.systemui.screenshot.policy.TestUserIds.WORK
+import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.DESKTOP_MODE_ENABLED
+import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.SHADE_EXPANDED
+import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.WORK_TASK_IS_TOP
+import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.WORK_TASK_NOT_TOP
+import com.android.window.flags.Flags
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+
+class WorkProfilePolicyTest {
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ private val kosmos = Kosmos()
+ private val policy = WorkProfilePolicy(kosmos.profileTypeRepository)
+
+ /**
+ * There is no guarantee that every RootTaskInfo contains a non-empty list of child tasks. Test
+ * the case where the RootTaskInfo would match but child tasks are empty.
+ */
+ @Test
+ fun withEmptyChildTasks_notMatched() = runTest {
+ val result =
+ policy.check(
+ DisplayContentModel(
+ displayId = 0,
+ systemUiState = SystemUiState(shadeExpanded = false),
+ rootTasks = listOf(RootTasks.emptyWithNoChildTasks)
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ WorkProfilePolicy.NAME,
+ WORK_TASK_NOT_TOP,
+ )
+ )
+ }
+
+ @Test
+ fun noWorkApp_notMatched() = runTest {
+ val result =
+ policy.check(
+ singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL))
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ WorkProfilePolicy.NAME,
+ WORK_TASK_NOT_TOP,
+ )
+ )
+ }
+
+ @Test
+ fun withWorkFullScreen_shadeExpanded_notMatched() = runTest {
+ val result =
+ policy.check(
+ singleFullScreen(
+ TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ shadeExpanded = true
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ WorkProfilePolicy.NAME,
+ SHADE_EXPANDED,
+ )
+ )
+ }
+
+ @Test
+ fun withWorkFullScreen_matched() = runTest {
+ val result =
+ policy.check(singleFullScreen(TaskSpec(taskId = 1002, name = FILES, userId = WORK)))
+
+ assertThat(result)
+ .isEqualTo(
+ PolicyResult.Matched(
+ policy = WorkProfilePolicy.NAME,
+ reason = WORK_TASK_IS_TOP,
+ CaptureParameters(
+ type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN),
+ component = ComponentName.unflattenFromString(FILES),
+ owner = UserHandle.of(WORK),
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withWorkFocusedInSplitScreen_matched() = runTest {
+ val result =
+ policy.check(
+ splitScreenApps(
+ top = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+ focusedTaskId = 1002
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ PolicyResult.Matched(
+ policy = WorkProfilePolicy.NAME,
+ reason = WORK_TASK_IS_TOP,
+ CaptureParameters(
+ type = IsolatedTask(taskId = 1002, taskBounds = SPLIT_TOP),
+ component = ComponentName.unflattenFromString(FILES),
+ owner = UserHandle.of(WORK),
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withWorkNotFocusedInSplitScreen_notMatched() = runTest {
+ val result =
+ policy.check(
+ splitScreenApps(
+ top = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+ focusedTaskId = 1003
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ WorkProfilePolicy.NAME,
+ WORK_TASK_NOT_TOP,
+ )
+ )
+ }
+
+ @Test
+ fun withWorkBelowPersonalPictureInPicture_matched() = runTest {
+ val result =
+ policy.check(
+ pictureInPictureApp(
+ pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
+ fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = WORK),
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ PolicyResult.Matched(
+ policy = WorkProfilePolicy.NAME,
+ reason = WORK_TASK_IS_TOP,
+ CaptureParameters(
+ type = IsolatedTask(taskId = 1003, taskBounds = FULL_SCREEN),
+ component = ComponentName.unflattenFromString(FILES),
+ owner = UserHandle.of(WORK),
+ )
+ )
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun withWorkFocusedInFreeForm_matched() = runTest {
+ val result =
+ policy.check(
+ freeFormApps(
+ TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
+ TaskSpec(taskId = 1003, name = FILES, userId = WORK),
+ focusedTaskId = 1003
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ PolicyResult.Matched(
+ policy = WorkProfilePolicy.NAME,
+ reason = WORK_TASK_IS_TOP,
+ CaptureParameters(
+ type = IsolatedTask(taskId = 1003, taskBounds = FREE_FORM),
+ component = ComponentName.unflattenFromString(FILES),
+ owner = UserHandle.of(WORK),
+ )
+ )
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun withWorkFocusedInFreeForm_desktopModeEnabled_notMatched() = runTest {
+ val result =
+ policy.check(
+ freeFormApps(
+ TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
+ TaskSpec(taskId = 1003, name = FILES, userId = WORK),
+ focusedTaskId = 1003
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ WorkProfilePolicy.NAME,
+ DESKTOP_MODE_ENABLED,
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index e611da0..ee03236 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -36,6 +36,7 @@
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -85,6 +86,7 @@
@Mock private lateinit var communalViewModel: CommunalViewModel
@Mock private lateinit var powerManager: PowerManager
@Mock private lateinit var dialogFactory: SystemUIDialogFactory
+ @Mock private lateinit var communalColors: CommunalColors
private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
private lateinit var shadeInteractor: ShadeInteractor
private lateinit var keyguardInteractor: KeyguardInteractor
@@ -116,6 +118,7 @@
keyguardInteractor,
shadeInteractor,
powerManager,
+ communalColors,
kosmos.sceneDataSourceDelegator,
)
testableLooper = TestableLooper.get(this)
@@ -156,6 +159,7 @@
keyguardInteractor,
shadeInteractor,
powerManager,
+ communalColors,
kosmos.sceneDataSourceDelegator,
)
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 0a8e470..7a39a0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -154,6 +154,7 @@
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository;
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -161,6 +162,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
+import com.android.systemui.statusbar.notification.stack.data.repository.FakeHeadsUpNotificationRepository;
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -358,6 +360,10 @@
protected TestScope mTestScope = mKosmos.getTestScope();
protected ShadeInteractor mShadeInteractor;
protected PowerInteractor mPowerInteractor;
+ protected FakeHeadsUpNotificationRepository mFakeHeadsUpNotificationRepository =
+ new FakeHeadsUpNotificationRepository();
+ protected HeadsUpNotificationInteractor mHeadsUpNotificationInteractor =
+ new HeadsUpNotificationInteractor(mFakeHeadsUpNotificationRepository);
protected NotificationPanelViewController.TouchHandler mTouchHandler;
protected ConfigurationController mConfigurationController;
protected SysuiStatusBarStateController mStatusBarStateController;
@@ -384,7 +390,6 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mFeatureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false);
mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false);
mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false);
@@ -730,6 +735,7 @@
mActivityStarter,
mSharedNotificationContainerInteractor,
mActiveNotificationsInteractor,
+ mHeadsUpNotificationInteractor,
mShadeAnimationInteractor,
mKeyguardViewConfigurator,
mDeviceEntryFaceAuthInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 650c45b..81e20c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -46,6 +46,7 @@
import android.animation.ValueAnimator;
import android.graphics.Point;
import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
@@ -62,6 +63,7 @@
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
@@ -1287,6 +1289,7 @@
}
@Test
+ @DisableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
public void shadeExpanded_whenHunIsPresent() {
when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
index 4df7ef5..6631d29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -14,8 +14,11 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.shade
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.HapticFeedbackConstants
@@ -29,10 +32,14 @@
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
+import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
+import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceUntilIdle
@@ -235,4 +242,41 @@
val bottomAreaAlpha by collectLastValue(mFakeKeyguardRepository.bottomAreaAlpha)
assertThat(bottomAreaAlpha).isEqualTo(1f)
}
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ fun shadeExpanded_whenHunIsPresent() = runTest {
+ launch(mainDispatcher) {
+ givenViewAttached()
+
+ // WHEN a pinned heads up is present
+ mFakeHeadsUpNotificationRepository.setNotifications(
+ fakeHeadsUpRowRepository("key", isPinned = true)
+ )
+ }
+ advanceUntilIdle()
+
+ // THEN the panel should be visible
+ assertThat(mNotificationPanelViewController.isExpanded).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ fun shadeExpanded_whenHunIsAnimatingAway() = runTest {
+ launch(mainDispatcher) {
+ givenViewAttached()
+
+ // WHEN a heads up is animating away
+ mFakeHeadsUpNotificationRepository.isHeadsUpAnimatingAway.value = true
+ }
+ advanceUntilIdle()
+
+ // THEN the panel should be visible
+ assertThat(mNotificationPanelViewController.isExpanded).isTrue()
+ }
+
+ private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) =
+ FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply {
+ this.isPinned.value = isPinned
+ }
}
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 cf7c6f4..b89ccef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -75,8 +75,6 @@
import com.android.systemui.scene.FakeWindowRootViewComponent;
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scene.shared.logger.SceneLogger;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
@@ -136,7 +134,6 @@
@Mock private ShadeWindowLogger mShadeWindowLogger;
@Mock private SelectedUserInteractor mSelectedUserInteractor;
@Mock private UserTracker mUserTracker;
- @Mock private SceneContainerFlags mSceneContainerFlags;
@Mock private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
@Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
@@ -185,14 +182,12 @@
mKosmos.getDeviceUnlockedInteractor());
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
- FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
KeyguardTransitionInteractor keyguardTransitionInteractor =
mKosmos.getKeyguardTransitionInteractor();
KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
keyguardRepository,
new FakeCommandQueue(),
powerInteractor,
- sceneContainerFlags,
new FakeKeyguardBouncerRepository(),
new ConfigurationInteractor(configurationRepository),
shadeRepository,
@@ -256,7 +251,6 @@
mShadeWindowLogger,
() -> mSelectedUserInteractor,
mUserTracker,
- mSceneContainerFlags,
() -> communalInteractor) {
@Override
protected boolean isDebuggable() {
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 b04503b..da09579 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -38,8 +38,6 @@
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
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.keyevent.domain.interactor.SysUIKeyEventHandler
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -166,8 +164,6 @@
.thenReturn(emptyFlow<TransitionStep>())
featureFlagsClassic = FakeFeatureFlagsClassic()
- featureFlagsClassic.set(TRACKPAD_GESTURE_COMMON, true)
- featureFlagsClassic.set(TRACKPAD_GESTURE_FEATURES, false)
featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
@@ -578,6 +574,14 @@
assertEquals(keyEvent, falsingCollector.lastKeyEvent)
}
+ @Test
+ fun cancelCurrentTouch_callsDragDownHelper() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ underTest.cancelCurrentTouch()
+
+ verify(dragDownHelper).stopDragging()
+ }
+
companion object {
private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
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 ba8eb6f..f380b6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -155,8 +155,6 @@
.thenReturn(emptyFlow())
val 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.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 20d877e..77ad17a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -62,7 +62,6 @@
import com.android.systemui.res.R;
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
import com.android.systemui.scene.shared.logger.SceneLogger;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
@@ -208,14 +207,12 @@
mock(SceneLogger.class),
mKosmos.getDeviceUnlockedInteractor());
- FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
KeyguardTransitionInteractor keyguardTransitionInteractor =
mKosmos.getKeyguardTransitionInteractor();
KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
mKeyguardRepository,
new FakeCommandQueue(),
powerInteractor,
- sceneContainerFlags,
new FakeKeyguardBouncerRepository(),
new ConfigurationInteractor(configurationRepository),
mShadeRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 433c95a..6bdd3ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -33,7 +33,6 @@
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
@@ -95,7 +94,6 @@
headsUpManager,
PowerInteractorFactory.create().powerInteractor,
ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher),
- kosmos.sceneContainerFlags,
kosmos::sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt
index eb418fd..4679a58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt
@@ -19,11 +19,12 @@
import android.content.Context
import android.content.res.Resources
import android.content.res.TypedArray
+import android.platform.test.annotations.PlatinumTest
import android.testing.AndroidTestingRunner
import android.util.AttributeSet
import androidx.test.filters.SmallTest
-import com.android.systemui.shared.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.R
import com.android.systemui.shared.shadow.DoubleShadowTextClock
import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertTrue
@@ -36,6 +37,7 @@
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
+@PlatinumTest(focusArea = "sysui")
@SmallTest
@RunWith(AndroidTestingRunner::class)
class DoubleShadowTextClockTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
index 56fc0c7..a05a23b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
@@ -21,7 +21,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@@ -34,21 +34,19 @@
@RunWith(AndroidTestingRunner::class)
class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() {
- private val progressProvider = TestUnfoldTransitionProvider()
+ private val progressProvider = FakeUnfoldTransitionProvider()
- @Mock
- private lateinit var parent: ViewGroup
+ @Mock private lateinit var parent: ViewGroup
- @Mock
- private lateinit var shouldBeAnimated: () -> Boolean
+ @Mock private lateinit var shouldBeAnimated: () -> Boolean
private lateinit var animator: UnfoldConstantTranslateAnimator
private val viewsIdToRegister
get() =
setOf(
- ViewIdToTranslate(START_VIEW_ID, Direction.START, shouldBeAnimated),
- ViewIdToTranslate(END_VIEW_ID, Direction.END, shouldBeAnimated)
+ ViewIdToTranslate(START_VIEW_ID, Direction.START, shouldBeAnimated),
+ ViewIdToTranslate(END_VIEW_ID, Direction.END, shouldBeAnimated)
)
@Before
@@ -122,11 +120,12 @@
progressProvider.onTransitionStarted()
progressProvider.onTransitionProgress(0f)
- val rtlMultiplier = if (layoutDirection == View.LAYOUT_DIRECTION_LTR) {
- 1
- } else {
- -1
- }
+ val rtlMultiplier =
+ if (layoutDirection == View.LAYOUT_DIRECTION_LTR) {
+ 1
+ } else {
+ -1
+ }
list.forEach { (view, direction) ->
assertEquals(
(-MAX_TRANSLATION * direction * rtlMultiplier).toInt(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
index 8841f48..3cb48d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
@@ -15,7 +15,7 @@
package com.android.systemui.shared.animation
import android.graphics.Point
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.view.Display
import android.view.Surface.ROTATION_0
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
index c39b29f..e9222c3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
@@ -37,8 +37,8 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 02954b8..7ddf7a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -26,9 +26,9 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index d2fc087..be5af88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -55,7 +55,6 @@
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.data.repository.FakeShadeRepository
@@ -87,8 +86,8 @@
import org.mockito.Mockito
import org.mockito.Mockito.mock
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(AndroidTestingRunner::class)
@@ -135,7 +134,6 @@
val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
val featureFlags = FakeFeatureFlagsClassic()
val shadeRepository = FakeShadeRepository()
- val sceneContainerFlags = FakeSceneContainerFlags()
val configurationRepository = FakeConfigurationRepository()
val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
@@ -146,7 +144,6 @@
keyguardRepository,
FakeCommandQueue(),
powerInteractor,
- sceneContainerFlags,
FakeKeyguardBouncerRepository(),
ConfigurationInteractor(configurationRepository),
shadeRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
index 16eb1d9..b18b7f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.commandline
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
index 5bc75e8..7e88ae0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.connectivity
import android.os.UserManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.lifecycle.Lifecycle
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
index 44e3bb4..7bd77a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
@@ -22,14 +22,14 @@
import android.os.HandlerThread;
import android.telephony.SubscriptionInfo;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.connectivity.NetworkController.EmergencyListener;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.connectivity.NetworkController.EmergencyListener;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
index a226ded..7aed4f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
@@ -15,8 +15,8 @@
*/
package com.android.systemui.statusbar.connectivity
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import junit.framework.Assert.assertFalse
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index f667b83..461d804 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -39,11 +39,12 @@
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.settingslib.SignalIcon.MobileIconGroup;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
index f6f939a..3bbf06d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
@@ -19,10 +19,11 @@
import static junit.framework.Assert.assertEquals;
import android.net.NetworkCapabilities;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
+import androidx.test.filters.SmallTest;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index 375ca063..35609a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -33,17 +33,18 @@
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
-import com.android.systemui.res.R;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.log.LogBuffer;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
index 9e73487..5bf0a94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
@@ -16,8 +16,8 @@
package com.android.systemui.statusbar.connectivity
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
index 2b94561..6b2ee76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
@@ -19,12 +19,13 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.widget.FrameLayout;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
index fda8f51..fc4702c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
@@ -42,10 +42,11 @@
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
index eb692eb..eafa78e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
@@ -25,13 +25,14 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.res.R;
@@ -40,6 +41,9 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.RowContentBindParams;
import com.android.systemui.statusbar.notification.row.RowContentBindStage;
+import com.android.systemui.statusbar.notification.row.RowInflaterTask;
+import com.android.systemui.statusbar.notification.row.RowInflaterTaskLogger;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -114,20 +118,25 @@
private NotificationEntry addGroup(int size) {
NotificationEntry summary = new NotificationEntryBuilder().build();
- summary.setRow(createRow());
+ summary.setRow(createRow(summary));
ArrayList<NotificationEntry> children = new ArrayList<>();
for (int i = 0; i < size; i++) {
NotificationEntry child = new NotificationEntryBuilder().build();
- child.setRow(createRow());
+ child.setRow(createRow(child));
children.add(child);
}
mGroupNotifs.put(summary, children);
return summary;
}
- private ExpandableNotificationRow createRow() {
+ private ExpandableNotificationRow createRow(NotificationEntry entry) {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ inflater.setFactory2(
+ new RowInflaterTask.RowAsyncLayoutInflater(entry, new FakeSystemClock(), mock(
+ RowInflaterTaskLogger.class)));
+
ExpandableNotificationRow row = (ExpandableNotificationRow)
- LayoutInflater.from(mContext).inflate(R.layout.status_bar_notification_row, null);
+ inflater.inflate(R.layout.status_bar_notification_row, null);
row.getPrivateLayout().setContractedChild(new View(mContext));
row.getPrivateLayout().setExpandedChild(new View(mContext));
return row;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
index a6381d1..5b72ca0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
@@ -25,10 +25,11 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -38,7 +39,6 @@
import org.junit.Before;
import org.junit.Test;
-
@SmallTest
@org.junit.runner.RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
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 2ef4374..2d8e692 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,8 +18,8 @@
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.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -30,7 +30,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.UiThreadTest;
import android.util.FloatProperty;
@@ -38,6 +37,8 @@
import android.view.View;
import android.view.animation.Interpolator;
+import androidx.test.filters.SmallTest;
+
import com.android.app.animation.Interpolators;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 54a6523..bb68ec5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -138,7 +138,6 @@
mHeadsUpManager,
mPowerInteractor,
mActiveNotificationsInteractor,
- mKosmos.getSceneContainerFlags(),
() -> mKosmos.getSceneInteractor());
mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 01492f6..aa79c23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -772,8 +772,7 @@
row.setUserExpanded(true);
row.setOnKeyguard(false);
row.setSensitive(/* sensitive= */true, /* hideSensitive= */false);
- row.setHideSensitive(/* hideSensitive= */true, /* animated= */false,
- /* delay= */0L, /* duration= */0L);
+ row.setHideSensitiveForIntrinsicHeight(/* hideSensitive= */true);
// THEN
assertThat(row.isExpanded()).isFalse();
@@ -787,8 +786,7 @@
row.setUserExpanded(true);
row.setOnKeyguard(false);
row.setSensitive(/* sensitive= */true, /* hideSensitive= */false);
- row.setHideSensitive(/* hideSensitive= */false, /* animated= */false,
- /* delay= */0L, /* duration= */0L);
+ row.setHideSensitiveForIntrinsicHeight(/* hideSensitive= */false);
// THEN
assertThat(row.isExpanded()).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
index 9c20e54..ffb8646 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
@@ -45,7 +45,6 @@
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.UiThreadTest;
import android.view.LayoutInflater;
@@ -53,6 +52,8 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 91e4666..7332bc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -79,10 +79,11 @@
initMocks(this)
fakeParent =
spy(FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE })
+ val mockEntry = createMockNotificationEntry()
row =
spy(
- ExpandableNotificationRow(mContext, /* attrs= */ null).apply {
- entry = createMockNotificationEntry()
+ ExpandableNotificationRow(mContext, /* attrs= */ null, mockEntry).apply {
+ entry = mockEntry
}
)
ViewUtils.attachView(fakeParent)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 3c1f559..97cb11e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -65,7 +65,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
@@ -73,11 +72,13 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.MetricsLogger;
import com.android.settingslib.notification.ConversationIconFactory;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
+import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.SbnBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index db053d8..9e2856d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -180,7 +180,6 @@
mHeadsUpManager,
PowerInteractorFactory.create().getPowerInteractor(),
mActiveNotificationsInteractor,
- mKosmos.getSceneContainerFlags(),
() -> mKosmos.getSceneInteractor()
);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
index 65a960b..1b85dfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -45,6 +45,7 @@
import com.android.internal.statusbar.statusBarService
import com.android.systemui.SysuiTestCase
import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.people.widget.PeopleSpaceWidgetManager
@@ -55,7 +56,6 @@
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.shade.shadeControllerSceneImpl
@@ -93,6 +93,7 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
+@EnableSceneContainer
class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
private val testNotificationChannel =
NotificationChannel(
@@ -143,8 +144,6 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- val sceneContainerFlags = kosmos.fakeSceneContainerFlags
- sceneContainerFlags.enabled = true
allowTestableLooperAsMainThread()
helper = NotificationTestHelper(mContext, mDependency)
Mockito.`when`(accessibilityManager.isTouchExplorationEnabled).thenReturn(false)
@@ -156,9 +155,9 @@
headsUpManager,
create().powerInteractor,
activeNotificationsInteractor,
- sceneContainerFlags,
- { sceneInteractor },
- )
+ ) {
+ sceneInteractor
+ }
gutsManager =
NotificationGutsManager(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index f31b1c4..13ced92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -55,20 +55,20 @@
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.telecom.TelecomManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.testing.UiThreadTest;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.Dependency;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -82,8 +82,6 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.HashSet;
-import java.util.Set;
import java.util.concurrent.CountDownLatch;
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
index 0a15f0d..4a91cd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
@@ -16,16 +16,23 @@
package com.android.systemui.statusbar.notification.row;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+
import android.provider.Settings;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableResources;
import android.testing.UiThreadTest;
import android.util.KeyValueListParser;
-import com.android.systemui.res.R;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+import com.android.systemui.res.R;
import org.junit.Before;
import org.junit.Test;
@@ -33,12 +40,6 @@
import java.util.ArrayList;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertTrue;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.mock;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@UiThreadTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
index ccedd36..51665d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
@@ -41,7 +41,6 @@
import android.graphics.drawable.Icon;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.text.SpannableString;
@@ -50,10 +49,12 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Dependency;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -65,8 +66,6 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.HashSet;
-import java.util.Set;
import java.util.concurrent.CountDownLatch;
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 9a7b8ec..745d20d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.statusbar.notification.stack
+import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.LayoutInflater
@@ -14,6 +15,7 @@
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
@@ -457,8 +459,13 @@
expansionFraction: Float,
expectedAlpha: Float
) {
+ val sbnMock: StatusBarNotification = mock()
+ val mockEntry = mock<NotificationEntry>().apply {
+ whenever(this.sbn).thenReturn(sbnMock)
+ }
+ val row = ExpandableNotificationRow(mContext, null, mockEntry)
whenever(ambientState.lastVisibleBackgroundChild)
- .thenReturn(ExpandableNotificationRow(mContext, null))
+ .thenReturn(row)
whenever(ambientState.isExpansionChanging).thenReturn(true)
whenever(ambientState.expansionFraction).thenReturn(expansionFraction)
whenever(hostLayoutController.speedBumpIndex).thenReturn(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 912ecb3..3a427f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -22,6 +22,8 @@
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -36,8 +38,6 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
import android.metrics.LogMaker;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
@@ -56,8 +56,6 @@
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -126,7 +124,6 @@
public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
- private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock private NotificationGutsManager mNotificationGutsManager;
@Mock private NotificationsController mNotificationsController;
@Mock private NotificationVisibilityProvider mVisibilityProvider;
@@ -193,8 +190,6 @@
allowTestableLooperAsMainThread();
MockitoAnnotations.initMocks(this);
- mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
-
when(mNotificationSwipeHelperBuilder.build()).thenReturn(mNotificationSwipeHelper);
when(mKeyguardTransitionRepo.getTransitions()).thenReturn(emptyFlow());
}
@@ -299,36 +294,11 @@
@Test
@DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_bouncerShowing_flagOff_hideEmptyView() {
+ public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
- // WHEN the flag is off and *only* CentralSurfaces has bouncer as showing
- mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, false);
- mController.setBouncerShowingFromCentralSurfaces(true);
- when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(false);
-
- setupShowEmptyShadeViewState(true);
- reset(mNotificationStackScrollLayout);
- mController.updateShowEmptyShadeView();
-
- // THEN the CentralSurfaces value is used. Since the bouncer is showing, we hide the empty
- // view.
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- /* visible= */ false,
- /* areNotificationsHiddenInShade= */ false);
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_bouncerShowing_flagOn_hideEmptyView() {
- when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
- initController(/* viewIsAttached= */ true);
-
- // WHEN the flag is on and *only* PrimaryBouncerInteractor has bouncer as showing
- mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(true);
- mController.setBouncerShowingFromCentralSurfaces(false);
setupShowEmptyShadeViewState(true);
reset(mNotificationStackScrollLayout);
@@ -343,36 +313,11 @@
@Test
@DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_bouncerNotShowing_flagOff_showEmptyView() {
+ public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
- // WHEN the flag is off and *only* CentralSurfaces has bouncer as not showing
- mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, false);
- mController.setBouncerShowingFromCentralSurfaces(false);
- when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(true);
-
- setupShowEmptyShadeViewState(true);
- reset(mNotificationStackScrollLayout);
- mController.updateShowEmptyShadeView();
-
- // THEN the CentralSurfaces value is used. Since the bouncer isn't showing, we can show the
- // empty view.
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- /* visible= */ true,
- /* areNotificationsHiddenInShade= */ false);
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_bouncerNotShowing_flagOn_showEmptyView() {
- when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
- initController(/* viewIsAttached= */ true);
-
- // WHEN the flag is on and *only* PrimaryBouncerInteractor has bouncer as not showing
- mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(false);
- mController.setBouncerShowingFromCentralSurfaces(true);
setupShowEmptyShadeViewState(true);
reset(mNotificationStackScrollLayout);
@@ -1018,7 +963,6 @@
mStackLogger,
mLogger,
mNotificationStackSizeCalculator,
- mFeatureFlags,
mNotificationTargetsHelper,
mSecureSettings,
mock(NotificationDismissibilityProvider.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 1e058ca..89ae9f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -90,6 +90,7 @@
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -106,6 +107,7 @@
import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
+import java.util.function.Consumer;
/**
* Tests for {@link NotificationStackScrollLayout}.
@@ -1044,6 +1046,96 @@
assertFalse(mStackScroller.getIsBeingDragged());
}
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_setsHeadsUpAnimatingAway() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ // WHEN we generate a disappear event
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+
+ // THEN headsUpAnimatingAway is true
+ verify(headsUpAnimatingAwayListener).accept(true);
+ assertTrue(mStackScroller.mHeadsUpAnimatingAway);
+ }
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_stackExpanded_headsUpAnimatingAwayNotSet() {
+ // GIVEN NSSL would be ready for HUN animations, BUT it is expanded
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ assertTrue("Should be expanded by default.", mStackScroller.isExpanded());
+ mStackScroller.setHeadsUpAnimatingAwayListener(headsUpAnimatingAwayListener);
+ mStackScroller.setAnimationsEnabled(true);
+ mStackScroller.setHeadsUpGoingAwayAnimationsAllowed(true);
+
+ // WHEN we generate a disappear event
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+
+ // THEN nothing happens
+ verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
+ assertFalse(mStackScroller.mHeadsUpAnimatingAway);
+ }
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_pendingAppearEvent_headsUpAnimatingAwayNotSet() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+ // BUT there is a pending appear event
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+
+ // WHEN we generate a disappear event
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+
+ // THEN nothing happens
+ verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
+ assertFalse(mStackScroller.mHeadsUpAnimatingAway);
+ }
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ public void testGenerateHeadsUpAppearEvent_headsUpAnimatingAwayNotSet() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ // WHEN we generate a disappear event
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+
+ // THEN headsUpAnimatingWay is not set
+ verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
+ assertFalse(mStackScroller.mHeadsUpAnimatingAway);
+ }
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ public void testOnChildAnimationsFinished_resetsheadsUpAnimatingAway() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ // AND there is a HUN animating away
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+ assertTrue("a HUN should be animating away", mStackScroller.mHeadsUpAnimatingAway);
+
+ // WHEN the child animations are finished
+ mStackScroller.onChildAnimationFinished();
+
+ // THEN headsUpAnimatingAway is false
+ verify(headsUpAnimatingAwayListener).accept(false);
+ assertFalse(mStackScroller.mHeadsUpAnimatingAway);
+ }
+
private MotionEvent captureTouchSentToSceneFramework() {
ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class);
verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture());
@@ -1056,6 +1148,14 @@
mStackScroller.setStatusBarState(state);
}
+ private void prepareStackScrollerForHunAnimations(
+ Consumer<Boolean> headsUpAnimatingAwayListener) {
+ mStackScroller.setHeadsUpAnimatingAwayListener(headsUpAnimatingAwayListener);
+ mStackScroller.setIsExpanded(false);
+ mStackScroller.setAnimationsEnabled(true);
+ mStackScroller.setHeadsUpGoingAwayAnimationsAllowed(true);
+ }
+
private ExpandableNotificationRow createClearableRow() {
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
NotificationEntry entry = mock(NotificationEntry.class);
@@ -1116,4 +1216,6 @@
assertThat(mActual.getY()).isEqualTo(expected.getY());
}
}
+
+ private abstract static class BooleanConsumer implements Consumer<Boolean> { }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
index 4bfd7e3..e2ac203 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
@@ -31,9 +31,9 @@
import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON
import com.android.systemui.power.shared.model.WakefulnessState.STARTING_TO_SLEEP
import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
-import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractorImpl
+import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import com.android.systemui.util.animation.data.repository.FakeAnimationStatusRepository
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -59,7 +59,7 @@
private val animationStatus = FakeAnimationStatusRepository()
private val configurationController = FakeConfigurationController()
- private val unfoldTransitionProgressProvider = TestUnfoldTransitionProvider()
+ private val unfoldTransitionProgressProvider = FakeUnfoldTransitionProvider()
private val powerRepository = FakePowerRepository()
private val powerInteractor =
PowerInteractor(
@@ -69,11 +69,6 @@
statusBarStateController = mock()
)
- private val unfoldTransitionRepository =
- UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider))
- private val unfoldTransitionInteractor =
- UnfoldTransitionInteractorImpl(unfoldTransitionRepository)
-
private val configurationRepository =
ConfigurationRepositoryImpl(
configurationController,
@@ -83,6 +78,11 @@
)
private val configurationInteractor = ConfigurationInteractor(configurationRepository)
+ private val unfoldTransitionRepository =
+ UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider))
+ private val unfoldTransitionInteractor =
+ UnfoldTransitionInteractor(unfoldTransitionRepository, configurationInteractor)
+
private lateinit var configuration: Configuration
private lateinit var underTest: HideNotificationsInteractor
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 e9ec323..f49dc98 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
@@ -37,12 +37,13 @@
import android.os.Handler;
import android.os.PowerManager;
import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.TestableResources;
import android.view.ViewRootImpl;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 25e4728..041e61c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -31,6 +31,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
@@ -101,6 +102,8 @@
import com.android.systemui.communal.shared.model.CommunalScenes;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.DisableSceneContainer;
+import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.fragments.FragmentService;
@@ -120,7 +123,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
@@ -336,8 +339,6 @@
private final DumpManager mDumpManager = new DumpManager();
private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager);
- private final FakeSceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags();
-
private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor =
mKosmos.getBrightnessMirrorShowingInteractor();
@@ -351,7 +352,9 @@
// Turn AOD on and toggle feature flag for jank fixes
mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true);
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
- mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
+ if (!SceneContainerFlag.isEnabled()) {
+ mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
+ }
IThermalService thermalService = mock(IThermalService.class);
mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
@@ -426,22 +429,25 @@
((Runnable) invocation.getArgument(0)).run();
return null;
}).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
-
- mShadeController = spy(new ShadeControllerImpl(
- mCommandQueue,
- mMainExecutor,
- mock(WindowRootViewVisibilityInteractor.class),
- mKeyguardStateController,
- mStatusBarStateController,
- mStatusBarKeyguardViewManager,
- mStatusBarWindowController,
- mDeviceProvisionedController,
- mNotificationShadeWindowController,
- 0,
- () -> mNotificationPanelViewController,
- () -> mAssistManager,
- () -> mNotificationGutsManager
- ));
+ if (SceneContainerFlag.isEnabled()) {
+ mShadeController = spy(mKosmos.getShadeController());
+ } else {
+ mShadeController = spy(new ShadeControllerImpl(
+ mCommandQueue,
+ mMainExecutor,
+ mock(WindowRootViewVisibilityInteractor.class),
+ mKeyguardStateController,
+ mStatusBarStateController,
+ mStatusBarKeyguardViewManager,
+ mStatusBarWindowController,
+ mDeviceProvisionedController,
+ mNotificationShadeWindowController,
+ 0,
+ () -> mNotificationPanelViewController,
+ () -> mAssistManager,
+ () -> mNotificationGutsManager
+ ));
+ }
mShadeController.setNotificationShadeWindowViewController(
mNotificationShadeWindowViewController);
mShadeController.setNotificationPresenter(mNotificationPresenter);
@@ -562,7 +568,6 @@
mUserTracker,
() -> mFingerprintManager,
mActivityStarter,
- mSceneContainerFlags,
mBrightnessMirrorShowingInteractor
);
mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver);
@@ -572,8 +577,7 @@
any(NotificationPanelViewController.class),
any(ShadeExpansionStateManager.class),
any(BiometricUnlockController.class),
- any(ViewGroup.class),
- any(KeyguardBypassController.class)))
+ any(ViewGroup.class)))
.thenReturn(mStatusBarKeyguardViewManager);
when(mKeyguardViewMediator.getViewMediatorCallback()).thenReturn(
@@ -1095,25 +1099,25 @@
}
@Test
+ @EnableSceneContainer
public void brightnesShowingChanged_flagEnabled_ScrimControllerNotified() {
- mSceneContainerFlags.setEnabled(true);
mCentralSurfaces.registerCallbacks();
mBrightnessMirrorShowingInteractor.setMirrorShowing(true);
mTestScope.getTestScheduler().runCurrent();
- verify(mScrimController).transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+ verify(mScrimController, atLeastOnce()).transitionTo(ScrimState.BRIGHTNESS_MIRROR);
mBrightnessMirrorShowingInteractor.setMirrorShowing(false);
mTestScope.getTestScheduler().runCurrent();
ArgumentCaptor<ScrimState> captor = ArgumentCaptor.forClass(ScrimState.class);
// The default is to call the one with the callback argument
- verify(mScrimController).transitionTo(captor.capture(), any());
+ verify(mScrimController, atLeastOnce()).transitionTo(captor.capture(), any());
assertThat(captor.getValue()).isNotEqualTo(ScrimState.BRIGHTNESS_MIRROR);
}
@Test
+ @DisableSceneContainer
public void brightnesShowingChanged_flagDisabled_ScrimControllerNotified() {
- mSceneContainerFlags.setEnabled(false);
mCentralSurfaces.registerCallbacks();
mBrightnessMirrorShowingInteractor.setMirrorShowing(true);
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 6fecbb0..7cb41f1 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
@@ -34,8 +34,8 @@
import android.os.Handler;
import android.os.PowerManager;
import android.provider.Settings;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.keyguard.KeyguardUpdateMonitor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 7362e34..4dd97bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -17,13 +17,15 @@
package com.android.systemui.statusbar.phone
import android.content.pm.PackageManager
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
@@ -72,6 +74,7 @@
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
@Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock private lateinit var devicePostureController: DevicePostureController
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var packageManager: PackageManager
@@ -138,7 +141,8 @@
private fun initKeyguardBypassController() {
keyguardBypassController =
KeyguardBypassController(
- context,
+ context.resources,
+ context.packageManager,
testScope.backgroundScope,
tunerService,
statusBarStateController,
@@ -146,7 +150,8 @@
keyguardStateController,
shadeRepository,
devicePostureController,
- dumpManager
+ keyguardTransitionInteractor,
+ dumpManager,
)
}
@@ -302,4 +307,26 @@
job.cancel()
}
}
+
+ @Test
+ fun canBypass_bypassDisabled() {
+ context.orCreateTestableResources.addOverride(
+ R.integer.config_face_unlock_bypass_override,
+ 2 /* FACE_UNLOCK_BYPASS_NEVER */
+ )
+ initKeyguardBypassController()
+ assertThat(keyguardBypassController.canBypass()).isFalse()
+ }
+
+ @Test
+ fun canBypass_bypassEnabled_alternateBouncerShowing() {
+ context.orCreateTestableResources.addOverride(
+ R.integer.config_face_unlock_bypass_override,
+ 1 /* FACE_UNLOCK_BYPASS_ALWAYS */
+ )
+ initKeyguardBypassController()
+ whenever(keyguardTransitionInteractor.getCurrentState())
+ .thenReturn(KeyguardState.ALTERNATE_BOUNCER)
+ assertThat(keyguardBypassController.canBypass()).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index dc3db4c..a6a4f24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -20,9 +20,9 @@
import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
+import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
-import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND;
import static com.google.common.truth.Truth.assertThat;
@@ -172,7 +172,6 @@
mKeyguardRepository,
mCommandQueue,
PowerInteractorFactory.create().getPowerInteractor(),
- mKosmos.getSceneContainerFlags(),
new FakeKeyguardBouncerRepository(),
new ConfigurationInteractor(new FakeConfigurationRepository()),
new FakeShadeRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 1463680..d365663 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -33,7 +33,6 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeControllerImpl
import com.android.systemui.shade.ShadeLogger
@@ -51,6 +50,8 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import javax.inject.Provider
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
@@ -61,8 +62,6 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.Optional
-import javax.inject.Provider
@SmallTest
class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@@ -296,7 +295,6 @@
Optional.of(sysuiUnfoldComponent),
Optional.of(progressProvider),
featureFlags,
- FakeSceneContainerFlags(),
userChipViewModel,
centralSurfacesImpl,
statusBarWindowStateController,
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 f8c01e7..f04a5ab 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
@@ -241,8 +241,7 @@
mShadeLockscreenInteractor,
new ShadeExpansionStateManager(),
mBiometricUnlockController,
- mNotificationContainer,
- mBypassController);
+ mNotificationContainer);
mStatusBarKeyguardViewManager.show(null);
ArgumentCaptor<PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
index feff046..1ec1765 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
@@ -8,7 +8,7 @@
import android.view.WindowManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.whenever
@@ -23,17 +23,14 @@
@TestableLooper.RunWithLooper
class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var windowManager: WindowManager
+ @Mock private lateinit var windowManager: WindowManager
- @Mock
- private lateinit var display: Display
+ @Mock private lateinit var display: Display
- @Mock
- private lateinit var currentActivityTypeProvider: CurrentActivityTypeProvider
+ @Mock private lateinit var currentActivityTypeProvider: CurrentActivityTypeProvider
private val view: View = View(context)
- private val progressProvider = TestUnfoldTransitionProvider()
+ private val progressProvider = FakeUnfoldTransitionProvider()
private val scopedProvider = ScopedUnfoldTransitionProgressProvider(progressProvider)
private lateinit var controller: StatusBarMoveFromCenterAnimationController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
index 1e628bd..dedd0af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
@@ -13,68 +13,103 @@
*/
package com.android.systemui.statusbar.phone
+import android.app.Dialog
import android.content.res.Configuration
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.view.WindowManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
class SystemUIBottomSheetDialogTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
private val configurationController = mock<ConfigurationController>()
private val config = mock<Configuration>()
+ private val delegate = mock<DialogDelegate<Dialog>>()
private lateinit var dialog: SystemUIBottomSheetDialog
@Before
fun setup() {
- dialog = SystemUIBottomSheetDialog(mContext, configurationController)
+ dialog =
+ with(kosmos) {
+ SystemUIBottomSheetDialog(
+ context,
+ testScope.backgroundScope,
+ configurationController,
+ delegate,
+ TestLayout(),
+ 0,
+ )
+ }
}
@Test
fun onStart_registersConfigCallback() {
- dialog.show()
+ kosmos.testScope.runTest {
+ dialog.show()
+ runCurrent()
- verify(configurationController).addCallback(any())
+ verify(configurationController).addCallback(any())
+ }
}
@Test
fun onStop_unregisterConfigCallback() {
- dialog.show()
- dialog.dismiss()
+ kosmos.testScope.runTest {
+ dialog.show()
+ runCurrent()
+ dialog.dismiss()
+ runCurrent()
- verify(configurationController).removeCallback(any())
+ verify(configurationController).removeCallback(any())
+ }
}
@Test
- fun onConfigurationChanged_calledInSubclass() {
- var onConfigChangedCalled = false
- val subclass =
- object : SystemUIBottomSheetDialog(mContext, configurationController) {
- override fun onConfigurationChanged() {
- onConfigChangedCalled = true
- }
- }
+ fun onConfigurationChanged_calledInDelegate() {
+ kosmos.testScope.runTest {
+ dialog.show()
+ runCurrent()
+ val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
+ verify(configurationController).addCallback(capture(captor))
- subclass.show()
+ captor.value.onConfigChanged(config)
+ runCurrent()
- val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
- verify(configurationController).addCallback(capture(captor))
- captor.value.onConfigChanged(config)
+ verify(delegate).onConfigurationChanged(any(), any())
+ }
+ }
- assertThat(onConfigChangedCalled).isTrue()
+ private class TestLayout : SystemUIBottomSheetDialog.WindowLayout {
+ override fun calculate(): Flow<SystemUIBottomSheetDialog.WindowLayout.Layout> {
+ return flowOf(
+ SystemUIBottomSheetDialog.WindowLayout.Layout(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.MATCH_PARENT,
+ )
+ )
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 9b6940e..598b12c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -137,6 +137,7 @@
wifiRepository,
mock(),
mock(),
+ mock(),
)
demoRepo =
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 07abd27..b5525b1 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
@@ -26,6 +26,7 @@
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo
+import android.net.wifi.WifiManager
import android.os.ParcelUuid
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
@@ -46,8 +47,10 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.log.LogBuffer
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -64,10 +67,14 @@
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import com.android.wifitrackerlib.MergedCarrierEntry
+import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiPickerTracker
import com.google.common.truth.Truth.assertThat
import java.util.UUID
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -80,6 +87,7 @@
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertTrue
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
@@ -96,7 +104,11 @@
class MobileConnectionsRepositoryTest : SysuiTestCase() {
private val flags =
- FakeFeatureFlagsClassic().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
+ FakeFeatureFlagsClassic().also {
+ it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
+ it.set(Flags.INSTANT_TETHER, true)
+ it.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+ }
private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
@@ -113,9 +125,17 @@
@Mock private lateinit var summaryLogger: TableLogBuffer
@Mock private lateinit var logBufferFactory: TableLogBufferFactory
@Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var wifiManager: WifiManager
+ @Mock private lateinit var wifiPickerTrackerFactory: WifiPickerTrackerFactory
+ @Mock private lateinit var wifiPickerTracker: WifiPickerTracker
+ @Mock private lateinit var wifiTableLogBuffer: TableLogBuffer
private val mobileMappings = FakeMobileMappingsProxy()
private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
+ private val mainExecutor = FakeExecutor(FakeSystemClock())
+ private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock())
+ private val wifiPickerTrackerCallback =
+ argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
private val dispatcher = StandardTestDispatcher()
private val testScope = TestScope(dispatcher)
@@ -138,6 +158,9 @@
mock<TableLogBuffer>()
}
+ whenever(wifiPickerTrackerFactory.create(any(), capture(wifiPickerTrackerCallback), any()))
+ .thenReturn(wifiPickerTracker)
+
// For convenience, set up the subscription info callbacks
whenever(subscriptionManager.getActiveSubscriptionInfo(anyInt())).thenAnswer { invocation ->
when (invocation.getArgument(0) as Int) {
@@ -164,15 +187,14 @@
wifiRepository =
WifiRepositoryImpl(
- fakeBroadcastDispatcher,
- connectivityManager,
- connectivityRepository,
- mock(),
- mock(),
- FakeExecutor(FakeSystemClock()),
- dispatcher,
+ flags,
testScope.backgroundScope,
- mock(),
+ mainExecutor,
+ dispatcher,
+ wifiPickerTrackerFactory,
+ wifiManager,
+ wifiLogBuffer,
+ wifiTableLogBuffer,
)
carrierConfigRepository =
@@ -229,6 +251,7 @@
wifiRepository,
fullConnectionFactory,
updateMonitor,
+ mock(),
)
testScope.runCurrent()
@@ -276,7 +299,7 @@
testScope.runTest {
val latest by collectLastValue(underTest.subscriptions)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -289,7 +312,7 @@
testScope.runTest {
val latest by collectLastValue(underTest.subscriptions)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -443,7 +466,7 @@
collectLastValue(underTest.subscriptions)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -460,7 +483,7 @@
collectLastValue(underTest.subscriptions)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -477,7 +500,7 @@
collectLastValue(underTest.subscriptions)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -489,7 +512,7 @@
// WHEN the wifi network updates to be not carrier merged
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+ setWifiState(isCarrierMerged = false)
runCurrent()
// THEN the repos update
@@ -505,7 +528,7 @@
collectLastValue(underTest.subscriptions)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+ setWifiState(isCarrierMerged = false)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -518,7 +541,7 @@
// WHEN the wifi network updates to be carrier merged
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
runCurrent()
// THEN the repos update
@@ -529,6 +552,7 @@
}
@Test
+ @Ignore("b/333912012")
fun testConnectionCache_clearsInvalidSubscriptions() =
testScope.runTest {
collectLastValue(underTest.subscriptions)
@@ -553,12 +577,13 @@
}
@Test
+ @Ignore("b/333912012")
fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() =
testScope.runTest {
collectLastValue(underTest.subscriptions)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -581,6 +606,7 @@
/** Regression test for b/261706421 */
@Test
+ @Ignore("b/333912012")
fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() =
testScope.runTest {
collectLastValue(underTest.subscriptions)
@@ -604,6 +630,54 @@
}
@Test
+ fun testConnectionsCache_keepsReposCached() =
+ testScope.runTest {
+ // Collect subscriptions to start the job
+ collectLastValue(underTest.subscriptions)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1_1 = underTest.getRepoForSubId(SUB_1_ID)
+
+ // All subscriptions disappear
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // Sub1 comes back
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1_2 = underTest.getRepoForSubId(SUB_1_ID)
+
+ assertThat(repo1_1).isSameInstanceAs(repo1_2)
+ }
+
+ @Test
+ fun testConnectionsCache_doesNotDropReferencesThatHaveBeenRealized() =
+ testScope.runTest {
+ // Collect subscriptions to start the job
+ collectLastValue(underTest.subscriptions)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // Client grabs a reference to a repository, but doesn't keep it around
+ underTest.getRepoForSubId(SUB_1_ID)
+
+ // All subscriptions disappear
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+
+ assertThat(repo1).isNotNull()
+ }
+
+ @Test
fun testConnectionRepository_invalidSubId_doesNotThrow() =
testScope.runTest {
underTest.getRepoForSubId(SUB_1_ID)
@@ -792,7 +866,7 @@
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
assertThat(latest).isTrue()
}
@@ -814,7 +888,7 @@
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
assertThat(latest).isTrue()
}
@@ -837,7 +911,7 @@
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
assertThat(latest).isTrue()
}
@@ -859,7 +933,7 @@
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
assertThat(latest).isTrue()
}
@@ -893,7 +967,7 @@
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ setWifiState(isCarrierMerged = true)
// THEN there's a carrier merged connection
assertThat(latest).isTrue()
@@ -929,7 +1003,7 @@
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ setWifiState(isCarrierMerged = true)
// THEN there's a carrier merged connection
assertThat(latest).isTrue()
@@ -952,7 +1026,7 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
// BUT the wifi repo has gotten updates that it *is* carrier merged
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
// THEN hasCarrierMergedConnection is true
assertThat(latest).isTrue()
@@ -973,7 +1047,7 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
// BUT the wifi repo has gotten updates that it *is* carrier merged
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
// THEN hasCarrierMergedConnection is **false** (The default network being CELLULAR
// takes precedence over the wifi network being carrier merged.)
@@ -995,7 +1069,7 @@
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
// BUT the wifi repo has gotten updates that it *is* carrier merged
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
// AND we're in airplane mode
airplaneModeRepository.setIsAirplaneMode(true)
@@ -1063,7 +1137,8 @@
airplaneModeRepository,
wifiRepository,
fullConnectionFactory,
- updateMonitor
+ updateMonitor,
+ mock(),
)
val latest by collectLastValue(underTest.defaultDataSubRatConfig)
@@ -1223,12 +1298,26 @@
return callbackCaptor.value!!
}
- // Note: This is used to update the [WifiRepository].
- private fun TestScope.getNormalNetworkCallback(): ConnectivityManager.NetworkCallback {
- runCurrent()
- val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
- verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
+ private fun setWifiState(isCarrierMerged: Boolean) {
+ if (isCarrierMerged) {
+ val mergedEntry =
+ mock<MergedCarrierEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.isDefaultNetwork).thenReturn(true)
+ whenever(this.subscriptionId).thenReturn(SUB_CM_ID)
+ }
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+ } else {
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.isDefaultNetwork).thenReturn(true)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(null)
+ }
+ wifiPickerTrackerCallback.value.onWifiEntriesChanged()
}
private fun TestScope.getSubscriptionCallback():
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index f9ab25e..dfe8023 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -43,15 +43,12 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlin.time.Duration.Companion.seconds
-import kotlin.time.DurationUnit
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -694,32 +691,6 @@
assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
}
- @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
- @Test
- fun satBasedIcon_hasHysteresisWhenDisabled() =
- testScope.runTest {
- val latest by collectLastValue(underTest.signalLevelIcon)
-
- val hysteresisDuration = 5.seconds
- connectionRepository.satelliteConnectionHysteresisSeconds.value =
- hysteresisDuration.toInt(DurationUnit.SECONDS)
-
- connectionRepository.isNonTerrestrial.value = true
-
- assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
-
- // Disable satellite
- connectionRepository.isNonTerrestrial.value = false
-
- // Satellite icon should still be visible
- assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
-
- // Wait for the icon to change
- advanceTimeBy(hysteresisDuration)
-
- assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java)
- }
-
private fun createInteractor(
overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
index 42bbe3e..c4ab943 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
@@ -26,6 +26,10 @@
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+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.WifiInteractorImpl
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.util.mockito.mock
@@ -53,6 +57,10 @@
private val deviceProvisionedRepository = FakeDeviceProvisioningRepository()
private val deviceProvisioningInteractor =
DeviceProvisioningInteractor(deviceProvisionedRepository)
+ private val connectivityRepository = FakeConnectivityRepository()
+ private val wifiRepository = FakeWifiRepository()
+ private val wifiInteractor =
+ WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
@Before
fun setUp() {
@@ -61,6 +69,7 @@
repo,
iconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
}
@@ -103,6 +112,7 @@
repo,
iconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
@@ -150,6 +160,7 @@
repo,
iconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
@@ -205,6 +216,7 @@
repo,
iconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
@@ -337,6 +349,7 @@
repo,
iconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
@@ -353,4 +366,28 @@
// THEN the value is still false, because the flag is off
assertThat(latest).isFalse()
}
+
+ @Test
+ fun isWifiActive_falseWhenWifiNotActive() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isWifiActive)
+
+ // WHEN wifi is not active
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Invalid("test"))
+
+ // THEN the interactor returns false due to the wifi network not being active
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isWifiActive_trueWhenWifiIsActive() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isWifiActive)
+
+ // WHEN wifi is active
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 0, level = 1))
+
+ // THEN the interactor returns true due to the wifi network being active
+ assertThat(latest).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index 1d6cd37..64f19b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -26,6 +26,10 @@
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
+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.WifiInteractorImpl
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.util.mockito.mock
@@ -44,14 +48,18 @@
private lateinit var underTest: DeviceBasedSatelliteViewModel
private lateinit var interactor: DeviceBasedSatelliteInteractor
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
-
private val repo = FakeDeviceBasedSatelliteRepository()
+ private val testScope = TestScope()
+
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+
private val deviceProvisionedRepository = FakeDeviceProvisioningRepository()
private val deviceProvisioningInteractor =
DeviceProvisioningInteractor(deviceProvisionedRepository)
-
- private val testScope = TestScope()
+ private val connectivityRepository = FakeConnectivityRepository()
+ private val wifiRepository = FakeWifiRepository()
+ private val wifiInteractor =
+ WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
@Before
fun setUp() {
@@ -63,6 +71,7 @@
repo,
mobileIconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
@@ -253,4 +262,40 @@
// THEN icon is null because the device is not provisioned
assertThat(latest).isInstanceOf(Icon::class.java)
}
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun icon_wifiIsActive() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // GIVEN device is provisioned
+ deviceProvisionedRepository.setDeviceProvisioned(true)
+
+ // GIVEN wifi network is active
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 0, level = 1))
+
+ // THEN icon is null because the device is connected to wifi
+ assertThat(latest).isNull()
+
+ // GIVEN device loses wifi connection
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Invalid("test"))
+
+ // Wait for delay to be completed
+ advanceTimeBy(10.seconds)
+
+ // THEN icon is set because the device lost wifi connection
+ assertThat(latest).isInstanceOf(Icon::class.java)
+ }
}
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/WifiRepositoryImplTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 3126362..f8d50f5 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/WifiRepositoryImplTest.kt
@@ -68,14 +68,14 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {
+class WifiRepositoryImplTest : SysuiTestCase() {
// Using lazy means that the class will only be constructed once it's fetched. Because the
// repository internally sets some values on construction, we need to set up some test
// parameters (like feature flags) *before* construction. Using lazy allows us to do that setup
// inside each test case without needing to manually recreate the repository.
- private val underTest: WifiRepositoryViaTrackerLib by lazy {
- WifiRepositoryViaTrackerLib(
+ private val underTest: WifiRepositoryImpl by lazy {
+ WifiRepositoryImpl(
featureFlags,
testScope.backgroundScope,
executor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java
new file mode 100644
index 0000000..f1dbee2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.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.systemui.statusbar.policy;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.settingslib.fuelgauge.BatterySaverUtils;
+import com.android.systemui.Flags;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.power.EnhancedEstimates;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
[email protected]
+public class BatteryControllerStartableTest extends SysuiTestCase {
+
+ private BatteryController mBatteryController;
+ private BatteryControllerStartable mBatteryControllerStartable;
+ private MockitoSession mMockitoSession;
+ private FakeExecutor mExecutor;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+
+ @Before
+ public void setUp() throws IllegalStateException {
+ MockitoAnnotations.initMocks(this);
+ mMockitoSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(BatterySaverUtils.class)
+ .startMocking();
+
+ mExecutor = new FakeExecutor(new FakeSystemClock());
+
+ mBatteryController = new BatteryControllerImpl(getContext(),
+ mock(EnhancedEstimates.class),
+ mock(PowerManager.class),
+ mock(BroadcastDispatcher.class),
+ mock(DemoModeController.class),
+ mock(DumpManager.class),
+ mock(BatteryControllerLogger.class),
+ new Handler(Looper.getMainLooper()),
+ new Handler(Looper.getMainLooper()));
+ mBatteryController.init();
+
+ mBatteryControllerStartable = new BatteryControllerStartable(mBatteryController,
+ mBroadcastDispatcher, mExecutor);
+ }
+
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE)
+ public void start_flagEnabled_registersListeners() {
+ mBatteryControllerStartable.start();
+ mExecutor.runAllReady();
+
+ verify(mBroadcastDispatcher).registerReceiver(any(), any());
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE)
+ public void start_flagDisabled_doesNotRegistersListeners() {
+ mBatteryControllerStartable.start();
+ mExecutor.runAllReady();
+
+ verifyZeroInteractions(mBroadcastDispatcher);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index a5c766d..9d4f1fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -36,11 +36,12 @@
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerSaveState;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.dx.mockito.inline.extended.StaticInOrder;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
index 777fa28..1c54263 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
@@ -20,7 +20,7 @@
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.hardware.camera2.impl.CameraMetadataNative
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastSender
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index cb6ce68..9bb7607 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -44,8 +44,8 @@
import android.os.Handler;
import android.os.UserManager;
import android.security.IKeyChainService;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
index dc0d07c..b9557d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
@@ -23,14 +23,15 @@
import android.app.RemoteInput;
import android.os.Handler;
import android.provider.DeviceConfig;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableResources;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index 69536c5..bdd3d18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -30,7 +30,6 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository
@@ -60,7 +59,6 @@
keyguardRepository,
mock<CommandQueue>(),
PowerInteractorFactory.create().powerInteractor,
- kosmos.sceneContainerFlags,
FakeKeyguardBouncerRepository(),
ConfigurationInteractor(FakeConfigurationRepository()),
FakeShadeRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectTest.kt
new file mode 100644
index 0000000..b1df159
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.surfaceeffects.revealeffect
+
+import android.graphics.RenderEffect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.model.SysUiStateTest
+import com.android.systemui.surfaceeffects.RenderEffectDrawCallback
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
[email protected](setAsMainLooper = true)
+class RippleRevealEffectTest : SysUiStateTest() {
+
+ @get:Rule val animatorTestRule = AnimatorTestRule(this)
+
+ @Test
+ fun play_triggersDrawCallback() {
+ var effectFromCallback: RenderEffect? = null
+ val revealEffectConfig = RippleRevealEffectConfig(duration = 1000f)
+ val drawCallback =
+ object : RenderEffectDrawCallback {
+ override fun onDraw(renderEffect: RenderEffect) {
+ effectFromCallback = renderEffect
+ }
+ }
+ val revealEffect = RippleRevealEffect(revealEffectConfig, drawCallback)
+ assertThat(effectFromCallback).isNull()
+
+ revealEffect.play()
+
+ animatorTestRule.advanceTimeBy(500L)
+
+ assertThat(effectFromCallback).isNotNull()
+ }
+
+ @Test
+ fun play_triggersStateChangedCallback() {
+ val revealEffectConfig = RippleRevealEffectConfig(duration = 1000f)
+ val drawCallback =
+ object : RenderEffectDrawCallback {
+ override fun onDraw(renderEffect: RenderEffect) {}
+ }
+ var animationStartedCalled = false
+ var animationEndedCalled = false
+ val stateChangedCallback =
+ object : RippleRevealEffect.AnimationStateChangedCallback {
+ override fun onAnimationStart() {
+ animationStartedCalled = true
+ }
+
+ override fun onAnimationEnd() {
+ animationEndedCalled = true
+ }
+ }
+ val revealEffect =
+ RippleRevealEffect(revealEffectConfig, drawCallback, stateChangedCallback)
+
+ assertThat(animationStartedCalled).isFalse()
+ assertThat(animationEndedCalled).isFalse()
+
+ revealEffect.play()
+
+ assertThat(animationStartedCalled).isTrue()
+
+ animatorTestRule.advanceTimeBy(revealEffectConfig.duration.toLong())
+
+ assertThat(animationEndedCalled).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
index 28adbce..2cdc8d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
@@ -22,6 +22,8 @@
import androidx.test.filters.SmallTest
import com.android.internal.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.display.data.repository.DeviceStateRepository
import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -31,11 +33,12 @@
import com.android.systemui.power.shared.model.WakefulnessModel
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.FOLDABLE_DEVICE_STATE_CLOSED
import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.FOLDABLE_DEVICE_STATE_HALF_OPEN
import com.android.systemui.unfold.DisplaySwitchLatencyTracker.DisplaySwitchLatencyEvent
import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
-import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractorImpl
+import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
@@ -86,11 +89,20 @@
private val areAnimationEnabled = MutableStateFlow(true)
private val lastWakefulnessEvent = MutableStateFlow(WakefulnessModel())
private val systemClock = FakeSystemClock()
- private val unfoldTransitionProgressProvider = TestUnfoldTransitionProvider()
+ private val configurationController = FakeConfigurationController()
+ private val configurationRepository =
+ ConfigurationRepositoryImpl(
+ configurationController,
+ context,
+ testScope.backgroundScope,
+ mock()
+ )
+ private val configurationInteractor = ConfigurationInteractor(configurationRepository)
+ private val unfoldTransitionProgressProvider = FakeUnfoldTransitionProvider()
private val unfoldTransitionRepository =
UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider))
private val unfoldTransitionInteractor =
- UnfoldTransitionInteractorImpl(unfoldTransitionRepository)
+ UnfoldTransitionInteractor(unfoldTransitionRepository, configurationInteractor)
@Before
fun setup() {
@@ -155,7 +167,10 @@
fun unfold_progressUnavailable_logsLatencyTillScreenTurnedOn() {
testScope.runTest {
val unfoldTransitionInteractorWithEmptyProgressProvider =
- UnfoldTransitionInteractorImpl(UnfoldTransitionRepositoryImpl(Optional.empty()))
+ UnfoldTransitionInteractor(
+ UnfoldTransitionRepositoryImpl(Optional.empty()),
+ configurationInteractor,
+ )
displaySwitchLatencyTracker =
DisplaySwitchLatencyTracker(
mockContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
index b9c7e61..fd513c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
@@ -35,7 +35,7 @@
@SmallTest
class UnfoldHapticsPlayerTest : SysuiTestCase() {
- private val progressProvider = TestUnfoldTransitionProvider()
+ private val progressProvider = FakeUnfoldTransitionProvider()
private val vibrator: Vibrator = mock()
private val testFoldProvider = TestFoldProvider()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
index ba72716..2955384 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.unfold.util.FoldableDeviceStates
import com.android.systemui.unfold.util.FoldableTestUtils
import com.android.systemui.util.mockito.any
+import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,45 +38,41 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
-import java.util.Optional
@RunWith(AndroidTestingRunner::class)
@SmallTest
class UnfoldLatencyTrackerTest : SysuiTestCase() {
- @Mock
- lateinit var latencyTracker: LatencyTracker
+ @Mock lateinit var latencyTracker: LatencyTracker
- @Mock
- lateinit var deviceStateManager: DeviceStateManager
+ @Mock lateinit var deviceStateManager: DeviceStateManager
- @Mock
- lateinit var screenLifecycle: ScreenLifecycle
+ @Mock lateinit var screenLifecycle: ScreenLifecycle
- @Captor
- private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
+ @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
- @Captor
- private lateinit var screenLifecycleCaptor: ArgumentCaptor<ScreenLifecycle.Observer>
+ @Captor private lateinit var screenLifecycleCaptor: ArgumentCaptor<ScreenLifecycle.Observer>
private lateinit var deviceStates: FoldableDeviceStates
private lateinit var unfoldLatencyTracker: UnfoldLatencyTracker
- private val transitionProgressProvider = TestUnfoldTransitionProvider()
+ private val transitionProgressProvider = FakeUnfoldTransitionProvider()
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- unfoldLatencyTracker = UnfoldLatencyTracker(
- latencyTracker,
- deviceStateManager,
- Optional.of(transitionProgressProvider),
- context.mainExecutor,
- context,
- context.contentResolver,
- screenLifecycle
- ).apply { init() }
+ unfoldLatencyTracker =
+ UnfoldLatencyTracker(
+ latencyTracker,
+ deviceStateManager,
+ Optional.of(transitionProgressProvider),
+ context.mainExecutor,
+ context,
+ context.contentResolver,
+ screenLifecycle
+ )
+ .apply { init() }
deviceStates = FoldableTestUtils.findDeviceStates(context)
verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
@@ -107,7 +104,7 @@
}
@Test
- fun unfold_firstFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventNotPropagated() {
+ fun firstFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventNotPropagated() {
setAnimationsEnabled(true)
sendFoldEvent(folded = false)
@@ -118,7 +115,7 @@
}
@Test
- fun unfold_secondFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
+ fun secondFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
setAnimationsEnabled(true)
sendFoldEvent(folded = true)
sendFoldEvent(folded = false)
@@ -131,7 +128,7 @@
}
@Test
- fun unfold_unfoldFoldUnfoldAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
+ fun unfoldFoldUnfoldAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
setAnimationsEnabled(true)
sendFoldEvent(folded = false)
sendFoldEvent(folded = true)
@@ -196,4 +193,4 @@
durationScale.toString()
)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
index 6ec0251..0c452eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
@@ -17,21 +17,18 @@
@SmallTest
class UnfoldTransitionWallpaperControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var wallpaperController: WallpaperController
+ @Mock private lateinit var wallpaperController: WallpaperController
- private val progressProvider = TestUnfoldTransitionProvider()
+ private val progressProvider = FakeUnfoldTransitionProvider()
- @JvmField
- @Rule
- val mockitoRule = MockitoJUnit.rule()
+ @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
private lateinit var unfoldWallpaperController: UnfoldTransitionWallpaperController
@Before
fun setup() {
- unfoldWallpaperController = UnfoldTransitionWallpaperController(progressProvider,
- wallpaperController)
+ unfoldWallpaperController =
+ UnfoldTransitionWallpaperController(progressProvider, wallpaperController)
unfoldWallpaperController.init()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt
deleted file mode 100644
index 6a801e0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt
+++ /dev/null
@@ -1,91 +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.unfold.domain.interactor
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
-import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
-import com.google.common.truth.Truth.assertThat
-import java.util.Optional
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.async
-import kotlinx.coroutines.test.TestScope
-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.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-open class UnfoldTransitionInteractorTest : SysuiTestCase() {
-
- private val testScope = TestScope()
-
- private val unfoldTransitionProgressProvider = TestUnfoldTransitionProvider()
- private val unfoldTransitionRepository =
- UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider))
-
- private lateinit var underTest: UnfoldTransitionInteractor
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- underTest = UnfoldTransitionInteractorImpl(unfoldTransitionRepository)
- }
-
- @Test
- fun waitForTransitionFinish_noEvents_doesNotComplete() =
- testScope.runTest {
- val deferred = async { underTest.waitForTransitionFinish() }
-
- runCurrent()
-
- assertThat(deferred.isCompleted).isFalse()
- deferred.cancel()
- }
-
- @Test
- fun waitForTransitionFinish_finishEvent_completes() =
- testScope.runTest {
- val deferred = async { underTest.waitForTransitionFinish() }
-
- runCurrent()
- unfoldTransitionProgressProvider.onTransitionFinished()
- runCurrent()
-
- assertThat(deferred.isCompleted).isTrue()
- deferred.cancel()
- }
-
- @Test
- fun waitForTransitionFinish_otherEvent_doesNotComplete() =
- testScope.runTest {
- val deferred = async { underTest.waitForTransitionFinish() }
-
- runCurrent()
- unfoldTransitionProgressProvider.onTransitionStarted()
- runCurrent()
-
- assertThat(deferred.isCompleted).isFalse()
- deferred.cancel()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
index 2bc05fc..e5f619b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
@@ -23,7 +23,7 @@
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
import com.android.systemui.utils.os.FakeHandler
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
@@ -34,7 +34,7 @@
@RunWithLooper(setAsMainLooper = true)
class MainThreadUnfoldTransitionProgressProviderTest : SysuiTestCase() {
- private val wrappedProgressProvider = TestUnfoldTransitionProvider()
+ private val wrappedProgressProvider = FakeUnfoldTransitionProvider()
private val fakeHandler = FakeHandler(Looper.getMainLooper())
private val listener = TestUnfoldProgressListener()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
index d864d53..70ec050 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
@@ -20,7 +20,7 @@
import android.view.Surface
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener
@@ -43,14 +43,14 @@
@Mock lateinit var rotationChangeProvider: RotationChangeProvider
- private val sourceProvider = TestUnfoldTransitionProvider()
+ private val sourceProvider = FakeUnfoldTransitionProvider()
@Mock lateinit var transitionListener: TransitionProgressListener
@Captor private lateinit var rotationListenerCaptor: ArgumentCaptor<RotationListener>
lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
- private lateinit var testableLooper : TestableLooper
+ private lateinit var testableLooper: TestableLooper
@Before
fun setUp() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
index 2f29b3b..451bd24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
@@ -22,7 +22,7 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.util.mockito.any
import org.junit.Before
@@ -42,7 +42,7 @@
@Mock lateinit var sinkProvider: TransitionProgressListener
- private val sourceProvider = TestUnfoldTransitionProvider()
+ private val sourceProvider = FakeUnfoldTransitionProvider()
private lateinit var contentResolver: ContentResolver
private lateinit var progressProvider: ScaleAwareTransitionProgressProvider
@@ -132,6 +132,6 @@
durationScale.toString()
)
- animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false)
+ animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */ false)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
index 95c934e..4486402 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
@@ -23,7 +23,7 @@
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
import com.android.systemui.unfold.progress.TestUnfoldProgressListener
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
@@ -43,7 +43,7 @@
@RunWithLooper
class ScopedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
- private val rootProvider = TestUnfoldTransitionProvider()
+ private val rootProvider = FakeUnfoldTransitionProvider()
private val listener = TestUnfoldProgressListener()
private val testScope = TestScope(UnconfinedTestDispatcher())
private val bgThread =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
index f484ea0..cd4d7b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
@@ -19,7 +19,7 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
import com.android.systemui.unfold.progress.TestUnfoldProgressListener
import com.google.common.util.concurrent.MoreExecutors
import org.junit.Before
@@ -32,7 +32,7 @@
class UnfoldOnlyProgressProviderTest : SysuiTestCase() {
private val listener = TestUnfoldProgressListener()
- private val sourceProvider = TestUnfoldTransitionProvider()
+ private val sourceProvider = FakeUnfoldTransitionProvider()
private val foldProvider = TestFoldProvider()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
index a85ae7df..35f9c41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
@@ -3,7 +3,7 @@
import android.content.pm.UserInfo
import android.graphics.Bitmap
import android.os.UserManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java
index 900d792..2436725 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java
@@ -22,9 +22,9 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
-import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArraySet;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
index b10f16c..ab52c34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
@@ -25,9 +25,10 @@
import static org.mockito.Mockito.when;
import android.hardware.Sensor;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.concurrency.FakeExecution;
import com.android.systemui.util.concurrency.FakeExecutor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
index b0bd83e..741b2e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
@@ -27,10 +27,11 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.media.AudioManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.concurrency.FakeExecutor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java
index fb82b8f..483dc0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java
@@ -15,14 +15,14 @@
*/
package com.android.systemui.volume;
+import static com.google.common.truth.Truth.assertThat;
+
import android.media.MediaMetadata;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import junit.framework.Assert;
-
import org.junit.Test;
@SmallTest
@@ -30,11 +30,59 @@
@Test
public void testMediaMetadataToString_null() {
- Assert.assertEquals(null, Util.mediaMetadataToString(null));
+ assertThat(Util.mediaMetadataToString(null)).isNull();
}
@Test
public void testMediaMetadataToString_notNull() {
- Assert.assertNotNull(Util.mediaMetadataToString(new MediaMetadata.Builder().build()));
+ assertThat(Util.mediaMetadataToString(new MediaMetadata.Builder().build())).isNotNull();
+ }
+
+ @Test
+ public void translateToRange_translatesStartToStart() {
+ assertThat(
+ (int) Util.translateToRange(
+ /* value= */ 0,
+ /* valueRangeStart= */ 0,
+ /* valueRangeEnd= */ 7,
+ /* targetRangeStart= */ 0,
+ /* targetRangeEnd= */700)
+ ).isEqualTo(0);
+ }
+
+ @Test
+ public void translateToRange_translatesValueToValue() {
+ assertThat(
+ (int) Util.translateToRange(
+ /* value= */ 4,
+ /* valueRangeStart= */ 0,
+ /* valueRangeEnd= */ 7,
+ /* targetRangeStart= */ 0,
+ /* targetRangeEnd= */700)
+ ).isEqualTo(400);
+ }
+
+ @Test
+ public void translateToRange_translatesEndToEnd() {
+ assertThat(
+ (int) Util.translateToRange(
+ /* value= */ 7,
+ /* valueRangeStart= */ 0,
+ /* valueRangeEnd= */ 7,
+ /* targetRangeStart= */ 0,
+ /* targetRangeEnd= */700)
+ ).isEqualTo(700);
+ }
+
+ @Test
+ public void translateToRange_returnsStartForEmptyRange() {
+ assertThat(
+ (int) Util.translateToRange(
+ /* value= */ 7,
+ /* valueRangeStart= */ 7,
+ /* valueRangeEnd= */ 7,
+ /* targetRangeStart= */ 700,
+ /* targetRangeEnd= */700)
+ ).isEqualTo(700);
}
}
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 ed2fb2c..3b468aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -248,6 +248,8 @@
VolumeDialogController.StreamState ss = new VolumeDialogController.StreamState();
ss.name = STREAMS.get(i);
ss.level = 1;
+ ss.levelMin = 0;
+ ss.levelMax = 25;
state.states.append(i, ss);
}
return state;
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 c24c86c..d9a0c4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -121,8 +121,6 @@
import com.android.systemui.scene.FakeWindowRootViewComponent;
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scene.shared.logger.SceneLogger;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.settings.UserTracker;
@@ -360,8 +358,6 @@
@Mock
private Display mDefaultDisplay;
@Mock
- private SceneContainerFlags mSceneContainerFlags;
- @Mock
private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
@@ -430,14 +426,12 @@
mock(SceneLogger.class),
mKosmos.getDeviceUnlockedInteractor());
- FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
KeyguardTransitionInteractor keyguardTransitionInteractor =
mKosmos.getKeyguardTransitionInteractor();
KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
keyguardRepository,
new FakeCommandQueue(),
powerInteractor,
- sceneContainerFlags,
new FakeKeyguardBouncerRepository(),
new ConfigurationInteractor(configurationRepository),
shadeRepository,
@@ -503,7 +497,6 @@
mShadeWindowLogger,
() -> mSelectedUserInteractor,
mUserTracker,
- mSceneContainerFlags,
mKosmos::getCommunalInteractor
);
mNotificationShadeWindowController.fetchWindowRootView();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
deleted file mode 100644
index d2c8aea..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ /dev/null
@@ -1,141 +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.systemui.wmshell;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.verify;
-
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.notetask.NoteTaskInitializer;
-import com.android.systemui.settings.FakeDisplayTracker;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.onehanded.OneHandedEventCallback;
-import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.recents.RecentTasks;
-import com.android.wm.shell.splitscreen.SplitScreen;
-import com.android.wm.shell.sysui.ShellInterface;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
-/**
- * Tests for {@link WMShell}.
- *
- * Build/Install/Run:
- * atest SystemUITests:WMShellTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class WMShellTest extends SysuiTestCase {
- WMShell mWMShell;
-
- @Mock ShellInterface mShellInterface;
- @Mock CommandQueue mCommandQueue;
- @Mock ConfigurationController mConfigurationController;
- @Mock KeyguardStateController mKeyguardStateController;
- @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock ScreenLifecycle mScreenLifecycle;
- @Mock SysUiState mSysUiState;
- @Mock Pip mPip;
- @Mock SplitScreen mSplitScreen;
- @Mock OneHanded mOneHanded;
- @Mock WakefulnessLifecycle mWakefulnessLifecycle;
- @Mock UserTracker mUserTracker;
- @Mock ShellExecutor mSysUiMainExecutor;
- @Mock NoteTaskInitializer mNoteTaskInitializer;
- @Mock DesktopMode mDesktopMode;
- @Mock RecentTasks mRecentTasks;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
- mWMShell = new WMShell(
- mContext,
- mShellInterface,
- Optional.of(mPip),
- Optional.of(mSplitScreen),
- Optional.of(mOneHanded),
- Optional.of(mDesktopMode),
- Optional.of(mRecentTasks),
- mCommandQueue,
- mConfigurationController,
- mKeyguardStateController,
- mKeyguardUpdateMonitor,
- mScreenLifecycle,
- mSysUiState,
- mWakefulnessLifecycle,
- mUserTracker,
- displayTracker,
- mNoteTaskInitializer,
- mSysUiMainExecutor
- );
- }
-
- @Test
- public void initPip_registersCommandQueueCallback() {
- mWMShell.initPip(mPip);
-
- verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class));
- }
-
- @Test
- public void initOneHanded_registersCallbacks() {
- mWMShell.initOneHanded(mOneHanded);
-
- verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class));
- verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class));
- verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class));
- verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback.class));
- }
-
- @Test
- public void initDesktopMode_registersListener() {
- mWMShell.initDesktopMode(mDesktopMode);
- verify(mDesktopMode).addVisibleTasksListener(
- any(DesktopModeTaskRepository.VisibleTasksListener.class),
- any(Executor.class));
- }
-
- @Test
- public void initRecentTasks_registersListener() {
- mWMShell.initRecentTasks(mRecentTasks);
- verify(mRecentTasks).addAnimationStateListener(any(Executor.class), any());
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
index de7b14d..42b6e18 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
@@ -30,8 +30,9 @@
import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor
+import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule
import com.android.systemui.scene.SceneContainerFrameworkModule
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSource
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
@@ -60,6 +61,7 @@
TestMocksModule::class,
CoroutineTestScopeModule::class,
FakeSystemUiModule::class,
+ DefaultBlueprintModule::class,
SceneContainerFrameworkModule::class,
FaceWakeUpTriggersConfigModule::class,
]
@@ -104,11 +106,10 @@
@Provides
fun provideBaseShadeInteractor(
- sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>,
sceneContainerOff: Provider<ShadeInteractorLegacyImpl>
): BaseShadeInteractor {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
} else {
sceneContainerOff.get()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt
new file mode 100644
index 0000000..8b0affe2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility
+
+import com.android.systemui.qs.ReduceBrightColorsController
+
+class FakeReduceBrightColorsController : ReduceBrightColorsController {
+
+ private var isEnabled = false
+
+ private val callbacks = LinkedHashSet<ReduceBrightColorsController.Listener>()
+
+ override fun addCallback(listener: ReduceBrightColorsController.Listener) {
+ callbacks.add(listener)
+ }
+
+ override fun removeCallback(listener: ReduceBrightColorsController.Listener) {
+ callbacks.remove(listener)
+ }
+
+ override fun isReduceBrightColorsActivated(): Boolean {
+ return isEnabled
+ }
+
+ override fun setReduceBrightColorsActivated(activated: Boolean) {
+ if (activated != isEnabled) {
+ isEnabled = activated
+ for (callback in callbacks) {
+ callback.onActivated(activated)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/ReduceBrightColorsControllerKosmos.kt
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/ReduceBrightColorsControllerKosmos.kt
index 979d8e7..5bbe3bf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/ReduceBrightColorsControllerKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.scene.shared.flag
+package com.android.systemui.accessibility
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.ReduceBrightColorsController
-var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
-val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags }
+var Kosmos.reduceBrightColorsController: ReduceBrightColorsController by
+ Kosmos.Fixture { fakeReduceBrightColorsController }
+val Kosmos.fakeReduceBrightColorsController by Kosmos.Fixture { FakeReduceBrightColorsController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index a6dd3cd..219794f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -32,6 +32,8 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.currentTime
@@ -68,6 +70,8 @@
var lockoutStartedReportCount = 0
+ private val credentialCheckingMutex = Mutex(locked = false)
+
override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
return authenticationMethod.value
}
@@ -124,30 +128,32 @@
override suspend fun checkCredential(
credential: LockscreenCredential
): AuthenticationResultModel {
- val expectedCredential = credentialOverride ?: getExpectedCredential(securityMode)
- val isSuccessful =
- when {
- credential.type != getCurrentCredentialType(securityMode) -> false
- credential.type == LockPatternUtils.CREDENTIAL_TYPE_PIN ->
- credential.isPin && credential.matches(expectedCredential)
- credential.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ->
- credential.isPassword && credential.matches(expectedCredential)
- credential.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN ->
- credential.isPattern && credential.matches(expectedCredential)
- else -> error("Unexpected credential type ${credential.type}!")
- }
+ return credentialCheckingMutex.withLock {
+ val expectedCredential = credentialOverride ?: getExpectedCredential(securityMode)
+ val isSuccessful =
+ when {
+ credential.type != getCurrentCredentialType(securityMode) -> false
+ credential.type == LockPatternUtils.CREDENTIAL_TYPE_PIN ->
+ credential.isPin && credential.matches(expectedCredential)
+ credential.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ->
+ credential.isPassword && credential.matches(expectedCredential)
+ credential.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN ->
+ credential.isPattern && credential.matches(expectedCredential)
+ else -> error("Unexpected credential type ${credential.type}!")
+ }
- val failedAttempts = _failedAuthenticationAttempts.value
- return if (isSuccessful || failedAttempts < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
- AuthenticationResultModel(
- isSuccessful = isSuccessful,
- lockoutDurationMs = 0,
- )
- } else {
- AuthenticationResultModel(
- isSuccessful = false,
- lockoutDurationMs = LOCKOUT_DURATION_MS,
- )
+ val failedAttempts = _failedAuthenticationAttempts.value
+ if (isSuccessful || failedAttempts < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
+ AuthenticationResultModel(
+ isSuccessful = isSuccessful,
+ lockoutDurationMs = 0,
+ )
+ } else {
+ AuthenticationResultModel(
+ isSuccessful = false,
+ lockoutDurationMs = LOCKOUT_DURATION_MS,
+ )
+ }
}
}
@@ -155,6 +161,23 @@
_isPinEnhancedPrivacyEnabled.value = isEnabled
}
+ /**
+ * Pauses any future credential checking. The test must call [unpauseCredentialChecking] to
+ * flush the accumulated credential checks.
+ */
+ suspend fun pauseCredentialChecking() {
+ credentialCheckingMutex.lock()
+ }
+
+ /**
+ * Unpauses future credential checking, if it was paused using [pauseCredentialChecking]. This
+ * doesn't flush any pending coroutine jobs; the test code may still choose to do that using
+ * `runCurrent`.
+ */
+ fun unpauseCredentialChecking() {
+ credentialCheckingMutex.unlock()
+ }
+
private fun getExpectedCredential(securityMode: SecurityMode): List<Any> {
return when (val credentialType = getCurrentCredentialType(securityMode)) {
LockPatternUtils.CREDENTIAL_TYPE_PIN -> credentialOverride ?: DEFAULT_PIN
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
index 8b1a1d9..e2386a6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
@@ -16,11 +16,13 @@
package com.android.systemui.biometrics.ui.viewmodel
+import com.android.keyguard.logging.DeviceEntryIconLogger
import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.statusbar.phone.systemUIDialogManager
+import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ExperimentalCoroutinesApi
@@ -29,5 +31,6 @@
deviceEntryIconViewModel = deviceEntryIconViewModel,
alternateBouncerInteractor = alternateBouncerInteractor,
systemUIDialogManager = systemUIDialogManager,
+ logger = mock<DeviceEntryIconLogger>(),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
index 9ce9ff2..4a02f6d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.domain.interactor
+import com.android.internal.logging.uiEventLogger
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.bouncer.data.repository.bouncerRepository
import com.android.systemui.classifier.domain.interactor.falsingInteractor
@@ -23,6 +24,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.sessionTracker
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -34,6 +36,8 @@
deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
falsingInteractor = falsingInteractor,
powerInteractor = powerInteractor,
+ uiEventLogger = uiEventLogger,
+ sessionTracker = sessionTracker,
sceneInteractor = sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
index 5c3e1f4..60d97d1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
@@ -17,8 +17,6 @@
package com.android.systemui.bouncer.shared.flag
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-var Kosmos.fakeComposeBouncerFlags by
- Kosmos.Fixture { FakeComposeBouncerFlags(fakeSceneContainerFlags) }
+var Kosmos.fakeComposeBouncerFlags by Kosmos.Fixture { FakeComposeBouncerFlags() }
val Kosmos.composeBouncerFlags by Kosmos.Fixture<ComposeBouncerFlags> { fakeComposeBouncerFlags }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt
index c116bbd..7482c0f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt
@@ -16,14 +16,11 @@
package com.android.systemui.bouncer.shared.flag
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
-class FakeComposeBouncerFlags(
- private val sceneContainerFlags: SceneContainerFlags,
- var composeBouncerEnabled: Boolean = false
-) : ComposeBouncerFlags {
+class FakeComposeBouncerFlags(var composeBouncerEnabled: Boolean = false) : ComposeBouncerFlags {
override fun isComposeBouncerOrSceneContainerEnabled(): Boolean {
- return sceneContainerFlags.isEnabled() || composeBouncerEnabled
+ return SceneContainerFlag.isEnabled || composeBouncerEnabled
}
@Deprecated(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 4b6ef37..3dd382f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -34,7 +34,6 @@
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.settings.userTracker
import com.android.systemui.smartspace.data.repository.smartspaceRepository
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -46,21 +45,20 @@
broadcastDispatcher = broadcastDispatcher,
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
- mediaRepository = communalMediaRepository,
communalPrefsRepository = communalPrefsRepository,
+ mediaRepository = communalMediaRepository,
smartspaceRepository = smartspaceRepository,
- appWidgetHost = mock(),
keyguardInteractor = keyguardInteractor,
+ communalSettingsInteractor = communalSettingsInteractor,
+ appWidgetHost = mock(),
editWidgetsActivityStarter = editWidgetsActivityStarter,
userTracker = userTracker,
activityStarter = activityStarter,
userManager = userManager,
dockManager = fakeDockManager,
+ sceneInteractor = sceneInteractor,
logBuffer = logcatLogBuffer("CommunalInteractor"),
tableLogBuffer = mock(),
- communalSettingsInteractor = communalSettingsInteractor,
- sceneInteractor = sceneInteractor,
- sceneContainerFlags = sceneContainerFlags,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
index e36ddc1..e3c218d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.ui.viewmodel
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.util.communalColors
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.dreamingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToDreamingTransitionViewModel
@@ -37,5 +38,6 @@
glanceableHubToDreamTransitionViewModel = glanceableHubToDreamingTransitionViewModel,
communalInteractor = communalInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
+ communalColors = communalColors,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt
similarity index 68%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt
index 979d8e7..e76cf68 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.scene.shared.flag
+package com.android.systemui.communal.util
import com.android.systemui.kosmos.Kosmos
-var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
-val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags }
+val Kosmos.communalColors: CommunalColors by Kosmos.Fixture { fakeCommunalColors }
+val Kosmos.fakeCommunalColors by Kosmos.Fixture { FakeCommunalColors() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt
new file mode 100644
index 0000000..7046658
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.util
+
+import android.graphics.Color
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeCommunalColors : CommunalColors {
+ private val _backgroundColor = MutableStateFlow(Color.valueOf(Color.BLACK))
+
+ override val backgroundColor: StateFlow<Color>
+ get() = _backgroundColor.asStateFlow()
+
+ fun setBackgroundColor(color: Color) {
+ _backgroundColor.value = color
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
index 636d509..24603ef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
@@ -19,7 +19,6 @@
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
val Kosmos.qsLongPressEffect by
- Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardInteractor, testScope) }
+ Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index e21c766..2e751cc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -24,12 +24,9 @@
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor.ConfigurationBasedDimensions
@@ -49,7 +46,6 @@
@JvmStatic
fun create(
featureFlags: FakeFeatureFlags = FakeFeatureFlags(),
- sceneContainerFlags: SceneContainerFlags = FakeSceneContainerFlags(),
repository: FakeKeyguardRepository = FakeKeyguardRepository(),
commandQueue: FakeCommandQueue = FakeCommandQueue(),
bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
@@ -88,7 +84,6 @@
repository = repository,
commandQueue = commandQueue,
featureFlags = featureFlags,
- sceneContainerFlags = sceneContainerFlags,
bouncerRepository = bouncerRepository,
configurationRepository = configurationRepository,
shadeRepository = shadeRepository,
@@ -96,13 +91,12 @@
KeyguardInteractor(
repository = repository,
commandQueue = commandQueue,
- sceneContainerFlags = sceneContainerFlags,
+ powerInteractor = powerInteractor,
bouncerRepository = bouncerRepository,
configurationInteractor = ConfigurationInteractor(configurationRepository),
shadeRepository = shadeRepository,
- sceneInteractorProvider = { sceneInteractor },
keyguardTransitionInteractor = keyguardTransitionInteractor,
- powerInteractor = powerInteractor,
+ sceneInteractorProvider = { sceneInteractor },
fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
sharedNotificationContainerInteractor = { sncInteractor },
applicationScope = testScope,
@@ -114,7 +108,6 @@
val repository: FakeKeyguardRepository,
val commandQueue: FakeCommandQueue,
val featureFlags: FakeFeatureFlags,
- val sceneContainerFlags: SceneContainerFlags,
val bouncerRepository: FakeKeyguardBouncerRepository,
val configurationRepository: FakeConfigurationRepository,
val shadeRepository: FakeShadeRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
index 2a0c01c..9426718 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -23,7 +23,6 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.statusbar.commandQueue
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -34,7 +33,6 @@
repository = keyguardRepository,
commandQueue = commandQueue,
powerInteractor = powerInteractor,
- sceneContainerFlags = sceneContainerFlags,
bouncerRepository = keyguardBouncerRepository,
configurationInteractor = configurationInteractor,
shadeRepository = shadeRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModelKosmos.kt
new file mode 100644
index 0000000..bc35dc8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModelKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.accessibilityActionsViewModelKosmos by Fixture {
+ AccessibilityActionsViewModel(
+ communalInteractor = communalInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
+ keyguardInteractor = keyguardInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
index b4f1218..bdd4afa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt
@@ -18,7 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
@@ -27,6 +27,6 @@
val Kosmos.alternateBouncerViewModel by Fixture {
AlternateBouncerViewModel(
statusBarKeyguardViewManager = statusBarKeyguardViewManager,
- animationFlow = keyguardTransitionAnimationFlow,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 709f864..58b0ff8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
@@ -26,7 +26,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -47,7 +46,6 @@
transitionInteractor = keyguardTransitionInteractor,
keyguardInteractor = keyguardInteractor,
viewModel = aodToLockscreenTransitionViewModel,
- sceneContainerFlags = sceneContainerFlags,
keyguardViewController = { statusBarKeyguardViewManager },
deviceEntryInteractor = deviceEntryInteractor,
deviceEntrySourceInteractor = deviceEntrySourceInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
index 1e25f7f..30a4f21 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -22,6 +22,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
val Kosmos.lockscreenContentViewModel by
Kosmos.Fixture {
@@ -32,5 +33,6 @@
longPress = keyguardLongPressViewModel,
shadeInteractor = shadeInteractor,
applicationScope = applicationCoroutineScope,
+ unfoldTransitionInteractor = unfoldTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index fdc3e0a..162f278 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -48,10 +48,9 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.sceneContainerConfig
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.scene.shared.model.sceneDataSource
import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
+import com.android.systemui.shade.shadeController
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.screenOffAnimationController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
@@ -71,8 +70,6 @@
val testDispatcher by lazy { kosmos.testDispatcher }
val testScope by lazy { kosmos.testScope }
val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic }
- val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
- val sceneContainerFlags by lazy { kosmos.sceneContainerFlags }
val fakeExecutor by lazy { kosmos.fakeExecutor }
val fakeExecutorHandler by lazy { kosmos.fakeExecutorHandler }
val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
@@ -112,6 +109,7 @@
}
val brightnessMirrorShowingInteractor by lazy { kosmos.brightnessMirrorShowingInteractor }
val qsLongPressEffect by lazy { kosmos.qsLongPressEffect }
+ val shadeController by lazy { kosmos.shadeController }
init {
kosmos.applicationContext = testCase.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/SessionTrackerKosmos.kt
similarity index 67%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/log/SessionTrackerKosmos.kt
index 979d8e7..eac9149 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/SessionTrackerKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.scene.shared.flag
+package com.android.systemui.log
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
-val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags }
+val Kosmos.sessionTracker: SessionTracker by Kosmos.Fixture { sessionTrackerMock }
+val Kosmos.sessionTrackerMock: SessionTracker by Kosmos.Fixture { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
index 7ce810e..7dab5c2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
@@ -17,5 +17,6 @@
package com.android.systemui.media.controls.data.repository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.time.systemClock
-val Kosmos.mediaFilterRepository by Kosmos.Fixture { MediaFilterRepository() }
+val Kosmos.mediaFilterRepository by Kosmos.Fixture { MediaFilterRepository(systemClock) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt
index da2170c..2f3d3c3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.ui.viewmodel
import android.content.applicationContext
+import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaControlInteractor
@@ -27,6 +28,7 @@
MediaControlViewModel(
applicationContext = applicationContext,
backgroundDispatcher = testDispatcher,
+ backgroundExecutor = fakeExecutor,
interactor = mediaControlInteractor,
logger = mediaUiEventLogger,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaFlagsKosmos.kt
index 6f652f2..e88d22a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaFlagsKosmos.kt
@@ -18,9 +18,5 @@
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
-val Kosmos.mediaFlags by
- Kosmos.Fixture {
- MediaFlags(featureFlags = featureFlagsClassic, sceneContainerFlags = sceneContainerFlags)
- }
+val Kosmos.mediaFlags by Kosmos.Fixture { MediaFlags(featureFlags = featureFlagsClassic) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/reducebrightness/ReduceBrightColorsTileKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/reducebrightness/ReduceBrightColorsTileKosmos.kt
index 979d8e7..339f3bb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/reducebrightness/ReduceBrightColorsTileKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.scene.shared.flag
+package com.android.systemui.qs.tiles.impl.reducebrightness
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
-var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
-val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags }
+val Kosmos.qsReduceBrightColorsTileConfig by
+ Kosmos.Fixture { QSAccessibilityModule.provideReduceBrightColorsTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/screenrecord/ScreenRecordTileKosmos.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/screenrecord/ScreenRecordTileKosmos.kt
index 979d8e7..ddcca94 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/screenrecord/ScreenRecordTileKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.scene.shared.flag
+package com.android.systemui.qs.tiles.impl.screenrecord
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.screenrecord.ScreenRecordModule
-var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
-val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags }
+val Kosmos.qsScreenRecordTileConfig by
+ Kosmos.Fixture { ScreenRecordModule.provideScreenRecordTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt
deleted file mode 100644
index ded7256..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.shared.flag
-
-import dagger.Binds
-import dagger.Module
-import dagger.Provides
-
-class FakeSceneContainerFlags(
- var enabled: Boolean = SceneContainerFlag.isEnabled,
-) : SceneContainerFlags {
-
- override fun isEnabled(): Boolean {
- return enabled
- }
-
- override fun requirementDescription(): String {
- return ""
- }
-}
-
-@Module(includes = [FakeSceneContainerFlagsModule.Bindings::class])
-class FakeSceneContainerFlagsModule(
- @get:Provides val sceneContainerFlags: FakeSceneContainerFlags = FakeSceneContainerFlags(),
-) {
- @Module
- interface Bindings {
- @Binds fun bindFake(fake: FakeSceneContainerFlags): SceneContainerFlags
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index 07e2d6b..543d5b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -23,7 +23,6 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.ShadeModule
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
@@ -36,7 +35,6 @@
var Kosmos.baseShadeInteractor: BaseShadeInteractor by
Kosmos.Fixture {
ShadeModule.provideBaseShadeInteractor(
- sceneContainerFlags = sceneContainerFlags,
sceneContainerOn = { shadeInteractorSceneContainerImpl },
sceneContainerOff = { shadeInteractorLegacyImpl },
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
new file mode 100644
index 0000000..8d653f7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.shade.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.shade.domain.interactor.privacyChipInteractor
+import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.mobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.mobileIconsViewModel
+
+val Kosmos.shadeHeaderViewModel: ShadeHeaderViewModel by
+ Kosmos.Fixture {
+ ShadeHeaderViewModel(
+ applicationScope = applicationCoroutineScope,
+ context = applicationContext,
+ activityStarter = activityStarter,
+ shadeInteractor = shadeInteractor,
+ mobileIconsInteractor = mobileIconsInteractor,
+ mobileIconsViewModel = mobileIconsViewModel,
+ privacyChipInteractor = privacyChipInteractor,
+ clockInteractor = shadeHeaderClockInteractor,
+ broadcastDispatcher = broadcastDispatcher,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
index dc1b9fe..7bf77e5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
@@ -30,4 +30,7 @@
override val topHeadsUpRow: Flow<HeadsUpRowRepository?> = MutableStateFlow(null)
override val activeHeadsUpRows: MutableStateFlow<Set<HeadsUpRowRepository>> =
MutableStateFlow(emptySet())
+ override fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
+ isHeadsUpAnimatingAway.value = animatingAway
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index c65d0a3..94f6ecd3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import com.android.systemui.dump.dumpManager
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -48,5 +49,6 @@
userSetupInteractor,
zenModeInteractor,
testDispatcher,
+ dumpManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index b249211..f0eea38 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -21,7 +21,6 @@
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
@@ -30,7 +29,6 @@
dumpManager = dumpManager,
interactor = notificationStackAppearanceInteractor,
shadeInteractor = shadeInteractor,
- flags = sceneContainerFlags,
featureFlags = featureFlagsClassic,
keyguardInteractor = keyguardInteractor,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 45b28b1..cbba80b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -46,6 +46,7 @@
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
+import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
import kotlinx.coroutines.ExperimentalCoroutinesApi
@OptIn(ExperimentalCoroutinesApi::class)
@@ -81,5 +82,6 @@
primaryBouncerToLockscreenTransitionViewModel =
primaryBouncerToLockscreenTransitionViewModel,
aodBurnInViewModel = aodBurnInViewModel,
+ unfoldTransitionInteractor = unfoldTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
index cf800d0..9c3f510 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
@@ -24,7 +24,6 @@
import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.policy.headsUpManager
@@ -36,7 +35,7 @@
headsUpManager = headsUpManager,
powerInteractor = powerInteractor,
activeNotificationsInteractor = activeNotificationsInteractor,
- sceneInteractorProvider = { sceneInteractor },
- sceneContainerFlags = sceneContainerFlags,
- )
+ ) {
+ sceneInteractor
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
similarity index 94%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
index 638925d..74b2da4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt
new file mode 100644
index 0000000..386c7c5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.airplane.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+
+val Kosmos.airplaneModeInteractor: AirplaneModeInteractor by
+ Kosmos.Fixture {
+ AirplaneModeInteractor(
+ FakeAirplaneModeRepository(),
+ FakeConnectivityRepository(),
+ FakeMobileConnectionsRepository(),
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 8109b60..eb2d6c0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -64,8 +64,6 @@
override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
- override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0)
-
private var isInEcmMode: Boolean = false
override suspend fun isInEcmMode(): Boolean = isInEcmMode
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKosmos.kt
new file mode 100644
index 0000000..eb6265a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.mobileIconsInteractor: MobileIconsInteractor by
+ Kosmos.Fixture { fakeMobileIconsInteractor }
+val Kosmos.fakeMobileIconsInteractor: FakeMobileIconsInteractor by
+ Kosmos.Fixture { FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKosmos.kt
new file mode 100644
index 0000000..c5f6557
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.airplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.mobileIconsInteractor
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.mobileIconsViewModel: MobileIconsViewModel by
+ Kosmos.Fixture {
+ MobileIconsViewModel(
+ logger = mock(),
+ verboseLogger = mock(),
+ interactor = mobileIconsInteractor,
+ airplaneModeInteractor = airplaneModeInteractor,
+ constants = mock(),
+ flags = featureFlagsClassic,
+ scope = applicationCoroutineScope,
+ )
+ }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
similarity index 97%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
index 28d632d..331e2fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FakeUnfoldTransitionProvider.kt
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FakeUnfoldTransitionProvider.kt
index 56c6245..94f0c44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FakeUnfoldTransitionProvider.kt
@@ -2,7 +2,7 @@
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
-class TestUnfoldTransitionProvider : UnfoldTransitionProgressProvider, TransitionProgressListener {
+class FakeUnfoldTransitionProvider : UnfoldTransitionProgressProvider, TransitionProgressListener {
private val listeners = mutableListOf<TransitionProgressListener>()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/UnfoldTransitionProgressProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/UnfoldTransitionProgressProviderKosmos.kt
index 7c54a57..a0f5b58 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/UnfoldTransitionProgressProviderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/UnfoldTransitionProgressProviderKosmos.kt
@@ -18,6 +18,8 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
-var Kosmos.unfoldTransitionProgressProvider by Fixture { mock<UnfoldTransitionProgressProvider>() }
+val Kosmos.fakeUnfoldTransitionProgressProvider by Fixture { FakeUnfoldTransitionProvider() }
+
+val Kosmos.unfoldTransitionProgressProvider by
+ Fixture<UnfoldTransitionProgressProvider> { fakeUnfoldTransitionProgressProvider }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorKosmos.kt
index d03616a..6faa3b4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorKosmos.kt
@@ -16,10 +16,14 @@
package com.android.systemui.unfold.domain.interactor
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.unfold.data.repository.unfoldTransitionRepository
val Kosmos.unfoldTransitionInteractor by Fixture {
- UnfoldTransitionInteractorImpl(repository = unfoldTransitionRepository)
+ UnfoldTransitionInteractor(
+ repository = unfoldTransitionRepository,
+ configurationInteractor = configurationInteractor,
+ )
}
diff --git a/packages/SystemUI/utils/Android.bp b/packages/SystemUI/utils/Android.bp
new file mode 100644
index 0000000..1ef3816
--- /dev/null
+++ b/packages/SystemUI/utils/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+ name: "SystemUI-shared-utils",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "kotlinx_coroutines",
+ ],
+}
diff --git a/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/FlowConflated.kt b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/FlowConflated.kt
new file mode 100644
index 0000000..ed97c60
--- /dev/null
+++ b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/FlowConflated.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalTypeInference::class)
+
+package com.android.systemui.utils.coroutines.flow
+
+import kotlin.experimental.ExperimentalTypeInference
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.channelFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.produceIn
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+/**
+ * Creates an instance of a _cold_ [Flow] with elements that are sent to a [SendChannel] provided to
+ * the builder's [block] of code via [ProducerScope]. It allows elements to be produced by code that
+ * is running in a different context or concurrently.
+ *
+ * The resulting flow is _cold_, which means that [block] is called every time a terminal operator
+ * is applied to the resulting flow.
+ *
+ * This builder ensures thread-safety and context preservation, thus the provided [ProducerScope]
+ * can be used from any context, e.g. from a callback-based API. The resulting flow completes as
+ * soon as the code in the [block] completes. [awaitClose] should be used to keep the flow running,
+ * otherwise the channel will be closed immediately when block completes. [awaitClose] argument is
+ * called either when a flow consumer cancels the flow collection or when a callback-based API
+ * invokes [SendChannel.close] manually and is typically used to cleanup the resources after the
+ * completion, e.g. unregister a callback. Using [awaitClose] is mandatory in order to prevent
+ * memory leaks when the flow collection is cancelled, otherwise the callback may keep running even
+ * when the flow collector is already completed. To avoid such leaks, this method throws
+ * [IllegalStateException] if block returns, but the channel is not closed yet.
+ *
+ * A [conflated][conflate] channel is used. Use the [buffer] operator on the resulting flow to
+ * specify a user-defined value and to control what happens when data is produced faster than
+ * consumed, i.e. to control the back-pressure behavior.
+ *
+ * Adjacent applications of [callbackFlow], [flowOn], [buffer], and [produceIn] are always fused so
+ * that only one properly configured channel is used for execution.
+ *
+ * Example of usage that converts a multi-shot callback API to a flow. For single-shot callbacks use
+ * [suspendCancellableCoroutine].
+ *
+ * ```
+ * fun flowFrom(api: CallbackBasedApi): Flow<T> = callbackFlow {
+ * val callback = object : Callback { // Implementation of some callback interface
+ * override fun onNextValue(value: T) {
+ * // To avoid blocking you can configure channel capacity using
+ * // either buffer(Channel.CONFLATED) or buffer(Channel.UNLIMITED) to avoid overfill
+ * trySendBlocking(value)
+ * .onFailure { throwable ->
+ * // Downstream has been cancelled or failed, can log here
+ * }
+ * }
+ * override fun onApiError(cause: Throwable) {
+ * cancel(CancellationException("API Error", cause))
+ * }
+ * override fun onCompleted() = channel.close()
+ * }
+ * api.register(callback)
+ * /*
+ * * Suspends until either 'onCompleted'/'onApiError' from the callback is invoked
+ * * or flow collector is cancelled (e.g. by 'take(1)' or because a collector's coroutine was cancelled).
+ * * In both cases, callback will be properly unregistered.
+ * */
+ * awaitClose { api.unregister(callback) }
+ * }
+ * ```
+ * > The callback `register`/`unregister` methods provided by an external API must be thread-safe,
+ * > because `awaitClose` block can be called at any time due to asynchronous nature of
+ * > cancellation, even concurrently with the call of the callback.
+ *
+ * This builder is to be preferred over [callbackFlow], due to the latter's default configuration of
+ * using an internal buffer, negatively impacting system health.
+ *
+ * @see callbackFlow
+ */
+fun <T> conflatedCallbackFlow(
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit,
+): Flow<T> = callbackFlow(block).conflate()
+
+/**
+ * Creates an instance of a _cold_ [Flow] with elements that are sent to a [SendChannel] provided to
+ * the builder's [block] of code via [ProducerScope]. It allows elements to be produced by code that
+ * is running in a different context or concurrently. The resulting flow is _cold_, which means that
+ * [block] is called every time a terminal operator is applied to the resulting flow.
+ *
+ * This builder ensures thread-safety and context preservation, thus the provided [ProducerScope]
+ * can be used concurrently from different contexts. The resulting flow completes as soon as the
+ * code in the [block] and all its children completes. Use [awaitClose] as the last statement to
+ * keep it running. A more detailed example is provided in the documentation of [callbackFlow].
+ *
+ * A [conflated][conflate] channel is used. Use the [buffer] operator on the resulting flow to
+ * specify a user-defined value and to control what happens when data is produced faster than
+ * consumed, i.e. to control the back-pressure behavior.
+ *
+ * Adjacent applications of [channelFlow], [flowOn], [buffer], and [produceIn] are always fused so
+ * that only one properly configured channel is used for execution.
+ *
+ * Examples of usage:
+ * ```
+ * fun <T> Flow<T>.merge(other: Flow<T>): Flow<T> = channelFlow {
+ * // collect from one coroutine and send it
+ * launch {
+ * collect { send(it) }
+ * }
+ * // collect and send from this coroutine, too, concurrently
+ * other.collect { send(it) }
+ * }
+ *
+ * fun <T> contextualFlow(): Flow<T> = channelFlow {
+ * // send from one coroutine
+ * launch(Dispatchers.IO) {
+ * send(computeIoValue())
+ * }
+ * // send from another coroutine, concurrently
+ * launch(Dispatchers.Default) {
+ * send(computeCpuValue())
+ * }
+ * }
+ * ```
+ *
+ * This builder is to be preferred over [channelFlow], due to the latter's default configuration of
+ * using an internal buffer, negatively impacting system health.
+ *
+ * @see channelFlow
+ */
+fun <T> conflatedChannelFlow(
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit,
+): Flow<T> = channelFlow(block).conflate()
diff --git a/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
new file mode 100644
index 0000000..5f8c660
--- /dev/null
+++ b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalTypeInference::class)
+
+package com.android.systemui.utils.coroutines.flow
+
+import kotlin.experimental.ExperimentalTypeInference
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.FlowCollector
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.transformLatest
+
+/**
+ * Returns a flow that emits elements from the original flow transformed by [transform] function.
+ * When the original flow emits a new value, computation of the [transform] block for previous value
+ * is cancelled.
+ *
+ * For example, the following flow:
+ * ```
+ * flow {
+ * emit("a")
+ * delay(100)
+ * emit("b")
+ * }.mapLatest { value ->
+ * println("Started computing $value")
+ * delay(200)
+ * "Computed $value"
+ * }
+ * ```
+ *
+ * will print "Started computing a" and "Started computing b", but the resulting flow will contain
+ * only "Computed b" value.
+ *
+ * This operator is [conflated][conflate] by default, and as such should be preferred over usage of
+ * [mapLatest], due to the latter's default configuration of using an internal buffer, negatively
+ * impacting system health.
+ *
+ * @see mapLatest
+ */
+fun <T, R> Flow<T>.mapLatestConflated(@BuilderInference transform: suspend (T) -> R): Flow<R> =
+ mapLatest(transform).conflate()
+
+/**
+ * Returns a flow that switches to a new flow produced by [transform] function every time the
+ * original flow emits a value. When the original flow emits a new value, the previous flow produced
+ * by `transform` block is cancelled.
+ *
+ * For example, the following flow:
+ * ```
+ * flow {
+ * emit("a")
+ * delay(100)
+ * emit("b")
+ * }.flatMapLatest { value ->
+ * flow {
+ * emit(value)
+ * delay(200)
+ * emit(value + "_last")
+ * }
+ * }
+ * ```
+ *
+ * produces `a b b_last`
+ *
+ * This operator is [conflated][conflate] by default, and as such should be preferred over usage of
+ * [flatMapLatest], due to the latter's default configuration of using an internal buffer,
+ * negatively impacting system health.
+ *
+ * @see flatMapLatest
+ */
+fun <T, R> Flow<T>.flatMapLatestConflated(
+ @BuilderInference transform: suspend (T) -> Flow<R>,
+): Flow<R> = flatMapLatest(transform).conflate()
+
+/**
+ * Returns a flow that produces element by [transform] function every time the original flow emits a
+ * value. When the original flow emits a new value, the previous `transform` block is cancelled,
+ * thus the name `transformLatest`.
+ *
+ * For example, the following flow:
+ * ```
+ * flow {
+ * emit("a")
+ * delay(100)
+ * emit("b")
+ * }.transformLatest { value ->
+ * emit(value)
+ * delay(200)
+ * emit(value + "_last")
+ * }
+ * ```
+ *
+ * produces `a b b_last`.
+ *
+ * This operator is [conflated][conflate] by default, and as such should be preferred over usage of
+ * [transformLatest], due to the latter's default configuration of using an internal buffer,
+ * negatively impacting system health.
+ *
+ * @see transformLatest
+ */
+fun <T, R> Flow<T>.transformLatestConflated(
+ @BuilderInference transform: suspend FlowCollector<R>.(T) -> Unit,
+): Flow<R> = transformLatest(transform).conflate()
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 23e269a..cbbce1a 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -19,6 +19,7 @@
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE;
import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA;
@@ -39,6 +40,7 @@
import android.content.SharedPreferences;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
+import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
@@ -109,22 +111,16 @@
static final String LOCK_WALLPAPER_STAGE = "wallpaper-lock-stage";
@VisibleForTesting
static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage";
-
@VisibleForTesting
static final String WALLPAPER_BACKUP_DEVICE_INFO_STAGE = "wallpaper-backup-device-info-stage";
-
static final String EMPTY_SENTINEL = "empty";
static final String QUOTA_SENTINEL = "quota";
-
// Shared preferences constants.
static final String PREFS_NAME = "wbprefs.xml";
static final String SYSTEM_GENERATION = "system_gen";
static final String LOCK_GENERATION = "lock_gen";
- /**
- * An approximate area threshold to compare device dimension similarity
- */
- static final int AREA_THRESHOLD = 50; // TODO (b/327637867): determine appropriate threshold
+ static final float DEFAULT_ACCEPTABLE_PARALLAX = 0.2f;
// If this file exists, it means we exceeded our quota last time
private File mQuotaFile;
@@ -336,7 +332,6 @@
mEventLogger.onSystemImageWallpaperBackupFailed(error);
}
-
private void backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs,
boolean lockChanged, int lockGeneration, FullBackupDataOutput data) throws IOException {
final File lockImageStage = new File(getFilesDir(), LOCK_WALLPAPER_STAGE);
@@ -409,6 +404,16 @@
}
}
+ private static String readText(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ String result = "";
+ if (parser.next() == XmlPullParser.TEXT) {
+ result = parser.getText();
+ parser.nextTag();
+ }
+ return result;
+ }
+
@VisibleForTesting
// fullBackupFile is final, so we intercept backups here in tests.
protected void backupFile(File file, FullBackupDataOutput data) {
@@ -438,18 +443,10 @@
boolean lockImageStageExists = lockImageStage.exists();
try {
- // Parse the device dimensions of the source device and compare with target to
- // to identify whether we need to skip the remainder of the restore process
+ // Parse the device dimensions of the source device
Pair<Point, Point> sourceDeviceDimensions = parseDeviceDimensions(
deviceDimensionsStage);
- Point targetDeviceDimensions = getScreenDimensions();
- if (sourceDeviceDimensions != null && targetDeviceDimensions != null
- && isSourceDeviceSignificantlySmallerThanTarget(sourceDeviceDimensions.first,
- targetDeviceDimensions)) {
- Slog.d(TAG, "The source device is significantly smaller than target");
- }
-
// First parse the live component name so that we know for logging if we care about
// logging errors with the image restore.
ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
@@ -466,9 +463,10 @@
// to back up the original image on the source device, or there was no user-supplied
// wallpaper image present.
if (lockImageStageExists) {
- restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK);
+ restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK,
+ sourceDeviceDimensions);
}
- restoreFromStage(imageStage, infoStage, "wp", sysWhich);
+ restoreFromStage(imageStage, infoStage, "wp", sysWhich, sourceDeviceDimensions);
// And reset to the wallpaper service we should be using
if (mLockHasLiveComponent) {
@@ -543,16 +541,6 @@
}
}
- private static String readText(TypedXmlPullParser parser)
- throws IOException, XmlPullParserException {
- String result = "";
- if (parser.next() == XmlPullParser.TEXT) {
- result = parser.getText();
- parser.nextTag();
- }
- return result;
- }
-
@VisibleForTesting
void updateWallpaperComponent(ComponentName wpService, int which)
throws IOException {
@@ -578,10 +566,13 @@
}
}
- private void restoreFromStage(File stage, File info, String hintTag, int which)
+ private void restoreFromStage(File stage, File info, String hintTag, int which,
+ Pair<Point, Point> sourceDeviceDimensions)
throws IOException {
if (stage.exists()) {
if (multiCrop()) {
+ // TODO(b/332937943): implement offset adjustment by manually adjusting crop to
+ // adhere to device aspect ratio
SparseArray<Rect> cropHints = parseCropHints(info, hintTag);
if (cropHints != null) {
Slog.i(TAG, "Got restored wallpaper; applying which=" + which
@@ -601,7 +592,6 @@
}
return;
}
-
// Parse the restored info file to find the crop hint. Note that this currently
// relies on a priori knowledge of the wallpaper info file schema.
Rect cropHint = parseCropHint(info, hintTag);
@@ -609,8 +599,33 @@
Slog.i(TAG, "Got restored wallpaper; applying which=" + which
+ "; cropHint = " + cropHint);
try (FileInputStream in = new FileInputStream(stage)) {
- mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, true,
- which);
+
+ if (sourceDeviceDimensions != null && sourceDeviceDimensions.first != null) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ ParcelFileDescriptor pdf = ParcelFileDescriptor.open(stage, MODE_READ_ONLY);
+ BitmapFactory.decodeFileDescriptor(pdf.getFileDescriptor(),
+ null, options);
+ Point bitmapSize = new Point(options.outWidth, options.outHeight);
+ Point sourceDeviceSize = new Point(sourceDeviceDimensions.first.x,
+ sourceDeviceDimensions.first.y);
+ Point targetDeviceDimensions = getScreenDimensions();
+
+ // TODO: for now we handle only the case where the target device has smaller
+ // aspect ratio than the source device i.e. the target device is more narrow
+ // than the source device
+ if (isTargetMoreNarrowThanSource(targetDeviceDimensions,
+ sourceDeviceSize)) {
+ Rect adjustedCrop = findNewCropfromOldCrop(cropHint,
+ sourceDeviceDimensions.first, true, targetDeviceDimensions,
+ bitmapSize, true);
+
+ cropHint.set(adjustedCrop);
+ }
+ }
+
+ mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint,
+ true, which);
// And log the success
if ((which & FLAG_SYSTEM) > 0) {
@@ -629,6 +644,209 @@
}
}
+ /**
+ * This method computes the crop of the stored wallpaper to preserve its center point as the
+ * user had set it in the previous device.
+ *
+ * The algorithm involves first computing the original crop of the user (without parallax). Then
+ * manually adjusting the user's original crop to respect the current device's aspect ratio
+ * (thereby preserving the center point). Then finally, adding any leftover image real-estate
+ * (i.e. space left over on the horizontal axis) to add parallax effect. Parallax is only added
+ * if was present in the old device's settings.
+ *
+ */
+ private Rect findNewCropfromOldCrop(Rect oldCrop, Point oldDisplaySize, boolean oldRtl,
+ Point newDisplaySize, Point bitmapSize, boolean newRtl) {
+ Rect cropWithoutParallax = withoutParallax(oldCrop, oldDisplaySize, oldRtl, bitmapSize);
+ oldCrop = oldCrop.isEmpty() ? new Rect(0, 0, bitmapSize.x, bitmapSize.y) : oldCrop;
+ float oldParallaxAmount = ((float) oldCrop.width() / cropWithoutParallax.width()) - 1;
+
+ Rect newCropWithSameCenterWithoutParallax = sameCenter(newDisplaySize, bitmapSize,
+ cropWithoutParallax);
+
+ Rect newCrop = newCropWithSameCenterWithoutParallax;
+
+ // calculate the amount of left-over space there is in the image after adjusting the crop
+ // from the above operation i.e. in a rtl configuration, this is the remaining space in the
+ // image after subtracting the new crop's right edge coordinate from the image itself, and
+ // for ltr, its just the new crop's left edge coordinate (as it's the distance from the
+ // beginning of the image)
+ int widthAvailableForParallaxOnTheNewDevice =
+ (newRtl) ? newCrop.left : bitmapSize.x - newCrop.right;
+
+ // calculate relatively how much this available space is as a fraction of the total cropped
+ // image
+ float availableParallaxAmount =
+ (float) widthAvailableForParallaxOnTheNewDevice / newCrop.width();
+
+ float minAcceptableParallax = Math.min(DEFAULT_ACCEPTABLE_PARALLAX, oldParallaxAmount);
+
+ if (DEBUG) {
+ Slog.d(TAG, "- cropWithoutParallax: " + cropWithoutParallax);
+ Slog.d(TAG, "- oldParallaxAmount: " + oldParallaxAmount);
+ Slog.d(TAG, "- newCropWithSameCenterWithoutParallax: "
+ + newCropWithSameCenterWithoutParallax);
+ Slog.d(TAG, "- widthAvailableForParallaxOnTheNewDevice: "
+ + widthAvailableForParallaxOnTheNewDevice);
+ Slog.d(TAG, "- availableParallaxAmount: " + availableParallaxAmount);
+ Slog.d(TAG, "- minAcceptableParallax: " + minAcceptableParallax);
+ Slog.d(TAG, "- oldCrop: " + oldCrop);
+ Slog.d(TAG, "- oldDisplaySize: " + oldDisplaySize);
+ Slog.d(TAG, "- oldRtl: " + oldRtl);
+ Slog.d(TAG, "- newDisplaySize: " + newDisplaySize);
+ Slog.d(TAG, "- bitmapSize: " + bitmapSize);
+ Slog.d(TAG, "- newRtl: " + newRtl);
+ }
+ if (availableParallaxAmount >= minAcceptableParallax) {
+ // but in any case, don't put more parallax than the amount of the old device
+ float parallaxToAdd = Math.min(availableParallaxAmount, oldParallaxAmount);
+
+ int widthToAddForParallax = (int) (newCrop.width() * parallaxToAdd);
+ if (DEBUG) {
+ Slog.d(TAG, "- parallaxToAdd: " + parallaxToAdd);
+ Slog.d(TAG, "- widthToAddForParallax: " + widthToAddForParallax);
+ }
+ if (newRtl) {
+ newCrop.left -= widthToAddForParallax;
+ } else {
+ newCrop.right += widthToAddForParallax;
+ }
+ }
+ return newCrop;
+ }
+
+ /**
+ * This method computes the original crop of the user without parallax.
+ *
+ * NOTE: When the user sets the wallpaper with a specific crop, there may additional image added
+ * to the crop to support parallax. In order to determine the user's actual crop the parallax
+ * must be removed if it exists.
+ */
+ Rect withoutParallax(Rect crop, Point displaySize, boolean rtl, Point bitmapSize) {
+ // in the case an image's crop is not set, we assume the image itself is cropped
+ if (crop.isEmpty()) {
+ crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ }
+
+ if (DEBUG) {
+ Slog.w(TAG, "- crop: " + crop);
+ }
+
+ Rect adjustedCrop = new Rect(crop);
+ float suggestedDisplayRatio = (float) displaySize.x / displaySize.y;
+
+ // here we calculate the width of the wallpaper image such that it has the same aspect ratio
+ // as the given display i.e. the width of the image on a single page of the device without
+ // parallax (i.e. displaySize will correspond to the display the crop was originally set on)
+ int wallpaperWidthWithoutParallax = (int) (0.5f + (float) displaySize.x * crop.height()
+ / displaySize.y);
+ // subtracting wallpaperWidthWithoutParallax from the wallpaper crop gives the amount of
+ // parallax added
+ int widthToRemove = Math.max(0, crop.width() - wallpaperWidthWithoutParallax);
+
+ if (DEBUG) {
+ Slog.d(TAG, "- adjustedCrop: " + adjustedCrop);
+ Slog.d(TAG, "- suggestedDisplayRatio: " + suggestedDisplayRatio);
+ Slog.d(TAG, "- wallpaperWidthWithoutParallax: " + wallpaperWidthWithoutParallax);
+ Slog.d(TAG, "- widthToRemove: " + widthToRemove);
+ }
+ if (rtl) {
+ adjustedCrop.left += widthToRemove;
+ } else {
+ adjustedCrop.right -= widthToRemove;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "- adjustedCrop: " + crop);
+ }
+ return adjustedCrop;
+ }
+
+ /**
+ * This method computes a new crop based on the given crop in order to preserve the center point
+ * of the given crop on the provided displaySize. This is only for the case where the device
+ * displaySize has a smaller aspect ratio than the cropped image.
+ *
+ * NOTE: If the width to height ratio is less in the device display than cropped image
+ * this means the aspect ratios are off and there will be distortions in the image
+ * if the image is applied to the current display (i.e. the image will be skewed ->
+ * pixels in the image will not align correctly with the same pixels in the image that are
+ * above them)
+ */
+ Rect sameCenter(Point displaySize, Point bitmapSize, Rect crop) {
+
+ // in the case an image's crop is not set, we assume the image itself is cropped
+ if (crop.isEmpty()) {
+ crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ }
+
+ float screenRatio = (float) displaySize.x / displaySize.y;
+ float cropRatio = (float) crop.width() / crop.height();
+
+ Rect adjustedCrop = new Rect(crop);
+
+ if (screenRatio < cropRatio) {
+ // the screen is more narrow than the image, and as such, the image will need to be
+ // zoomed in till it fits in the vertical axis. Due to this, we need to manually adjust
+ // the image's crop in order for it to fit into the screen without having the framework
+ // do it (since the framework left aligns the image after zooming)
+
+ // Calculate the height of the adjusted wallpaper crop so it respects the aspect ratio
+ // of the device. To calculate the height, we will use the width of the current crop.
+ // This is so we find the largest height possible which also respects the device aspect
+ // ratio.
+ int heightToAdd = (int) (0.5f + crop.width() / screenRatio - crop.height());
+
+ // Calculate how much extra image space available that can be used to adjust
+ // the crop. If this amount is less than heightToAdd, from above, then that means we
+ // can't use heightToAdd. Instead we will need to use the maximum possible height, which
+ // is the height of the original bitmap. NOTE: the bitmap height may be different than
+ // the crop.
+ // since there is no guarantee to have height available on both sides
+ // (e.g. the available height might be fully at the bottom), grab the minimum
+ int availableHeight = 2 * Math.min(crop.top, bitmapSize.y - crop.bottom);
+ int actualHeightToAdd = Math.min(heightToAdd, availableHeight);
+
+ // half of the additional height is added to the top and bottom of the crop
+ adjustedCrop.top -= actualHeightToAdd / 2 + actualHeightToAdd % 2;
+ adjustedCrop.bottom += actualHeightToAdd / 2;
+
+ // Calculate the width of the adjusted crop. Initially we used the fixed width of the
+ // crop to calculate the heightToAdd, but since this height may be invalid (based on
+ // the calculation above) we calculate the width again instead of using the fixed width,
+ // using the adjustedCrop's updated height.
+ int widthToRemove = (int) (0.5f + crop.width() - adjustedCrop.height() * screenRatio);
+
+ // half of the additional width is subtracted from the left and right side of the crop
+ int widthToRemoveLeft = widthToRemove / 2;
+ int widthToRemoveRight = widthToRemove / 2 + widthToRemove % 2;
+
+ adjustedCrop.left += widthToRemoveLeft;
+ adjustedCrop.right -= widthToRemoveRight;
+
+ if (DEBUG) {
+ Slog.d(TAG, "cropRatio: " + cropRatio);
+ Slog.d(TAG, "screenRatio: " + screenRatio);
+ Slog.d(TAG, "heightToAdd: " + heightToAdd);
+ Slog.d(TAG, "actualHeightToAdd: " + actualHeightToAdd);
+ Slog.d(TAG, "availableHeight: " + availableHeight);
+ Slog.d(TAG, "widthToRemove: " + widthToRemove);
+ Slog.d(TAG, "adjustedCrop: " + adjustedCrop);
+ }
+
+ return adjustedCrop;
+ }
+
+ return adjustedCrop;
+ }
+
+ private boolean isTargetMoreNarrowThanSource(Point targetDisplaySize, Point srcDisplaySize) {
+ float targetScreenRatio = (float) targetDisplaySize.x / targetDisplaySize.y;
+ float srcScreenRatio = (float) srcDisplaySize.x / srcDisplaySize.y;
+
+ return (targetScreenRatio < srcScreenRatio);
+ }
+
private void logRestoreErrorIfNoLiveComponent(int which, String error) {
if (mSystemHasLiveComponent) {
return;
@@ -644,6 +862,7 @@
mEventLogger.onLockImageWallpaperRestoreFailed(error);
}
}
+
private Rect parseCropHint(File wallpaperInfo, String sectionTag) {
Rect cropHint = new Rect();
try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
@@ -681,7 +900,7 @@
if (type != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
if (!sectionTag.equals(tag)) continue;
- for (Pair<Integer, String> pair: List.of(
+ for (Pair<Integer, String> pair : List.of(
new Pair<>(WallpaperManager.PORTRAIT, "Portrait"),
new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"),
new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"),
@@ -907,22 +1126,6 @@
return internalDisplays;
}
- /**
- * This method compares the source and target dimensions, and returns true if there is a
- * significant difference in area between them and the source dimensions are smaller than the
- * target dimensions.
- *
- * @param sourceDimensions is the dimensions of the source device
- * @param targetDimensions is the dimensions of the target device
- */
- @VisibleForTesting
- boolean isSourceDeviceSignificantlySmallerThanTarget(Point sourceDimensions,
- Point targetDimensions) {
- int rawAreaDelta = (targetDimensions.x * targetDimensions.y)
- - (sourceDimensions.x * sourceDimensions.y);
- return rawAreaDelta > AREA_THRESHOLD;
- }
-
@VisibleForTesting
boolean isDeviceInRestore() {
try {
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index ec9223c..3ecdf3f 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -59,7 +59,6 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.graphics.Point;
import android.graphics.Rect;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
@@ -841,26 +840,6 @@
testParseCropHints(testMap);
}
- @Test
- public void test_sourceDimensionsAreLargerThanTarget() {
- // source device is larger than target, expecting to get false
- Point sourceDimensions = new Point(2208, 1840);
- Point targetDimensions = new Point(1080, 2092);
- boolean isSourceSmaller = mWallpaperBackupAgent
- .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
- assertThat(isSourceSmaller).isEqualTo(false);
- }
-
- @Test
- public void test_sourceDimensionsMuchSmallerThanTarget() {
- // source device is smaller than target, expecting to get true
- Point sourceDimensions = new Point(1080, 2092);
- Point targetDimensions = new Point(2208, 1840);
- boolean isSourceSmaller = mWallpaperBackupAgent
- .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
- assertThat(isSourceSmaller).isEqualTo(true);
- }
-
private void testParseCropHints(Map<Integer, Rect> testMap) throws Exception {
assumeTrue(multiCrop());
mockRestoredStaticWallpaperFile(testMap);
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 8ab2e0f..eb2ef29 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -91,6 +91,16 @@
}
flag {
+ name: "focus_click_point_window_bounds_from_a11y_window_info"
+ namespace: "accessibility"
+ description: "Uses A11yWindowInfo bounds for focus click point bounds checking"
+ bug: "317166487"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "fullscreen_fling_gesture"
namespace: "accessibility"
description: "When true, adds a fling gesture animation for fullscreen magnification"
@@ -144,3 +154,12 @@
description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness."
bug: "295575684"
}
+flag {
+ name: "send_hover_events_based_on_event_stream"
+ namespace: "accessibility"
+ description: "Send hover enter and exit based on the state of the hover event stream rather than the internal state of the touch explorer state machine. Because of the nondeterministic nature of gesture detection when done in talkback, relying on the internal state can cause crashes."
+ bug: "314251047"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index ccf9a90..fc0fb5b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -16,7 +16,15 @@
package com.android.server.accessibility;
+import static android.Manifest.permission.CREATE_VIRTUAL_DEVICE;
+import static android.Manifest.permission.INJECT_EVENTS;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.Manifest.permission.MANAGE_ACCESSIBILITY;
+import static android.Manifest.permission.MANAGE_BIND_INSTANT_SERVICE;
+import static android.Manifest.permission.MODIFY_ACCESSIBILITY_DATA;
+import static android.Manifest.permission.RETRIEVE_WINDOW_CONTENT;
+import static android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION;
+import static android.Manifest.permission.STATUS_BAR_SERVICE;
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;
@@ -45,7 +53,6 @@
import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-import android.Manifest;
import android.accessibilityservice.AccessibilityGestureEvent;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -53,9 +60,11 @@
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.MagnificationConfig;
import android.accessibilityservice.TouchInteractionController;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
+import android.annotation.PermissionManuallyEnforced;
+import android.annotation.RequiresNoPermission;
import android.annotation.UserIdInt;
import android.app.ActivityOptions;
import android.app.AlertDialog;
@@ -95,6 +104,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.PermissionEnforcer;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteCallbackList;
@@ -204,7 +214,6 @@
* event dispatch for {@link AccessibilityEvent}s generated across all processes
* on the device. Events are dispatched to {@link AccessibilityService}s.
*/
-@SuppressWarnings("MissingPermissionAnnotation")
public class AccessibilityManagerService extends IAccessibilityManager.Stub
implements AbstractAccessibilityServiceConnection.SystemSupport,
AccessibilityUserState.ServiceInfoChangeListener,
@@ -479,7 +488,9 @@
AccessibilityDisplayListener a11yDisplayListener,
MagnificationController magnificationController,
@Nullable AccessibilityInputFilter inputFilter,
- ProxyManager proxyManager) {
+ ProxyManager proxyManager,
+ PermissionEnforcer permissionEnforcer) {
+ super(permissionEnforcer);
mContext = context;
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
@@ -514,6 +525,7 @@
* @param context A {@link Context} instance.
*/
public AccessibilityManagerService(Context context) {
+ super(PermissionEnforcer.fromContext(context));
mContext = context;
mPowerManager = context.getSystemService(PowerManager.class);
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
@@ -627,6 +639,7 @@
}
@Override
+ @RequiresNoPermission
public IAccessibilityManager.WindowTransformationSpec getWindowTransformationSpec(
int windowId) {
IAccessibilityManager.WindowTransformationSpec windowTransformationSpec =
@@ -723,8 +736,7 @@
void setBindInstantServiceAllowed(int userId, boolean allowed) {
mContext.enforceCallingOrSelfPermission(
- Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
- "setBindInstantServiceAllowed");
+ MANAGE_BIND_INSTANT_SERVICE, "setBindInstantServiceAllowed");
synchronized (mLock) {
final AccessibilityUserState userState = getUserStateLocked(userId);
if (allowed != userState.getBindInstantServiceAllowedLocked()) {
@@ -1120,6 +1132,7 @@
}
@Override
+ @RequiresNoPermission
public long addClient(IAccessibilityManagerClient callback, int userId) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".addClient", FLAGS_ACCESSIBILITY_MANAGER,
@@ -1183,6 +1196,7 @@
}
@Override
+ @RequiresNoPermission
public boolean removeClient(IAccessibilityManagerClient callback, int userId) {
// TODO(b/190216606): Add tracing for removeClient when implementation is the same in master
@@ -1211,6 +1225,7 @@
}
@Override
+ @RequiresNoPermission
public void sendAccessibilityEvent(AccessibilityEvent event, int userId) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".sendAccessibilityEvent", FLAGS_ACCESSIBILITY_MANAGER,
@@ -1327,12 +1342,13 @@
* system action.
*/
@Override
+ @EnforcePermission(MANAGE_ACCESSIBILITY)
public void registerSystemAction(RemoteAction action, int actionId) {
+ registerSystemAction_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".registerSystemAction",
FLAGS_ACCESSIBILITY_MANAGER, "action=" + action + ";actionId=" + actionId);
}
- mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
getSystemActionPerformer().registerSystemAction(actionId, action);
}
@@ -1342,12 +1358,14 @@
* system action.
*/
@Override
+ @EnforcePermission(MANAGE_ACCESSIBILITY)
public void unregisterSystemAction(int actionId) {
+ unregisterSystemAction_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".unregisterSystemAction",
FLAGS_ACCESSIBILITY_MANAGER, "actionId=" + actionId);
}
- mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
+
getSystemActionPerformer().unregisterSystemAction(actionId);
}
@@ -1360,6 +1378,7 @@
}
@Override
+ @RequiresNoPermission
public ParceledListSlice<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(
int userId) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
@@ -1403,6 +1422,7 @@
}
@Override
+ @RequiresNoPermission
public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType,
int userId) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
@@ -1445,6 +1465,7 @@
}
@Override
+ @RequiresNoPermission
public void interrupt(int userId) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".interrupt",
@@ -1498,6 +1519,7 @@
}
@Override
+ @RequiresNoPermission
public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken,
IAccessibilityInteractionConnection connection, String packageName,
int userId) throws RemoteException {
@@ -1513,6 +1535,7 @@
}
@Override
+ @RequiresNoPermission
public void removeAccessibilityInteractionConnection(IWindow window) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".removeAccessibilityInteractionConnection",
@@ -1522,23 +1545,25 @@
}
@Override
+ @EnforcePermission(MODIFY_ACCESSIBILITY_DATA)
public void setPictureInPictureActionReplacingConnection(
IAccessibilityInteractionConnection connection) throws RemoteException {
+ setPictureInPictureActionReplacingConnection_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".setPictureInPictureActionReplacingConnection",
FLAGS_ACCESSIBILITY_MANAGER, "connection=" + connection);
}
- mSecurityPolicy.enforceCallingPermission(Manifest.permission.MODIFY_ACCESSIBILITY_DATA,
- SET_PIP_ACTION_REPLACEMENT);
mA11yWindowManager.setPictureInPictureActionReplacingConnection(connection);
}
@Override
+ @EnforcePermission(RETRIEVE_WINDOW_CONTENT)
public void registerUiTestAutomationService(IBinder owner,
IAccessibilityServiceClient serviceClient,
AccessibilityServiceInfo accessibilityServiceInfo,
int userId,
int flags) {
+ registerUiTestAutomationService_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".registerUiTestAutomationService",
FLAGS_ACCESSIBILITY_MANAGER,
@@ -1546,9 +1571,6 @@
+ ";accessibilityServiceInfo=" + accessibilityServiceInfo + ";flags=" + flags);
}
- mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
- FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE);
-
synchronized (mLock) {
changeCurrentUserForTestAutomationIfNeededLocked(userId);
mUiAutomationManager.registerUiTestAutomationServiceLocked(owner, serviceClient,
@@ -1560,6 +1582,7 @@
}
@Override
+ @RequiresNoPermission
public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".unregisterUiTestAutomationService",
@@ -1619,15 +1642,14 @@
}
@Override
+ @EnforcePermission(RETRIEVE_WINDOW_CONTENT)
public IBinder getWindowToken(int windowId, int userId) {
+ getWindowToken_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getWindowToken",
FLAGS_ACCESSIBILITY_MANAGER, "windowId=" + windowId + ";userId=" + userId);
}
- mSecurityPolicy.enforceCallingPermission(
- Manifest.permission.RETRIEVE_WINDOW_TOKEN,
- GET_WINDOW_TOKEN);
synchronized (mLock) {
// We treat calls from a profile as if made by its parent as profiles
// share the accessibility state of the parent. The call below
@@ -1663,18 +1685,15 @@
* specified target.
*/
@Override
+ @EnforcePermission(STATUS_BAR_SERVICE)
public void notifyAccessibilityButtonClicked(int displayId, String targetName) {
+ notifyAccessibilityButtonClicked_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonClicked",
FLAGS_ACCESSIBILITY_MANAGER,
"displayId=" + displayId + ";targetName=" + targetName);
}
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Caller does not hold permission "
- + android.Manifest.permission.STATUS_BAR_SERVICE);
- }
if (targetName == null) {
synchronized (mLock) {
final AccessibilityUserState userState = getCurrentUserStateLocked();
@@ -1694,37 +1713,27 @@
* user, {@code false} otherwise
*/
@Override
+ @EnforcePermission(STATUS_BAR_SERVICE)
public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
+ notifyAccessibilityButtonVisibilityChanged_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonVisibilityChanged",
FLAGS_ACCESSIBILITY_MANAGER, "shown=" + shown);
}
- mSecurityPolicy.enforceCallingOrSelfPermission(
- android.Manifest.permission.STATUS_BAR_SERVICE);
synchronized (mLock) {
notifyAccessibilityButtonVisibilityChangedLocked(shown);
}
}
@Override
- @RequiresPermission(allOf = {
- Manifest.permission.STATUS_BAR_SERVICE,
- Manifest.permission.MANAGE_ACCESSIBILITY
- })
+ @EnforcePermission(allOf = { STATUS_BAR_SERVICE, MANAGE_ACCESSIBILITY })
public void notifyQuickSettingsTilesChanged(
@UserIdInt int userId, @NonNull List<ComponentName> tileComponentNames) {
+ notifyQuickSettingsTilesChanged_enforcePermission();
if (!android.view.accessibility.Flags.a11yQsShortcut()) {
return;
}
-
- mContext.enforceCallingPermission(
- Manifest.permission.STATUS_BAR_SERVICE,
- /* function= */ "notifyQuickSettingsTilesChanged");
- mContext.enforceCallingPermission(
- Manifest.permission.MANAGE_ACCESSIBILITY,
- /* function= */ "notifyQuickSettingsTilesChanged");
-
if (DEBUG) {
Slog.d(LOG_TAG, TextUtils.formatSimple(
"notifyQuickSettingsTilesChanged userId: %d, tileComponentNames: %s",
@@ -3953,19 +3962,15 @@
* class implementing a supported accessibility feature, or {@code null} if there's no
* specified target.
*/
+ @EnforcePermission(MANAGE_ACCESSIBILITY)
@Override
public void performAccessibilityShortcut(String targetName) {
+ performAccessibilityShortcut_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".performAccessibilityShortcut",
FLAGS_ACCESSIBILITY_MANAGER, "targetName=" + targetName);
}
- if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
- && (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
- != PackageManager.PERMISSION_GRANTED)) {
- throw new SecurityException(
- "performAccessibilityShortcut requires the MANAGE_ACCESSIBILITY permission");
- }
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::performAccessibilityShortcutInternal, this,
Display.DEFAULT_DISPLAY, UserShortcutType.HARDWARE, targetName));
@@ -4172,16 +4177,11 @@
* @hide
*/
@Override
+ @EnforcePermission(MANAGE_ACCESSIBILITY)
public void enableShortcutsForTargets(
boolean enable, @UserShortcutType int shortcutTypes,
@NonNull List<String> shortcutTargets, @UserIdInt int userId) {
- if (android.view.accessibility.Flags.migrateEnableShortcuts()) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.MANAGE_ACCESSIBILITY, "enableShortcutsForTargets");
- } else {
- mContext.enforceCallingPermission(
- Manifest.permission.MANAGE_ACCESSIBILITY, "enableShortcutsForTargets");
- }
+ enableShortcutsForTargets_enforcePermission();
for (int shortcutType : USER_SHORTCUT_TYPES) {
if ((shortcutTypes & shortcutType) == shortcutType) {
enableShortcutForTargets(enable, shortcutType, shortcutTargets, userId);
@@ -4376,10 +4376,9 @@
}
@Override
+ @EnforcePermission(MANAGE_ACCESSIBILITY)
public Bundle getA11yFeatureToTileMap(@UserIdInt int userId) {
- mContext.enforceCallingPermission(
- Manifest.permission.MANAGE_ACCESSIBILITY, "getA11yFeatureToTileMap");
-
+ getA11yFeatureToTileMap_enforcePermission();
Bundle bundle = new Bundle();
Map<ComponentName, ComponentName> a11yFeatureToTile =
getA11yFeatureToTileMapInternal(userId);
@@ -4435,17 +4434,13 @@
}
@Override
+ @EnforcePermission(MANAGE_ACCESSIBILITY)
public List<String> getAccessibilityShortcutTargets(@UserShortcutType int shortcutType) {
+ getAccessibilityShortcutTargets_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getAccessibilityShortcutTargets",
FLAGS_ACCESSIBILITY_MANAGER, "shortcutType=" + shortcutType);
}
-
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(
- "getAccessibilityShortcutService requires the MANAGE_ACCESSIBILITY permission");
- }
return getAccessibilityShortcutTargetsInternal(shortcutType);
}
@@ -4536,6 +4531,7 @@
* doesn't.
*/
@Override
+ @RequiresNoPermission
public boolean sendFingerprintGesture(int gestureKeyCode) {
if (mTraceManager.isA11yTracingEnabledForTypes(
FLAGS_ACCESSIBILITY_MANAGER | FLAGS_FINGERPRINT)) {
@@ -4546,6 +4542,8 @@
synchronized(mLock) {
if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) {
+ // TODO(b/333547153) remove the AIDL definitions for these functions that are
+ // restricted to system server and move them to AccessibilityManagerInternal.
throw new SecurityException("Only SYSTEM can call sendFingerprintGesture");
}
}
@@ -4564,6 +4562,7 @@
* registered.
*/
@Override
+ @RequiresNoPermission
public int getAccessibilityWindowId(@Nullable IBinder windowToken) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getAccessibilityWindowId",
@@ -4586,6 +4585,7 @@
* integer for non-interactive one.
*/
@Override
+ @RequiresNoPermission
public long getRecommendedTimeoutMillis() {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(
@@ -4610,8 +4610,10 @@
}
@Override
+ @EnforcePermission(STATUS_BAR_SERVICE)
public void setMagnificationConnection(
IMagnificationConnection connection) throws RemoteException {
+ setMagnificationConnection_enforcePermission();
if (mTraceManager.isA11yTracingEnabledForTypes(
FLAGS_ACCESSIBILITY_MANAGER | FLAGS_MAGNIFICATION_CONNECTION)) {
mTraceManager.logTrace(LOG_TAG + ".setMagnificationConnection",
@@ -4619,10 +4621,21 @@
"connection=" + connection);
}
- mSecurityPolicy.enforceCallingOrSelfPermission(
- android.Manifest.permission.STATUS_BAR_SERVICE);
-
getMagnificationConnectionManager().setConnection(connection);
+
+ if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
+ && connection == null
+ && mMagnificationController.isFullScreenMagnificationControllerInitialized()) {
+ // Since the connection does not exist, the system ui cannot provide the border
+ // implementation for fullscreen magnification. So we call reset to deactivate the
+ // fullscreen magnification to prevent the magnified but no border situation.
+ final ArrayList<Display> displays = getValidDisplayList();
+ for (int i = 0; i < displays.size(); i++) {
+ final Display display = displays.get(i);
+ getMagnificationController().getFullScreenMagnificationController()
+ .reset(display.getDisplayId(), false);
+ }
+ }
}
/**
@@ -4646,6 +4659,7 @@
}
@Override
+ @RequiresNoPermission
public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".associateEmbeddedHierarchy",
@@ -4658,6 +4672,7 @@
}
@Override
+ @RequiresNoPermission
public void disassociateEmbeddedHierarchy(@NonNull IBinder token) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".disassociateEmbeddedHierarchy",
@@ -4674,6 +4689,7 @@
* @return The stroke width.
*/
@Override
+ @RequiresNoPermission
public int getFocusStrokeWidth() {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getFocusStrokeWidth", FLAGS_ACCESSIBILITY_MANAGER);
@@ -4695,6 +4711,7 @@
* @return The color.
*/
@Override
+ @RequiresNoPermission
public int getFocusColor() {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getFocusColor", FLAGS_ACCESSIBILITY_MANAGER);
@@ -4716,6 +4733,7 @@
* @return {@code true} if the audio description is enabled, {@code false} otherwise.
*/
@Override
+ @RequiresNoPermission
public boolean isAudioDescriptionByDefaultEnabled() {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".isAudioDescriptionByDefaultEnabled",
@@ -4738,6 +4756,7 @@
* @param attributes The accessibility window attributes.
*/
@Override
+ @RequiresNoPermission
public void setAccessibilityWindowAttributes(int displayId, int windowId, int userId,
AccessibilityWindowAttributes attributes) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
@@ -4749,34 +4768,30 @@
}
@Override
- @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)
+ @EnforcePermission(SET_SYSTEM_AUDIO_CAPTION)
public void setSystemAudioCaptioningEnabled(boolean isEnabled, int userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.SET_SYSTEM_AUDIO_CAPTION,
- "setSystemAudioCaptioningEnabled");
-
+ setSystemAudioCaptioningEnabled_enforcePermission();
mCaptioningManagerImpl.setSystemAudioCaptioningEnabled(isEnabled, userId);
}
@Override
+ @RequiresNoPermission
public boolean isSystemAudioCaptioningUiEnabled(int userId) {
return mCaptioningManagerImpl.isSystemAudioCaptioningUiEnabled(userId);
}
@Override
- @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)
+ @EnforcePermission(SET_SYSTEM_AUDIO_CAPTION)
public void setSystemAudioCaptioningUiEnabled(boolean isEnabled, int userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.SET_SYSTEM_AUDIO_CAPTION,
- "setSystemAudioCaptioningUiEnabled");
-
+ setSystemAudioCaptioningUiEnabled_enforcePermission();
mCaptioningManagerImpl.setSystemAudioCaptioningUiEnabled(isEnabled, userId);
}
@Override
+ @EnforcePermission(CREATE_VIRTUAL_DEVICE)
public boolean registerProxyForDisplay(IAccessibilityServiceClient client, int displayId)
throws RemoteException {
- mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.CREATE_VIRTUAL_DEVICE);
+ registerProxyForDisplay_enforcePermission();
mSecurityPolicy.checkForAccessibilityPermissionOrRole();
if (client == null) {
return false;
@@ -4812,8 +4827,9 @@
}
@Override
+ @EnforcePermission(CREATE_VIRTUAL_DEVICE)
public boolean unregisterProxyForDisplay(int displayId) {
- mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.CREATE_VIRTUAL_DEVICE);
+ unregisterProxyForDisplay_enforcePermission();
mSecurityPolicy.checkForAccessibilityPermissionOrRole();
final long identity = Binder.clearCallingIdentity();
try {
@@ -4828,6 +4844,7 @@
}
@Override
+ @RequiresNoPermission
public boolean startFlashNotificationSequence(String opPkg,
@FlashNotificationReason int reason, IBinder token) {
final long identity = Binder.clearCallingIdentity();
@@ -4840,6 +4857,7 @@
}
@Override
+ @RequiresNoPermission
public boolean stopFlashNotificationSequence(String opPkg) {
final long identity = Binder.clearCallingIdentity();
try {
@@ -4850,6 +4868,7 @@
}
@Override
+ @RequiresNoPermission
public boolean startFlashNotificationEvent(String opPkg,
@FlashNotificationReason int reason, String reasonPkg) {
final long identity = Binder.clearCallingIdentity();
@@ -4862,6 +4881,7 @@
}
@Override
+ @RequiresNoPermission
public boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId) {
final long identity = Binder.clearCallingIdentity();
try {
@@ -4903,6 +4923,7 @@
}
@Override
+ @RequiresNoPermission
public boolean sendRestrictedDialogIntent(String packageName, int uid, int userId) {
// The accessibility service is allowed. Don't show the restricted dialog.
if (isAccessibilityTargetAllowed(packageName, uid, userId)) {
@@ -4936,8 +4957,9 @@
}
@Override
+ @EnforcePermission(MANAGE_ACCESSIBILITY)
public boolean isAccessibilityServiceWarningRequired(AccessibilityServiceInfo info) {
- mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
+ isAccessibilityServiceWarningRequired_enforcePermission();
final ComponentName componentName = info.getComponentName();
// Warning is not required if the service is already enabled.
@@ -4983,6 +5005,7 @@
}
@Override
+ @PermissionManuallyEnforced // DUMP
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
synchronized (mLock) {
@@ -5125,6 +5148,7 @@
}
@Override
+ @RequiresNoPermission
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
@@ -5234,7 +5258,14 @@
//Clip to the window bounds.
Rect windowBounds = mTempRect1;
- getWindowBounds(focus.getWindowId(), windowBounds);
+ if (Flags.focusClickPointWindowBoundsFromA11yWindowInfo()) {
+ AccessibilityWindowInfo window = focus.getWindow();
+ if (window != null) {
+ window.getBoundsInScreen(windowBounds);
+ }
+ } else {
+ getWindowBounds(focus.getWindowId(), windowBounds);
+ }
if (!boundsInScreenBeforeMagnification.intersect(windowBounds)) {
return false;
}
@@ -6138,9 +6169,9 @@
}
@Override
+ @EnforcePermission(INJECT_EVENTS)
public void injectInputEventToInputFilter(InputEvent event) {
- mSecurityPolicy.enforceCallingPermission(Manifest.permission.INJECT_EVENTS,
- "injectInputEventToInputFilter");
+ injectInputEventToInputFilter_enforcePermission();
synchronized (mLock) {
final long endMillis =
SystemClock.uptimeMillis() + WAIT_INPUT_FILTER_INSTALL_TIMEOUT_MS;
@@ -6225,11 +6256,11 @@
}
}
+ /** Used to attach accessibility overlays from the system itself i.e. magnification. */
+ @EnforcePermission(INTERNAL_SYSTEM_WINDOW)
@Override
- public void attachAccessibilityOverlayToDisplay(
- int displayId, SurfaceControl sc) {
- mContext.enforceCallingPermission(
- INTERNAL_SYSTEM_WINDOW, "attachAccessibilityOverlayToDisplay");
+ public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {
+ attachAccessibilityOverlayToDisplay_enforcePermission();
mMainHandler.sendMessage(
obtainMessage(
AccessibilityManagerService::attachAccessibilityOverlayToDisplayInternal,
@@ -6240,6 +6271,7 @@
null));
}
+ /** Called by services to attach accessibility overlays. */
@Override
public void attachAccessibilityOverlayToDisplay(
int interactionId,
diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
index b119d7d..853b824 100644
--- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
+++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
@@ -17,6 +17,7 @@
package com.android.server.accessibility;
import android.accessibilityservice.AccessibilityService;
+import android.annotation.RequiresNoPermission;
import android.os.Binder;
import android.os.RemoteException;
import android.util.Slog;
@@ -34,7 +35,6 @@
* If we are stripping and/or replacing the actions from a window, we need to intercept the
* nodes heading back to the service and swap out the actions.
*/
-@SuppressWarnings("MissingPermissionAnnotation")
public class ActionReplacingCallback extends IAccessibilityInteractionConnectionCallback.Stub {
private static final boolean DEBUG = false;
private static final String LOG_TAG = "ActionReplacingCallback";
@@ -97,6 +97,7 @@
}
@Override
+ @RequiresNoPermission
public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) {
synchronized (mLock) {
if (interactionId == mInteractionId) {
@@ -114,6 +115,7 @@
}
@Override
+ @RequiresNoPermission
public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
int interactionId) {
synchronized (mLock) {
@@ -132,6 +134,7 @@
}
@Override
+ @RequiresNoPermission
public void setPrefetchAccessibilityNodeInfoResult(List<AccessibilityNodeInfo> infos,
int interactionId)
throws RemoteException {
@@ -163,6 +166,7 @@
}
@Override
+ @RequiresNoPermission
public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId)
throws RemoteException {
// There's no reason to use this class when performing actions. Do something reasonable.
@@ -170,6 +174,7 @@
}
@Override
+ @RequiresNoPermission
public void sendTakeScreenshotOfWindowError(int errorCode, int interactionId)
throws RemoteException {
mServiceCallback.sendTakeScreenshotOfWindowError(errorCode, interactionId);
@@ -285,6 +290,7 @@
}
@Override
+ @RequiresNoPermission
public void sendAttachOverlayResult(
@AccessibilityService.AttachOverlayResult int result, int interactionId)
throws RemoteException {
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 be2ad21..2f54f8c 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -240,7 +240,7 @@
}
private void clear(MotionEvent event, int policyFlags) {
- if (mState.isTouchExploring()) {
+ if (mState.isTouchExploring() || Flags.sendHoverEventsBasedOnEventStream()) {
// If a touch exploration gesture is in progress send events for its end.
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
}
@@ -563,7 +563,7 @@
mSendHoverEnterAndMoveDelayed.clear();
mSendHoverExitDelayed.cancel();
// If a touch exploration gesture is in progress send events for its end.
- if (mState.isTouchExploring()) {
+ if (mState.isTouchExploring() || Flags.sendHoverEventsBasedOnEventStream()) {
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
}
if (mState.isClear()) {
@@ -852,11 +852,9 @@
final int pointerIdBits = (1 << pointerId);
if (mSendHoverEnterAndMoveDelayed.isPending()) {
// If we have not delivered the enter schedule an exit.
- if (Flags.resetHoverEventTimerOnActionUp()) {
- // We cancel first to reset the time window so that the user has the full amount of
- // time to do a multi tap.
- mSendHoverEnterAndMoveDelayed.repost();
- }
+ // We cancel first to reset the time window so that the user has the full amount of
+ // time to do a multi tap.
+ mSendHoverEnterAndMoveDelayed.repost();
mSendHoverExitDelayed.post(event, rawEvent, pointerIdBits, policyFlags);
} else {
// The user is touch exploring so we send events for end.
@@ -1601,9 +1599,12 @@
+ " pointers down.");
return;
}
- if (Flags.resetHoverEventTimerOnActionUp() && mEvents.size() == 0) {
+ if (mEvents.size() == 0) {
return;
}
+ if (Flags.sendHoverEventsBasedOnEventStream()) {
+ sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags);
+ }
// Send an accessibility event to announce the touch exploration start.
mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
if (isSendMotionEventsEnabled()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index a5bbc7e..aa0af7e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -118,6 +118,7 @@
private final MagnificationThumbnailFeatureFlag mMagnificationThumbnailFeatureFlag;
@NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier;
+ @NonNull private final Supplier<Boolean> mMagnificationConnectionStateSupplier;
/**
* This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds
@@ -682,6 +683,12 @@
if (!mRegistered) {
return false;
}
+ // If the border implementation is on system ui side but the connection is not
+ // established, the fullscreen magnification should not work.
+ if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
+ && !mMagnificationConnectionStateSupplier.get()) {
+ return false;
+ }
if (DEBUG) {
Slog.i(LOG_TAG,
"setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
@@ -941,7 +948,8 @@
@NonNull AccessibilityTraceManager traceManager, @NonNull Object lock,
@NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
@NonNull MagnificationScaleProvider scaleProvider,
- @NonNull Executor backgroundExecutor) {
+ @NonNull Executor backgroundExecutor,
+ @NonNull Supplier<Boolean> magnificationConnectionStateSupplier) {
this(
new ControllerContext(
context,
@@ -955,7 +963,8 @@
/* thumbnailSupplier= */ null,
backgroundExecutor,
() -> new Scroller(context),
- TimeAnimator::new);
+ TimeAnimator::new,
+ magnificationConnectionStateSupplier);
}
/** Constructor for tests */
@@ -968,11 +977,13 @@
Supplier<MagnificationThumbnail> thumbnailSupplier,
@NonNull Executor backgroundExecutor,
Supplier<Scroller> scrollerSupplier,
- Supplier<TimeAnimator> timeAnimatorSupplier) {
+ Supplier<TimeAnimator> timeAnimatorSupplier,
+ @NonNull Supplier<Boolean> magnificationConnectionStateSupplier) {
mControllerCtx = ctx;
mLock = lock;
mScrollerSupplier = scrollerSupplier;
mTimeAnimatorSupplier = timeAnimatorSupplier;
+ mMagnificationConnectionStateSupplier = magnificationConnectionStateSupplier;
mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
addInfoChangedCallback(magnificationInfoChangedCallback);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 20bec59..76367a2 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -798,7 +798,9 @@
mLock,
this,
mScaleProvider,
- mBackgroundExecutor
+ mBackgroundExecutor,
+ () -> (isMagnificationConnectionManagerInitialized()
+ && getMagnificationConnectionManager().isConnected())
);
}
}
@@ -831,6 +833,12 @@
}
}
+ private boolean isMagnificationConnectionManagerInitialized() {
+ synchronized (mLock) {
+ return mMagnificationConnectionManager != null;
+ }
+ }
+
private @Nullable PointF getCurrentMagnificationCenterLocked(int displayId, int targetMode) {
if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
if (mMagnificationConnectionManager == null
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index ced10fb..590a1ef 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -41,3 +41,17 @@
description: "Use weak reference to address binder leak problem"
bug: "307972253"
}
+
+flag {
+ name: "add_last_focused_id_to_client_state"
+ namespace: "autofill"
+ description: "Include the current view id into the FillEventHistory events as part of ClientState"
+ bug: "334141398"
+}
+
+flag {
+ name: "add_session_id_to_client_state"
+ namespace: "autofill"
+ description: "Include the session id into the FillEventHistory events as part of ClientState"
+ bug: "333927465"
+}
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index 4506779..ce9d180 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -16,21 +16,16 @@
package com.android.server.autofill;
-import static com.android.server.autofill.Session.REQUEST_ID_KEY;
-import static com.android.server.autofill.Session.SESSION_ID_KEY;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentSender;
-import android.os.Bundle;
import android.service.autofill.ConvertCredentialResponse;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.util.Slog;
import android.view.autofill.IAutoFillManagerClient;
-import android.view.inputmethod.InlineSuggestionsRequest;
/**
* Requests autofill response from a Remote Autofill Service. This autofill service can be
@@ -55,6 +50,7 @@
private final RemoteFillService mRemoteFillService;
private final SecondaryProviderCallback mCallback;
+
private int mLastFlag;
SecondaryProviderHandler(
@@ -109,37 +105,18 @@
/**
* Requests a new fill response.
*/
- public void onFillRequest(FillRequest pendingFillRequest,
- InlineSuggestionsRequest pendingInlineSuggestionsRequest, int flag, int id,
+ public void onFillRequest(FillRequest pendingFillRequest, int flag,
IAutoFillManagerClient client) {
Slog.v(TAG, "Requesting fill response to secondary provider.");
mLastFlag = flag;
if (mRemoteFillService != null && mRemoteFillService.isCredentialAutofillService()) {
Slog.v(TAG, "About to call CredAutofill service as secondary provider");
- FillRequest request = addSessionIdAndRequestIdToClientState(pendingFillRequest,
- pendingInlineSuggestionsRequest, id);
- mRemoteFillService.onFillCredentialRequest(request, client);
+ mRemoteFillService.onFillCredentialRequest(pendingFillRequest, client);
} else {
mRemoteFillService.onFillRequest(pendingFillRequest);
}
}
- private FillRequest addSessionIdAndRequestIdToClientState(FillRequest pendingFillRequest,
- InlineSuggestionsRequest pendingInlineSuggestionsRequest, int sessionId) {
- if (pendingFillRequest.getClientState() == null) {
- pendingFillRequest = new FillRequest(pendingFillRequest.getId(),
- pendingFillRequest.getFillContexts(),
- pendingFillRequest.getHints(),
- new Bundle(),
- pendingFillRequest.getFlags(),
- pendingInlineSuggestionsRequest,
- pendingFillRequest.getDelayedFillIntentSender());
- }
- pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, sessionId);
- pendingFillRequest.getClientState().putInt(REQUEST_ID_KEY, pendingFillRequest.getId());
- return pendingFillRequest;
- }
-
public void destroy() {
mRemoteFillService.destroy();
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 0b68f5f..3a38406 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -117,6 +117,7 @@
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ServiceInfo;
+import android.credentials.CredentialManager;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialResponse;
import android.graphics.Bitmap;
@@ -252,7 +253,6 @@
private final AutofillManagerServiceImpl mService;
private final Handler mHandler;
private final AutoFillUI mUi;
-
/**
* Context associated with the session, it has the same {@link Context#getDisplayId() displayId}
* of the activity being autofilled.
@@ -751,12 +751,17 @@
if (shouldRequestSecondaryProvider(mPendingFillRequest.getFlags())
&& mSecondaryProviderHandler != null) {
Slog.v(TAG, "Requesting fill response to secondary provider.");
+ if (!mIsPrimaryCredential) {
+ mPendingFillRequest = addCredentialManagerDataToClientState(
+ mPendingFillRequest,
+ mPendingInlineSuggestionsRequest, id);
+ }
mSecondaryProviderHandler.onFillRequest(mPendingFillRequest,
- mPendingInlineSuggestionsRequest,
- mPendingFillRequest.getFlags(), id, mClient);
+ mPendingFillRequest.getFlags(), mClient);
} else if (mRemoteFillService != null) {
if (mIsPrimaryCredential) {
- mPendingFillRequest = addSessionIdAndRequestIdToClientState(mPendingFillRequest,
+ mPendingFillRequest = addCredentialManagerDataToClientState(
+ mPendingFillRequest,
mPendingInlineSuggestionsRequest, id);
mRemoteFillService.onFillCredentialRequest(mPendingFillRequest, mClient);
} else {
@@ -904,8 +909,9 @@
}
}
- private FillRequest addSessionIdAndRequestIdToClientState(FillRequest pendingFillRequest,
+ private FillRequest addCredentialManagerDataToClientState(FillRequest pendingFillRequest,
InlineSuggestionsRequest pendingInlineSuggestionsRequest, int sessionId) {
+
if (pendingFillRequest.getClientState() == null) {
pendingFillRequest = new FillRequest(pendingFillRequest.getId(),
pendingFillRequest.getFillContexts(),
@@ -917,6 +923,10 @@
}
pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, sessionId);
pendingFillRequest.getClientState().putInt(REQUEST_ID_KEY, pendingFillRequest.getId());
+ ResultReceiver resultReceiver = constructCredentialManagerCallback(
+ pendingFillRequest.getId());
+ pendingFillRequest.getClientState().putParcelable(
+ CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER, resultReceiver);
return pendingFillRequest;
}
@@ -4870,10 +4880,6 @@
}
- if (isCredmanIntegrationActive(response)) {
- addCredentialManagerCallback(response);
- }
-
if (response.supportsInlineSuggestions()) {
synchronized (mLock) {
if (requestShowInlineSuggestionsLocked(response, filterText)) {
@@ -5153,30 +5159,14 @@
return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
}
- private void addCredentialManagerCallback(FillResponse response) {
- if (response.getDatasets() == null) {
- return;
- }
- for (Dataset dataset: response.getDatasets()) {
- if (dataset.getId() != null
- && dataset.getId().equals(AutofillManager.PINNED_DATASET_ID)) {
- Slog.d(TAG, "Adding Credential Manager callback to a pinned entry");
- addCredentialManagerCallbackForDataset(dataset, response.getRequestId());
- }
- }
- }
-
- private void addCredentialManagerCallbackForDataset(Dataset dataset, int requestId) {
- AutofillId autofillId = null;
- if (dataset != null && dataset.getFieldIds().size() == 1) {
- autofillId = dataset.getFieldIds().get(0);
- }
- final AutofillId finalAutofillId = autofillId;
+ private ResultReceiver constructCredentialManagerCallback(int requestId) {
final ResultReceiver resultReceiver = new ResultReceiver(mHandler) {
+ final AutofillId mAutofillId = mCurrentViewId;
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == SUCCESS_CREDMAN_SELECTOR) {
- Slog.d(TAG, "onReceiveResult from Credential Manager bottom sheet");
+ Slog.d(TAG, "onReceiveResult from Credential Manager "
+ + "bottom sheet with mCurrentViewId: " + mAutofillId);
GetCredentialResponse getCredentialResponse =
resultData.getParcelable(
CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
@@ -5184,7 +5174,7 @@
if (Flags.autofillCredmanDevIntegration()) {
sendCredentialManagerResponseToApp(getCredentialResponse,
- /*exception=*/ null, finalAutofillId);
+ /*exception=*/ null, mAutofillId);
} else {
Dataset datasetFromCredential = getDatasetFromCredentialResponse(
getCredentialResponse);
@@ -5198,12 +5188,14 @@
String[] exception = resultData.getStringArray(
CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION);
if (exception != null && exception.length >= 2) {
+ String errType = exception[0];
+ String errMsg = exception[1];
Slog.w(TAG, "Credman bottom sheet from pinned "
- + "entry failed with: + " + exception[0] + " , "
- + exception[1]);
+ + "entry failed with: + " + errType + " , "
+ + errMsg);
sendCredentialManagerResponseToApp(/*response=*/ null,
- new GetCredentialException(exception[0], exception[1]),
- finalAutofillId);
+ new GetCredentialException(errType, errMsg),
+ mAutofillId);
}
} else {
Slog.d(TAG, "Unknown resultCode from credential "
@@ -5214,15 +5206,7 @@
ResultReceiver ipcFriendlyResultReceiver =
toIpcFriendlyResultReceiver(resultReceiver);
- Intent metadataIntent = dataset.getCredentialFillInIntent();
- if (metadataIntent == null) {
- metadataIntent = new Intent();
- }
-
- metadataIntent.putExtra(
- android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
- ipcFriendlyResultReceiver);
- dataset.setCredentialFillInIntent(metadataIntent);
+ return ipcFriendlyResultReceiver;
}
private ResultReceiver toIpcFriendlyResultReceiver(ResultReceiver resultReceiver) {
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index 82e9a26..7a2106b 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -175,7 +175,7 @@
// Create a new association reassigned to this user and a valid association ID
final String packageName = restored.getPackageName();
- final int newId = mAssociationStore.getNextId(userId);
+ final int newId = mAssociationStore.getNextId();
AssociationInfo newAssociation = new AssociationInfo.Builder(newId, userId, packageName,
restored).build();
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 9cfb535..c892b84 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -85,7 +85,7 @@
final int userId = getNextIntArgRequired();
final List<AssociationInfo> associationsForUser =
mAssociationStore.getActiveAssociationsByUser(userId);
- final int maxId = mAssociationStore.getMaxId(userId);
+ final int maxId = mAssociationStore.getMaxId();
out.println("Max ID: " + maxId);
out.println("Association ID | Package Name | Mac Address");
for (AssociationInfo association : associationsForUser) {
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index a18776e..d09d7e6 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -130,7 +130,7 @@
private final @NonNull PackageManagerInternal mPackageManagerInternal;
private final @NonNull AssociationStore mAssociationStore;
@NonNull
- private final ComponentName mCompanionDeviceActivity;
+ private final ComponentName mCompanionAssociationActivity;
public AssociationRequestsProcessor(@NonNull Context context,
@NonNull PackageManagerInternal packageManagerInternal,
@@ -138,9 +138,9 @@
mContext = context;
mPackageManagerInternal = packageManagerInternal;
mAssociationStore = associationStore;
- mCompanionDeviceActivity = createRelative(
+ mCompanionAssociationActivity = createRelative(
mContext.getString(R.string.config_companionDeviceManagerPackage),
- ".CompanionDeviceActivity");
+ ".CompanionAssociationActivity");
}
/**
@@ -204,7 +204,7 @@
extras.putParcelable(EXTRA_RESULT_RECEIVER, prepareForIpc(mOnRequestConfirmationReceiver));
final Intent intent = new Intent();
- intent.setComponent(mCompanionDeviceActivity);
+ intent.setComponent(mCompanionAssociationActivity);
intent.putExtras(extras);
// 2b.3. Create a PendingIntent.
@@ -232,7 +232,7 @@
extras.putBoolean(EXTRA_FORCE_CANCEL_CONFIRMATION, true);
final Intent intent = new Intent();
- intent.setComponent(mCompanionDeviceActivity);
+ intent.setComponent(mCompanionAssociationActivity);
intent.putExtras(extras);
return createPendingIntent(packageUid, intent);
@@ -284,7 +284,7 @@
@Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
boolean selfManaged, @Nullable IAssociationRequestCallback callback,
@Nullable ResultReceiver resultReceiver) {
- final int id = mAssociationStore.getNextId(userId);
+ final int id = mAssociationStore.getNextId();
final long timestamp = System.currentTimeMillis();
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
index ae2b708..29e8095 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -132,7 +132,7 @@
@GuardedBy("mLock")
private final Map<Integer, AssociationInfo> mIdToAssociationMap = new HashMap<>();
@GuardedBy("mLock")
- private final Map<Integer, Integer> mUserToMaxId = new HashMap<>();
+ private int mMaxId = 0;
@GuardedBy("mLocalListeners")
private final Set<OnChangeListener> mLocalListeners = new LinkedHashSet<>();
@@ -162,7 +162,7 @@
mPersisted = false;
mIdToAssociationMap.clear();
- mUserToMaxId.clear();
+ mMaxId = 0;
// The data is stored in DE directories, so we can read the data for all users now
// (which would not be possible if the data was stored to CE directories).
@@ -172,7 +172,7 @@
for (AssociationInfo association : entry.getValue().getAssociations()) {
mIdToAssociationMap.put(association.getId(), association);
}
- mUserToMaxId.put(entry.getKey(), entry.getValue().getMaxId());
+ mMaxId = Math.max(mMaxId, entry.getValue().getMaxId());
}
mPersisted = true;
@@ -183,18 +183,18 @@
/**
* Get the current max association id.
*/
- public int getMaxId(int userId) {
+ public int getMaxId() {
synchronized (mLock) {
- return mUserToMaxId.getOrDefault(userId, 0);
+ return mMaxId;
}
}
/**
* Get the next available association id.
*/
- public int getNextId(int userId) {
+ public int getNextId() {
synchronized (mLock) {
- return getMaxId(userId) + 1;
+ return getMaxId() + 1;
}
}
@@ -214,7 +214,7 @@
}
mIdToAssociationMap.put(id, association);
- mUserToMaxId.put(userId, Math.max(mUserToMaxId.getOrDefault(userId, 0), id));
+ mMaxId = Math.max(mMaxId, id);
writeCacheToDisk(userId);
@@ -305,7 +305,7 @@
mExecutor.execute(() -> {
Associations associations = new Associations();
synchronized (mLock) {
- associations.setMaxId(mUserToMaxId.getOrDefault(userId, 0));
+ associations.setMaxId(mMaxId);
associations.setAssociations(
CollectionUtils.filter(mIdToAssociationMap.values().stream().toList(),
a -> a.getUserId() == userId));
diff --git a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
index cfb7f337..af49df6 100644
--- a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
@@ -56,6 +56,7 @@
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.CollectionUtils;
import com.android.server.companion.association.AssociationStore;
import java.io.PrintWriter;
@@ -1031,6 +1032,9 @@
public void sendDevicePresenceEventOnUnlocked(int userId) {
final List<DevicePresenceEvent> deviceEvents = getPendingDevicePresenceEventsByUserId(
userId);
+ if (CollectionUtils.isEmpty(deviceEvents)) {
+ return;
+ }
final List<ObservableUuid> observableUuids =
mObservableUuidStore.getObservableUuidsForUser(userId);
// Notify and bind the app after the phone is unlocked.
@@ -1068,7 +1072,7 @@
}
}
- clearPendingDevicePresenceEventsByUserId(userId);
+ removePendingDevicePresenceEventsByUserId(userId);
}
private List<DevicePresenceEvent> getPendingDevicePresenceEventsByUserId(int userId) {
@@ -1077,9 +1081,11 @@
}
}
- private void clearPendingDevicePresenceEventsByUserId(int userId) {
+ private void removePendingDevicePresenceEventsByUserId(int userId) {
synchronized (mPendingDevicePresenceEvents) {
- mPendingDevicePresenceEvents.get(userId).clear();
+ if (mPendingDevicePresenceEvents.contains(userId)) {
+ mPendingDevicePresenceEvents.remove(userId);
+ }
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 23373f1..afeafa4 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -302,7 +302,7 @@
if (Flags.interceptIntentsBeforeApplyingPolicy()) {
if (mIntentListenerCallback != null && intent != null
&& mIntentListenerCallback.shouldInterceptIntent(intent)) {
- Slog.d(TAG, "Virtual device intercepting intent");
+ logActivityLaunchBlocked("Virtual device intercepting intent");
return false;
}
if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId,
@@ -318,7 +318,7 @@
}
if (mIntentListenerCallback != null && intent != null
&& mIntentListenerCallback.shouldInterceptIntent(intent)) {
- Slog.d(TAG, "Virtual device intercepting intent");
+ logActivityLaunchBlocked("Virtual device intercepting intent");
return false;
}
}
@@ -331,15 +331,17 @@
boolean isNewTask) {
// Mirror displays cannot contain activities.
if (waitAndGetIsMirrorDisplay()) {
- Slog.d(TAG, "Mirror virtual displays cannot contain activities.");
+ logActivityLaunchBlocked("Mirror virtual displays cannot contain activities.");
return false;
}
if (!isWindowingModeSupported(windowingMode)) {
- Slog.d(TAG, "Virtual device doesn't support windowing mode " + windowingMode);
+ logActivityLaunchBlocked(
+ "Virtual device doesn't support windowing mode " + windowingMode);
return false;
}
if ((activityInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
- Slog.d(TAG, "Virtual device requires android:canDisplayOnRemoteDevices=true");
+ logActivityLaunchBlocked(
+ "Activity requires android:canDisplayOnRemoteDevices=true");
return false;
}
final UserHandle activityUser =
@@ -350,11 +352,11 @@
return true;
}
if (!activityUser.isSystem() && !mAllowedUsers.contains(activityUser)) {
- Slog.d(TAG, "Virtual device launch disallowed from user " + activityUser);
+ logActivityLaunchBlocked("Activity launch disallowed from user " + activityUser);
return false;
}
if (!activityMatchesDisplayCategory(activityInfo)) {
- Slog.d(TAG, "The activity's required display category '"
+ logActivityLaunchBlocked("The activity's required display category '"
+ activityInfo.requiredDisplayCategory
+ "' not found on virtual display with the following categories: "
+ mDisplayCategories);
@@ -363,7 +365,7 @@
synchronized (mGenericWindowPolicyControllerLock) {
if (!isAllowedByPolicy(mActivityLaunchAllowedByDefault, mActivityPolicyExemptions,
activityComponent)) {
- Slog.d(TAG, "Virtual device launch disallowed by policy: "
+ logActivityLaunchBlocked("Activity launch disallowed by policy: "
+ activityComponent);
return false;
}
@@ -371,7 +373,7 @@
if (isNewTask && launchingFromDisplayId != DEFAULT_DISPLAY
&& !isAllowedByPolicy(mCrossTaskNavigationAllowedByDefault,
mCrossTaskNavigationExemptions, activityComponent)) {
- Slog.d(TAG, "Virtual device cross task navigation disallowed by policy: "
+ logActivityLaunchBlocked("Cross task navigation disallowed by policy: "
+ activityComponent);
return false;
}
@@ -380,12 +382,18 @@
// based on FLAG_STREAM_PERMISSIONS
if (mPermissionDialogComponent != null
&& mPermissionDialogComponent.equals(activityComponent)) {
+ logActivityLaunchBlocked("Permission dialog not allowed on virtual device");
return false;
}
return true;
}
+ private void logActivityLaunchBlocked(String reason) {
+ Slog.d(TAG, "Virtual device activity launch disallowed on display "
+ + waitAndGetDisplayId() + ", reason: " + reason);
+ }
+
@Override
@SuppressWarnings("AndroidFrameworkRequiresPermission")
public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 9b72288..3c25835 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -226,7 +226,7 @@
token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
mNativeWrapper.closeUinput(inputDeviceDescriptor.getNativePointer());
String phys = inputDeviceDescriptor.getPhys();
- InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys);
+ InputManagerGlobal.getInstance().removeUniqueIdAssociationByDescriptor(phys);
// Type associations are added in the case of navigation touchpads. Those should be removed
// once the input device gets closed.
if (inputDeviceDescriptor.getType() == InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD) {
@@ -319,9 +319,9 @@
return formatSimple("virtual%s:%d", type, sNextPhysId.getAndIncrement());
}
- private void setUniqueIdAssociation(int displayId, String phys) {
+ private void setUniqueIdAssociationByPort(int displayId, String phys) {
final String displayUniqueId = mDisplayManagerInternal.getDisplayInfo(displayId).uniqueId;
- InputManagerGlobal.getInstance().addUniqueIdAssociation(phys, displayUniqueId);
+ InputManagerGlobal.getInstance().addUniqueIdAssociationByPort(phys, displayUniqueId);
}
boolean sendDpadKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) {
@@ -809,7 +809,7 @@
final int inputDeviceId;
- setUniqueIdAssociation(displayId, phys);
+ setUniqueIdAssociationByPort(displayId, phys);
try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId, displayId)) {
ptr = deviceOpener.get();
// See INVALID_PTR in libs/input/VirtualInputDevice.cpp.
@@ -835,7 +835,7 @@
throw e;
}
} catch (DeviceCreationException e) {
- InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys);
+ InputManagerGlobal.getInstance().removeUniqueIdAssociationByPort(phys);
throw e;
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 392c0c7..e095fa3 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -145,6 +145,7 @@
defaults: [
"platform_service_defaults",
"android.hardware.power-java_shared",
+ "latest_android_hardware_broadcastradio_java_static",
],
srcs: [
":android.hardware.tv.hdmi.connection-V1-java-source",
@@ -207,7 +208,6 @@
"android.hardware.boot-V1.2-java", // HIDL
"android.hardware.boot-V1-java", // AIDL
"android.hardware.broadcastradio-V2.0-java", // HIDL
- "android.hardware.broadcastradio-V2-java", // AIDL
"android.hardware.health-V1.0-java", // HIDL
"android.hardware.health-V2.0-java", // HIDL
"android.hardware.health-V2.1-java", // HIDL
@@ -243,7 +243,6 @@
"com.android.sysprop.watchdog",
"securebox",
"apache-commons-math",
- "backstage_power_flags_lib",
"notification_flags_lib",
"power_hint_flags_lib",
"biometrics_flags_lib",
diff --git a/packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java
similarity index 100%
rename from packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java
rename to services/core/java/com/android/server/ExplicitHealthCheckController.java
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index bdc4a7a..2545620 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -22,6 +22,7 @@
per-file *Battery* = file:/BATTERY_STATS_OWNERS
per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
per-file *Binder* = file:/core/java/com/android/internal/os/BINDER_OWNERS
+per-file ExplicitHealthCheckController.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS
per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
per-file **IpSec* = file:/services/core/java/com/android/server/net/OWNERS
per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
@@ -35,9 +36,9 @@
per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
per-file MmsServiceBroker.java = file:/telephony/OWNERS
per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS
-per-file PackageWatchdog.java, RescueParty.java = file:/services/core/java/com/android/server/rollback/OWNERS
+per-file PackageWatchdog.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS
per-file PinnerService.java = file:/core/java/android/app/pinner/OWNERS
-per-file RescueParty.java = [email protected], [email protected], [email protected]
+per-file RescueParty.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS
per-file SensitiveContentProtectionManagerService.java = file:/core/java/android/permission/OWNERS
per-file SystemClockTime.java = file:/services/core/java/com/android/server/timedetector/OWNERS
per-file SystemTimeZone.java = file:/services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
similarity index 99%
rename from packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
rename to services/core/java/com/android/server/PackageWatchdog.java
index 75a8bdf..6f20adf 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -39,15 +39,15 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.LongArrayQueue;
import android.util.Slog;
import android.util.Xml;
-import android.utils.BackgroundThread;
-import android.utils.LongArrayQueue;
-import android.utils.XmlUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
similarity index 99%
rename from packages/CrashRecovery/services/java/com/android/server/RescueParty.java
rename to services/core/java/com/android/server/RescueParty.java
index f86eb61..271d552 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -31,6 +31,7 @@
import android.crashrecovery.flags.Flags;
import android.os.Build;
import android.os.Environment;
+import android.os.FileUtils;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.SystemClock;
@@ -43,11 +44,10 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
-import android.utils.ArrayUtils;
-import android.utils.FileUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 4694e9f..6c7546e 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import static android.permission.flags.Flags.sensitiveContentImprovements;
import static android.permission.flags.Flags.sensitiveNotificationAppProtection;
import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
import static android.view.flags.Flags.sensitiveContentAppProtection;
@@ -24,6 +25,7 @@
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS;
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START;
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP;
+import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_NOTIFICATION_APP_PROTECTION_SESSION;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -91,9 +93,11 @@
private boolean mProjectionActive = false;
private static class MediaProjectionSession {
- final int mUid;
- final long mSessionId;
- final boolean mIsExempted;
+ private final int mUid;
+ private final long mSessionId;
+ private final boolean mIsExempted;
+ private final ArraySet<String> mAllSeenNotificationKeys = new ArraySet<>();
+ private final ArraySet<String> mSeenOtpNotificationKeys = new ArraySet<>();
MediaProjectionSession(int uid, boolean isExempted, long sessionId) {
mUid = uid;
@@ -123,6 +127,14 @@
);
}
+ public void logAppNotificationsProtected() {
+ FrameworkStatsLog.write(
+ SENSITIVE_NOTIFICATION_APP_PROTECTION_SESSION,
+ mSessionId,
+ mAllSeenNotificationKeys.size(),
+ mSeenOtpNotificationKeys.size());
+ }
+
public void logAppBlocked(int uid) {
FrameworkStatsLog.write(
FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
@@ -142,6 +154,32 @@
FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED
);
}
+
+ private void addSeenNotificationKey(String key) {
+ mAllSeenNotificationKeys.add(key);
+ }
+
+ private void addSeenOtpNotificationKey(String key) {
+ mAllSeenNotificationKeys.add(key);
+ mSeenOtpNotificationKeys.add(key);
+ }
+
+ public void addSeenNotifications(
+ @NonNull StatusBarNotification[] notifications,
+ @NonNull RankingMap rankingMap) {
+ for (StatusBarNotification sbn : notifications) {
+ if (sbn == null) {
+ Log.w(TAG, "Unable to parse null notification");
+ continue;
+ }
+
+ if (notificationHasSensitiveContent(sbn, rankingMap)) {
+ addSeenOtpNotificationKey(sbn.getKey());
+ } else {
+ addSeenNotificationKey(sbn.getKey());
+ }
+ }
+ }
}
private final MediaProjectionManager.Callback mProjectionCallback =
@@ -297,6 +335,9 @@
mProjectionActive = false;
if (mMediaProjectionSession != null) {
mMediaProjectionSession.logProjectionSessionStop();
+ if (sensitiveContentImprovements()) {
+ mMediaProjectionSession.logAppNotificationsProtected();
+ }
mMediaProjectionSession = null;
}
@@ -306,6 +347,7 @@
}
}
+ @GuardedBy("mSensitiveContentProtectionLock")
private void updateAppsThatShouldBlockScreenCapture() {
RankingMap rankingMap;
try {
@@ -315,10 +357,16 @@
rankingMap = null;
}
+ if (rankingMap == null) {
+ Log.w(TAG, "Ranking map not initialized.");
+ return;
+ }
+
updateAppsThatShouldBlockScreenCapture(rankingMap);
}
- private void updateAppsThatShouldBlockScreenCapture(RankingMap rankingMap) {
+ @GuardedBy("mSensitiveContentProtectionLock")
+ private void updateAppsThatShouldBlockScreenCapture(@NonNull RankingMap rankingMap) {
StatusBarNotification[] notifications;
try {
notifications = mNotificationListener.getActiveNotifications();
@@ -327,23 +375,28 @@
notifications = new StatusBarNotification[0];
}
+ if (sensitiveContentImprovements() && mMediaProjectionSession != null) {
+ mMediaProjectionSession.addSeenNotifications(notifications, rankingMap);
+ }
+
// notify windowmanager of any currently posted sensitive content notifications
ArraySet<PackageInfo> packageInfos =
getSensitivePackagesFromNotifications(notifications, rankingMap);
+
if (packageInfos.size() > 0) {
mWindowManager.addBlockScreenCaptureForApps(packageInfos);
}
}
- private ArraySet<PackageInfo> getSensitivePackagesFromNotifications(
- @NonNull StatusBarNotification[] notifications, RankingMap rankingMap) {
+ private static @NonNull ArraySet<PackageInfo> getSensitivePackagesFromNotifications(
+ @NonNull StatusBarNotification[] notifications, @NonNull RankingMap rankingMap) {
ArraySet<PackageInfo> sensitivePackages = new ArraySet<>();
- if (rankingMap == null) {
- Log.w(TAG, "Ranking map not initialized.");
- return sensitivePackages;
- }
-
for (StatusBarNotification sbn : notifications) {
+ if (sbn == null) {
+ Log.w(TAG, "Unable to parse null notification");
+ continue;
+ }
+
PackageInfo info = getSensitivePackageFromNotification(sbn, rankingMap);
if (info != null) {
sensitivePackages.add(info);
@@ -352,24 +405,20 @@
return sensitivePackages;
}
- private PackageInfo getSensitivePackageFromNotification(
- StatusBarNotification sbn, RankingMap rankingMap) {
- if (sbn == null) {
- Log.w(TAG, "Unable to protect null notification");
- return null;
- }
- if (rankingMap == null) {
- Log.w(TAG, "Ranking map not initialized.");
- return null;
- }
-
- NotificationListenerService.Ranking ranking = rankingMap.getRawRankingObject(sbn.getKey());
- if (ranking != null && ranking.hasSensitiveContent()) {
+ private static @Nullable PackageInfo getSensitivePackageFromNotification(
+ @NonNull StatusBarNotification sbn, @NonNull RankingMap rankingMap) {
+ if (notificationHasSensitiveContent(sbn, rankingMap)) {
return new PackageInfo(sbn.getPackageName(), sbn.getUid());
}
return null;
}
+ private static boolean notificationHasSensitiveContent(
+ @NonNull StatusBarNotification sbn, @NonNull RankingMap rankingMap) {
+ NotificationListenerService.Ranking ranking = rankingMap.getRawRankingObject(sbn.getKey());
+ return ranking != null && ranking.hasSensitiveContent();
+ }
+
@VisibleForTesting
class NotificationListener extends NotificationListenerService {
@Override
@@ -395,6 +444,16 @@
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
"SensitiveContentProtectionManagerService.onNotificationPosted");
try {
+ if (sbn == null) {
+ Log.w(TAG, "Unable to parse null notification");
+ return;
+ }
+
+ if (rankingMap == null) {
+ Log.w(TAG, "Ranking map not initialized.");
+ return;
+ }
+
synchronized (mSensitiveContentProtectionLock) {
if (!mProjectionActive) {
return;
@@ -407,6 +466,14 @@
mWindowManager.addBlockScreenCaptureForApps(
new ArraySet(Set.of(packageInfo)));
}
+
+ if (sensitiveContentImprovements() && mMediaProjectionSession != null) {
+ if (packageInfo != null) {
+ mMediaProjectionSession.addSeenOtpNotificationKey(sbn.getKey());
+ } else {
+ mMediaProjectionSession.addSeenNotificationKey(sbn.getKey());
+ }
+ }
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
@@ -419,6 +486,11 @@
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
"SensitiveContentProtectionManagerService.onNotificationRankingUpdate");
try {
+ if (rankingMap == null) {
+ Log.w(TAG, "Ranking map not initialized.");
+ return;
+ }
+
synchronized (mSensitiveContentProtectionLock) {
if (mProjectionActive) {
updateAppsThatShouldBlockScreenCapture(rankingMap);
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 67e18ca..4dd3a8f 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -149,7 +149,6 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
-import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.modules.utils.TypedXmlPullParser;
@@ -3278,7 +3277,7 @@
throws RemoteException {
super.setCeStorageProtection_enforcePermission();
- mVold.setCeStorageProtection(userId, HexDump.toHexString(secret));
+ mVold.setCeStorageProtection(userId, secret);
}
/* Only for use by LockSettingsService */
@@ -3288,7 +3287,7 @@
super.unlockCeStorage_enforcePermission();
if (StorageManager.isFileEncrypted()) {
- mVold.unlockCeStorage(userId, HexDump.toHexString(secret));
+ mVold.unlockCeStorage(userId, secret);
}
synchronized (mLock) {
mCeUnlockedUsers.append(userId);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7ea82b0..26e9bf5 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -240,6 +240,7 @@
import com.android.server.am.ActivityManagerService.ItemMatcher;
import com.android.server.am.LowMemDetector.MemFactor;
import com.android.server.am.ServiceRecord.ShortFgsInfo;
+import com.android.server.am.ServiceRecord.TimeLimitedFgsInfo;
import com.android.server.pm.KnownPackages;
import com.android.server.uri.NeededUriGrants;
import com.android.server.utils.AnrTimer;
@@ -500,6 +501,12 @@
// see ServiceRecord#getEarliestStopTypeAndTime()
private final ServiceAnrTimer mFGSAnrTimer;
+ /**
+ * Mapping of uid to {fgs_type, fgs_info} for time limited fgs types such as dataSync and
+ * mediaProcessing.
+ */
+ final SparseArray<SparseArray<TimeLimitedFgsInfo>> mTimeLimitedFgsInfo = new SparseArray<>();
+
// allowlisted packageName.
ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>();
@@ -2275,12 +2282,12 @@
// Whether to extend the SHORT_SERVICE time out.
boolean extendShortServiceTimeout = false;
- // Whether to extend the timeout for a time-limited FGS type.
- boolean extendFgsTimeout = false;
// Whether setFgsRestrictionLocked() is called in here. Only used for logging.
boolean fgsRestrictionRecalculated = false;
+ final int previousFgsType = r.foregroundServiceType;
+
int fgsTypeCheckCode = FGS_TYPE_POLICY_CHECK_UNKNOWN;
if (!ignoreForeground) {
if (foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE
@@ -2321,19 +2328,6 @@
final boolean isOldTypeShortFgsAndTimedOut =
r.shouldTriggerShortFgsTimeout(nowUptime);
- // Calling startForeground on a FGS type which has a time limit will only be
- // allowed if the app is in a state where it can normally start another FGS.
- // The timeout will behave as follows:
- // A) <TIME_LIMITED_TYPE> -> another <TIME_LIMITED_TYPE>
- // - If the start succeeds, the timeout is reset.
- // B) <TIME_LIMITED_TYPE> -> non-time-limited type
- // - If the start succeeds, the timeout will stop.
- // C) non-time-limited type -> <TIME_LIMITED_TYPE>
- // - If the start succeeds, the timeout will start.
- final boolean isOldTypeTimeLimited = r.isFgsTimeLimited();
- final boolean isNewTypeTimeLimited =
- r.canFgsTypeTimeOut(foregroundServiceType);
-
// If true, we skip the BFSL check.
boolean bypassBfslCheck = false;
@@ -2402,7 +2396,11 @@
// "if (r.mAllowStartForeground == REASON_DENIED...)" block below.
}
}
- } else if (r.isForeground && isOldTypeTimeLimited) {
+ } else if (getTimeLimitedFgsType(foregroundServiceType)
+ != ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
+ // Calling startForeground on a FGS type which has a time limit will only be
+ // allowed if the app is in a state where it can normally start another FGS
+ // and it hasn't hit the time limit for that type in the past 24hrs.
// See if the app could start an FGS or not.
r.clearFgsAllowStart();
@@ -2413,20 +2411,37 @@
final boolean fgsStartAllowed = !isBgFgsRestrictionEnabledForService
|| r.isFgsAllowedStart();
-
if (fgsStartAllowed) {
- if (isNewTypeTimeLimited) {
- // Note: in the future, we may want to look into metrics to see if
- // apps are constantly switching between a time-limited type and a
- // non-time-limited type or constantly calling startForeground()
- // opportunistically on the same type to gain runtime and apply the
- // stricter timeout. For now, always extend the timeout if the app
- // is in a state where it's allowed to start a FGS.
- extendFgsTimeout = true;
- } else {
- // FGS type is changing from a time-restricted type to one without
- // a time limit so proceed as normal.
- // The timeout will stop later, in maybeUpdateFgsTrackingLocked().
+ SparseArray<TimeLimitedFgsInfo> fgsInfo =
+ mTimeLimitedFgsInfo.get(r.appInfo.uid);
+ if (fgsInfo == null) {
+ fgsInfo = new SparseArray<>();
+ mTimeLimitedFgsInfo.put(r.appInfo.uid, fgsInfo);
+ }
+ final int timeLimitedFgsType =
+ getTimeLimitedFgsType(foregroundServiceType);
+ final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(timeLimitedFgsType);
+ if (fgsTypeInfo != null) {
+ // TODO(b/330399444): check to see if all time book-keeping for
+ // time limited types should use elapsedRealtime instead of uptime
+ final long before24Hr = Math.max(0,
+ SystemClock.elapsedRealtime() - (24 * 60 * 60 * 1000));
+ final long lastTimeOutAt = fgsTypeInfo.getTimeLimitExceededAt();
+ if (fgsTypeInfo.getFirstFgsStartTime() < before24Hr
+ || (lastTimeOutAt != Long.MIN_VALUE
+ && r.app.mState.getLastTopTime() > lastTimeOutAt)) {
+ // Reset the time limit info for this fgs type if it has been
+ // more than 24hrs since the first fgs start or if the app was
+ // in the TOP state after time limit was exhausted.
+ fgsTypeInfo.reset();
+ } else if (lastTimeOutAt > 0) {
+ // Time limit was exhausted within the past 24 hours and the app
+ // has not been in the TOP state since then, throw an exception.
+ throw new ForegroundServiceStartNotAllowedException("Time limit"
+ + " already exhausted for foreground service type "
+ + ServiceInfo.foregroundServiceTypeToLabel(
+ foregroundServiceType));
+ }
}
} else {
// This case will be handled in the BFSL check below.
@@ -2673,7 +2688,7 @@
mAm.notifyPackageUse(r.serviceInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
- maybeUpdateFgsTrackingLocked(r, extendFgsTimeout);
+ maybeUpdateFgsTrackingLocked(r, previousFgsType);
} else {
if (DEBUG_FOREGROUND_SERVICE) {
Slog.d(TAG, "Suppressing startForeground() for FAS " + r);
@@ -3687,75 +3702,117 @@
}
}
- void onFgsTimeout(ServiceRecord sr) {
- synchronized (mAm) {
- final long nowUptime = SystemClock.uptimeMillis();
- final int fgsType = sr.getTimedOutFgsType(nowUptime);
- if (fgsType == -1) {
- mFGSAnrTimer.discard(sr);
- return;
- }
- Slog.e(TAG_SERVICE, "FGS (" + ServiceInfo.foregroundServiceTypeToLabel(fgsType)
- + ") timed out: " + sr);
- mFGSAnrTimer.accept(sr);
- traceInstant("FGS timed out: ", sr);
-
- logFGSStateChangeLocked(sr,
- FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT,
- nowUptime > sr.mFgsEnterTime ? (int) (nowUptime - sr.mFgsEnterTime) : 0,
- FGS_STOP_REASON_UNKNOWN,
- FGS_TYPE_POLICY_CHECK_UNKNOWN,
- FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA,
- false /* fgsRestrictionRecalculated */
- );
- try {
- sr.app.getThread().scheduleTimeoutServiceForType(sr, sr.getLastStartId(), fgsType);
- } catch (RemoteException e) {
- Slog.w(TAG_SERVICE, "Exception from scheduleTimeoutServiceForType: " + e);
- }
-
- // ANR the service after giving the service some time to clean up.
- // ServiceRecord.getEarliestStopTypeAndTime() is an absolute time with a reference that
- // is not "now". Compute the time from "now" when starting the anr timer.
- final long anrTime = sr.getEarliestStopTypeAndTime().second
- + mAm.mConstants.mFgsAnrExtraWaitDuration - SystemClock.uptimeMillis();
- mFGSAnrTimer.start(sr, anrTime);
+ /**
+ * @return the fgs type for this service which has the most lenient time limit; if none of the
+ * types are time-restricted, return {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE}.
+ */
+ @ServiceInfo.ForegroundServiceType int getTimeLimitedFgsType(int foregroundServiceType) {
+ int fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+ long timeout = 0;
+ if ((foregroundServiceType & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING)
+ == ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING) {
+ fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING;
+ timeout = mAm.mConstants.mMediaProcessingFgsTimeoutDuration;
}
+ if ((foregroundServiceType & ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
+ == ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) {
+ // update the timeout and type if this type has a more lenient time limit
+ if (timeout == 0 || mAm.mConstants.mDataSyncFgsTimeoutDuration > timeout) {
+ fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC;
+ timeout = mAm.mConstants.mDataSyncFgsTimeoutDuration;
+ }
+ }
+ // Add logic for time limits introduced in the future for other fgs types above.
+ return fgsType;
}
- private void maybeUpdateFgsTrackingLocked(ServiceRecord sr, boolean extendTimeout) {
- if (!sr.isFgsTimeLimited()) {
- // Reset timers if they exist.
- sr.setIsFgsTimeLimited(false);
- mFGSAnrTimer.cancel(sr);
- mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+ private void maybeUpdateFgsTrackingLocked(ServiceRecord sr, int previousFgsType) {
+ final int previouslyTimeLimitedType = getTimeLimitedFgsType(previousFgsType);
+ if (previouslyTimeLimitedType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE
+ && !sr.isFgsTimeLimited()) {
+ // FGS was not previously time-limited and new type isn't either.
return;
}
- if (extendTimeout || !sr.wasFgsPreviouslyTimeLimited()) {
- traceInstant("FGS start: ", sr);
- sr.setIsFgsTimeLimited(true);
+ if (previouslyTimeLimitedType != ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
+ // FGS is switching types and the previous type was time-limited so update the runtime.
+ final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid);
+ if (fgsInfo != null) {
+ final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(previouslyTimeLimitedType);
+ if (fgsTypeInfo != null) {
+ // Update the total runtime for the previous time-limited fgs type.
+ fgsTypeInfo.updateTotalRuntime();
+ // TODO(b/330399444): handle the case where an app is running 2 services of the
+ // same time-limited type in parallel and stops one of them which leads to the
+ // second running one gaining additional runtime.
+ }
+ }
- // We'll restart the timeout.
- mFGSAnrTimer.cancel(sr);
- mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
-
- final Message msg = mAm.mHandler.obtainMessage(
- ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
- mAm.mHandler.sendMessageAtTime(msg, sr.getEarliestStopTypeAndTime().second);
+ if (!sr.isFgsTimeLimited()) {
+ // Reset timers since new type does not have a timeout.
+ mFGSAnrTimer.cancel(sr);
+ mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+ return;
+ }
}
+
+ traceInstant("FGS start: ", sr);
+ final long nowUptime = SystemClock.uptimeMillis();
+
+ // Fetch/create/update the fgs info for the time-limited type.
+ SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid);
+ if (fgsInfo == null) {
+ fgsInfo = new SparseArray<>();
+ mTimeLimitedFgsInfo.put(sr.appInfo.uid, fgsInfo);
+ }
+ final int timeLimitedFgsType = getTimeLimitedFgsType(sr.foregroundServiceType);
+ TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(timeLimitedFgsType);
+ if (fgsTypeInfo == null) {
+ fgsTypeInfo = sr.createTimeLimitedFgsInfo(nowUptime);
+ fgsInfo.put(timeLimitedFgsType, fgsTypeInfo);
+ }
+ fgsTypeInfo.setLastFgsStartTime(nowUptime);
+
+ // We'll cancel the previous ANR timer and start a fresh one below.
+ mFGSAnrTimer.cancel(sr);
+ mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+
+ final Message msg = mAm.mHandler.obtainMessage(
+ ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+ final long timeoutCallbackTime = sr.getNextFgsStopTime(timeLimitedFgsType, fgsTypeInfo);
+ if (timeoutCallbackTime == Long.MAX_VALUE) {
+ // This should never happen since we only get to this point if the service record's
+ // foregroundServiceType attribute contains a type that can be timed-out.
+ Slog.wtf(TAG, "Couldn't calculate timeout for time-limited fgs: " + sr);
+ return;
+ }
+ mAm.mHandler.sendMessageAtTime(msg, timeoutCallbackTime);
}
private void maybeStopFgsTimeoutLocked(ServiceRecord sr) {
- sr.setIsFgsTimeLimited(false); // reset fgs boolean holding time-limited type state.
- if (!sr.isFgsTimeLimited()) {
- return; // if none of the types are time-limited, return.
+ final int timeLimitedType = getTimeLimitedFgsType(sr.foregroundServiceType);
+ if (timeLimitedType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
+ return; // if the current fgs type is not time-limited, return.
+ }
+
+ final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid);
+ if (fgsInfo != null) {
+ final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(timeLimitedType);
+ if (fgsTypeInfo != null) {
+ // Update the total runtime for the previous time-limited fgs type.
+ fgsTypeInfo.updateTotalRuntime();
+ }
}
Slog.d(TAG_SERVICE, "Stop FGS timeout: " + sr);
mFGSAnrTimer.cancel(sr);
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
}
+ void onUidRemovedLocked(int uid) {
+ // Remove all time-limited fgs tracking info stored for this uid.
+ mTimeLimitedFgsInfo.delete(uid);
+ }
+
boolean hasServiceTimedOutLocked(ComponentName className, IBinder token) {
final int userId = UserHandle.getCallingUserId();
final long ident = mAm.mInjector.clearCallingIdentity();
@@ -3764,25 +3821,67 @@
if (sr == null) {
return false;
}
- final long nowUptime = SystemClock.uptimeMillis();
- return sr.getTimedOutFgsType(nowUptime) != -1;
+ return getTimeLimitedFgsType(sr.foregroundServiceType)
+ != ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
} finally {
mAm.mInjector.restoreCallingIdentity(ident);
}
}
+ void onFgsTimeout(ServiceRecord sr) {
+ synchronized (mAm) {
+ final int fgsType = getTimeLimitedFgsType(sr.foregroundServiceType);
+ if (fgsType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
+ mFGSAnrTimer.discard(sr);
+ return;
+ }
+ Slog.e(TAG_SERVICE, "FGS (" + ServiceInfo.foregroundServiceTypeToLabel(fgsType)
+ + ") timed out: " + sr);
+ mFGSAnrTimer.accept(sr);
+ traceInstant("FGS timed out: ", sr);
+
+ final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid);
+ if (fgsInfo != null) {
+ final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(fgsType);
+ if (fgsTypeInfo != null) {
+ // Update total runtime for the time-limited fgs type and mark it as timed out.
+ final long nowUptime = SystemClock.uptimeMillis();
+ fgsTypeInfo.updateTotalRuntime();
+ fgsTypeInfo.setTimeLimitExceededAt(nowUptime);
+
+ logFGSStateChangeLocked(sr,
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT,
+ nowUptime > fgsTypeInfo.getLastFgsStartTime()
+ ? (int) (nowUptime - fgsTypeInfo.getLastFgsStartTime()) : 0,
+ FGS_STOP_REASON_UNKNOWN,
+ FGS_TYPE_POLICY_CHECK_UNKNOWN,
+ FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA,
+ false /* fgsRestrictionRecalculated */
+ );
+ }
+ }
+
+ try {
+ sr.app.getThread().scheduleTimeoutServiceForType(sr, sr.getLastStartId(), fgsType);
+ } catch (RemoteException e) {
+ Slog.w(TAG_SERVICE, "Exception from scheduleTimeoutServiceForType: " + e);
+ }
+
+ // ANR the service after giving the service some time to clean up.
+ mFGSAnrTimer.start(sr, mAm.mConstants.mFgsAnrExtraWaitDuration);
+ }
+ }
+
void onFgsAnrTimeout(ServiceRecord sr) {
- final long nowUptime = SystemClock.uptimeMillis();
- final int fgsType = sr.getTimedOutFgsType(nowUptime);
- if (fgsType == -1 || !sr.wasFgsPreviouslyTimeLimited()) {
- return; // no timed out FGS type was found
+ final int fgsType = getTimeLimitedFgsType(sr.foregroundServiceType);
+ if (fgsType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
+ return; // no timed out FGS type was found (either it was stopped or it switched types)
}
final String reason = "A foreground service of type "
+ ServiceInfo.foregroundServiceTypeToLabel(fgsType)
- + " did not stop within a timeout: " + sr.getComponentName();
+ + " did not stop within its timeout: " + sr.getComponentName();
final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason);
-
tr.mLatencyTracker.waitingOnAMSLockStarted();
synchronized (mAm) {
tr.mLatencyTracker.waitingOnAMSLockEnded();
@@ -5725,14 +5824,11 @@
bringDownServiceLocked(r, enqueueOomAdj);
return msg;
}
- mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
- true);
+ mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app,
+ r);
if (isolated) {
r.isolationHostProc = app;
}
- } else {
- mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
- false);
}
if (r.fgRequired) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8022eb3..6612319c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -37,6 +37,9 @@
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED;
+import static android.app.ActivityManager.RESTRICTION_REASON_DEFAULT;
+import static android.app.ActivityManager.RESTRICTION_REASON_USAGE;
import static android.app.ActivityManager.StopUserOnSwitch;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
@@ -497,6 +500,8 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
@@ -683,8 +688,6 @@
public final IntentFirewall mIntentFirewall;
- public OomAdjProfiler mOomAdjProfiler = new OomAdjProfiler();
-
/**
* The global lock for AMS, it's de-facto the ActivityManagerService object as of now.
*/
@@ -2596,7 +2599,6 @@
BackgroundThread.getHandler(), this);
mOnBattery = DEBUG_POWER ? true
: mBatteryStatsService.getActiveStatistics().getIsOnBattery();
- mOomAdjProfiler.batteryPowerChanged(mOnBattery);
mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
@@ -2845,13 +2847,12 @@
updateCpuStatsNow();
synchronized (mProcLock) {
mOnBattery = DEBUG_POWER ? true : onBattery;
- mOomAdjProfiler.batteryPowerChanged(onBattery);
}
}
@Override
public void batteryStatsReset() {
- mOomAdjProfiler.reset();
+ // Empty for now.
}
@Override
@@ -4437,8 +4438,16 @@
final boolean clearPendingIntentsForStoppedApp = (android.content.pm.Flags.stayStopped()
&& packageStateStopped);
if (packageName == null || uninstalling || clearPendingIntentsForStoppedApp) {
+ final int cancelReason;
+ if (packageName == null) {
+ cancelReason = PendingIntentRecord.CANCEL_REASON_USER_STOPPED;
+ } else if (uninstalling) {
+ cancelReason = PendingIntentRecord.CANCEL_REASON_OWNER_UNINSTALLED;
+ } else {
+ cancelReason = PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED;
+ }
didSomething |= mPendingIntentController.removePendingIntentsForPackage(
- packageName, userId, appId, doit);
+ packageName, userId, appId, doit, cancelReason);
}
if (doit) {
@@ -5099,10 +5108,19 @@
} // else, stopped packages in private space may still hit the logic below
}
}
+
+ final boolean wasForceStopped = app.wasForceStopped()
+ || app.getWindowProcessController().wasForceStopped();
+ if (android.app.Flags.appRestrictionsApi() && wasForceStopped) {
+ noteAppRestrictionEnabled(app.info.packageName, app.uid,
+ RESTRICTION_LEVEL_FORCE_STOPPED, false,
+ RESTRICTION_REASON_USAGE, "unknown", 0L);
+ }
+
if (!sendBroadcast) {
if (!android.content.pm.Flags.stayStopped()) return;
// Nothing to do if it wasn't previously stopped
- if (!app.wasForceStopped() && !app.getWindowProcessController().wasForceStopped()) {
+ if (!wasForceStopped) {
return;
}
}
@@ -5407,14 +5425,12 @@
}
}
- /**
- * Checks if feature flag is enabled and if system is Headless (HSUM), case in which
- * home delay should be skipped.
- *
- * @hide
- */
- public boolean isHomeLaunchDelayable() {
- return !UserManager.isHeadlessSystemUserMode() && enableHomeDelay();
+ /** Checks whether the home launch delay feature is enabled. */
+ private boolean isHomeLaunchDelayable() {
+ // This feature is disabled on Auto since it seems to add an unacceptably long boot delay
+ // without even solving the underlying issue (it merely hits the timeout).
+ return enableHomeDelay() &&
+ !mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
}
final void ensureBootCompleted() {
@@ -7411,7 +7427,6 @@
mServices.updateScreenStateLocked(isAwake);
reportCurWakefulnessUsageEvent();
mActivityTaskManager.onScreenAwakeChanged(isAwake);
- mOomAdjProfiler.onWakefulnessChanged(wakefulness);
mOomAdjuster.onWakefulnessChanged(wakefulness);
updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY);
@@ -8943,8 +8958,10 @@
com.android.internal.R.integer.config_multiuserMaxRunningUsers);
final boolean delayUserDataLocking = res.getBoolean(
com.android.internal.R.bool.config_multiuserDelayUserDataLocking);
+ final int backgroundUserScheduledStopTimeSecs = res.getInteger(
+ com.android.internal.R.integer.config_backgroundUserScheduledStopTimeSecs);
mUserController.setInitialConfig(userSwitchUiEnabled, maxRunningUsers,
- delayUserDataLocking);
+ delayUserDataLocking, backgroundUserScheduledStopTimeSecs);
}
mAppErrors.loadAppsNotReportingCrashesFromConfig(res.getString(
com.android.internal.R.string.config_appsNotReportingCrashes));
@@ -9181,6 +9198,11 @@
private class MyBinderProxyCountEventListener implements BinderProxyCountEventListener {
@Override
public void onLimitReached(int uid) {
+ // Spawn a new thread for the dump as it'll take long time.
+ new Thread(() -> handleLimitReached(uid), "BinderProxy Dump: " + uid).start();
+ }
+
+ private void handleLimitReached(int uid) {
Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid "
+ Process.myUid());
BinderProxy.dumpProxyDebugInfo();
@@ -9834,6 +9856,11 @@
sb.append("Process-Runtime: ").append(runtimeMillis).append("\n");
}
}
+ if (eventType.equals("crash")) {
+ String formattedTime = DROPBOX_TIME_FORMATTER.format(
+ Instant.now().atZone(ZoneId.systemDefault()));
+ sb.append("Timestamp: ").append(formattedTime).append("\n");
+ }
if (activityShortComponentName != null) {
sb.append("Activity: ").append(activityShortComponentName).append("\n");
}
@@ -10167,7 +10194,11 @@
}
final int callingUid = Binder.getCallingUid();
- mProcessList.getAppStartInfoTracker().addStartInfoCompleteListener(listener, callingUid);
+ mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, true,
+ ALLOW_NON_FULL, "addApplicationStartInfoCompleteListener", null);
+
+ mProcessList.getAppStartInfoTracker().addStartInfoCompleteListener(listener,
+ UserHandle.getUid(userId, UserHandle.getAppId(callingUid)));
}
@@ -10182,13 +10213,30 @@
}
final int callingUid = Binder.getCallingUid();
- mProcessList.getAppStartInfoTracker().removeStartInfoCompleteListener(listener, callingUid,
- true);
+ mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, true,
+ ALLOW_NON_FULL, "removeApplicationStartInfoCompleteListener", null);
+
+ mProcessList.getAppStartInfoTracker().removeStartInfoCompleteListener(listener,
+ UserHandle.getUid(userId, UserHandle.getAppId(callingUid)), true);
}
@Override
public void addStartInfoTimestamp(int key, long timestampNs, int userId) {
enforceNotIsolatedCaller("addStartInfoTimestamp");
+
+ // For the simplification, we don't support USER_ALL nor USER_CURRENT here.
+ if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) {
+ throw new IllegalArgumentException("Unsupported userId");
+ }
+
+ final int callingUid = Binder.getCallingUid();
+ mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, true,
+ ALLOW_NON_FULL, "addStartInfoTimestamp", null);
+
+ final String packageName = Settings.getPackageNameForUid(mContext, callingUid);
+
+ mProcessList.getAppStartInfoTracker().addTimestampToStart(packageName,
+ UserHandle.getUid(userId, UserHandle.getAppId(callingUid)), timestampNs, key);
}
@Override
@@ -10510,12 +10558,6 @@
pw.println(
"-------------------------------------------------------------------------------");
}
- mOomAdjProfiler.dump(pw);
- pw.println();
- if (dumpAll) {
- pw.println(
- "-------------------------------------------------------------------------------");
- }
dumpLmkLocked(pw);
}
pw.println();
@@ -14299,6 +14341,20 @@
int newBackupUid;
synchronized(this) {
+ if (android.app.Flags.appRestrictionsApi()) {
+ try {
+ final boolean wasStopped = mPackageManagerInt.isPackageStopped(app.packageName,
+ UserHandle.getUserId(app.uid));
+ if (wasStopped) {
+ noteAppRestrictionEnabled(app.packageName, app.uid,
+ RESTRICTION_LEVEL_FORCE_STOPPED, false,
+ RESTRICTION_REASON_DEFAULT, "restore", 0L);
+ }
+ } catch (NameNotFoundException e) {
+ Slog.w(TAG, "No such package", e);
+ }
+ }
+
// !!! TODO: currently no check here that we're already bound
// Backup agent is now in use, its package can't be stopped.
try {
@@ -15446,6 +15502,7 @@
intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
} else {
mAppOpsService.uidRemoved(uid);
+ mServices.onUidRemovedLocked(uid);
}
}
break;
@@ -20084,6 +20141,34 @@
}
/**
+ * Log the reason for changing an app restriction. Purely used for logging purposes and does not
+ * cause any change to app state.
+ *
+ * @see ActivityManager#noteAppRestrictionEnabled(String, int, int, boolean, int, String, long)
+ */
+ @Override
+ public void noteAppRestrictionEnabled(String packageName, int uid,
+ @RestrictionLevel int restrictionType, boolean enabled,
+ @ActivityManager.RestrictionReason int reason, String subReason, long threshold) {
+ if (!android.app.Flags.appRestrictionsApi()) return;
+
+ enforceCallingPermission(android.Manifest.permission.DEVICE_POWER,
+ "noteAppRestrictionEnabled()");
+
+ final int userId = UserHandle.getCallingUserId();
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ if (uid == -1) {
+ uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
+ }
+ mAppRestrictionController.noteAppRestrictionEnabled(packageName, uid, restrictionType,
+ enabled, reason, subReason, threshold);
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ /**
* Get an app's background restriction level.
* This interface is intended for the shell command to use.
*/
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 372ec45..bf4f34fd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -174,6 +174,8 @@
private static final DateTimeFormatter LOG_NAME_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss", Locale.ROOT);
+ private static final String PROFILER_OUTPUT_VERSION_FLAG = "--profiler-output-version";
+
// IPC interface to activity manager -- don't need to do additional security checks.
final IActivityManager mInterface;
final IActivityTaskManager mTaskInterface;
@@ -199,6 +201,7 @@
private String mAgent; // Agent to attach on startup.
private boolean mAttachAgentDuringBind; // Whether agent should be attached late.
private int mClockType; // Whether we need thread cpu / wall clock / both.
+ private int mProfilerOutputVersion; // The version of the profiler output.
private int mDisplayId;
private int mTaskDisplayAreaFeatureId;
private int mWindowingMode;
@@ -527,6 +530,8 @@
} else if (opt.equals("--clock-type")) {
String clock_type = getNextArgRequired();
mClockType = ProfilerInfo.getClockTypeFromString(clock_type);
+ } else if (opt.equals(PROFILER_OUTPUT_VERSION_FLAG)) {
+ mProfilerOutputVersion = Integer.parseInt(getNextArgRequired());
} else if (opt.equals("--streaming")) {
mStreaming = true;
} else if (opt.equals("--attach-agent")) {
@@ -579,7 +584,7 @@
} else if (opt.equals("--splashscreen-show-icon")) {
mShowSplashScreen = true;
} else if (opt.equals("--dismiss-keyguard-if-insecure")
- || opt.equals("--dismiss-keyguard")) {
+ || opt.equals("--dismiss-keyguard")) {
mDismissKeyguardIfInsecure = true;
} else if (opt.equals("--allow-fgs-start-reason")) {
final int reasonCode = Integer.parseInt(getNextArgRequired());
@@ -692,8 +697,9 @@
return 1;
}
}
- profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop,
- mStreaming, mAgent, mAttachAgentDuringBind, mClockType);
+ profilerInfo =
+ new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop, mStreaming,
+ mAgent, mAttachAgentDuringBind, mClockType, mProfilerOutputVersion);
}
pw.println("Starting: " + intent);
@@ -1036,6 +1042,7 @@
mSamplingInterval = 0;
mStreaming = false;
mClockType = ProfilerInfo.CLOCK_TYPE_DEFAULT;
+ mProfilerOutputVersion = ProfilerInfo.OUTPUT_VERSION_DEFAULT;
String process = null;
@@ -1050,6 +1057,8 @@
} else if (opt.equals("--clock-type")) {
String clock_type = getNextArgRequired();
mClockType = ProfilerInfo.getClockTypeFromString(clock_type);
+ } else if (opt.equals(PROFILER_OUTPUT_VERSION_FLAG)) {
+ mProfilerOutputVersion = Integer.parseInt(getNextArgRequired());
} else if (opt.equals("--streaming")) {
mStreaming = true;
} else if (opt.equals("--sampling")) {
@@ -1097,7 +1106,7 @@
return -1;
}
profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false, mStreaming,
- null, false, mClockType);
+ null, false, mClockType, mProfilerOutputVersion);
}
if (!mInterface.profileControl(process, userId, start, profilerInfo, profileType)) {
@@ -4006,8 +4015,12 @@
return ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
case "background_restricted":
return ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
- case "hibernation":
- return ActivityManager.RESTRICTION_LEVEL_HIBERNATION;
+ case "force_stopped":
+ return ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED;
+ case "user_launch_only":
+ return ActivityManager.RESTRICTION_LEVEL_USER_LAUNCH_ONLY;
+ case "custom":
+ return ActivityManager.RESTRICTION_LEVEL_CUSTOM;
default:
return ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
}
@@ -4192,6 +4205,7 @@
pw.println(" Print this help text.");
pw.println(" start-activity [-D] [-N] [-W] [-P <FILE>] [--start-profiler <FILE>]");
pw.println(" [--sampling INTERVAL] [--clock-type <TYPE>] [--streaming]");
+ pw.println(" [" + PROFILER_OUTPUT_VERSION_FLAG + " NUMBER]");
pw.println(" [-R COUNT] [-S] [--track-allocation]");
pw.println(" [--user <USER_ID> | current] [--suspend] <INTENT>");
pw.println(" Start an Activity. Options are:");
@@ -4207,6 +4221,8 @@
pw.println(" The default value is dual. (use with --start-profiler)");
pw.println(" --streaming: stream the profiling output to the specified file");
pw.println(" (use with --start-profiler)");
+ pw.println(" " + PROFILER_OUTPUT_VERSION_FLAG + " Specify the version of the");
+ pw.println(" profiling output (use with --start-profiler)");
pw.println(" -P <FILE>: like above, but profiling stops when app goes idle");
pw.println(" --attach-agent <agent>: attach the given agent before binding");
pw.println(" --attach-agent-bind <agent>: attach the given agent during binding");
@@ -4298,6 +4314,7 @@
pw.println(" --dump-file <FILE>: Specify the file the trace should be dumped to.");
pw.println(" profile start [--user <USER_ID> current]");
pw.println(" [--clock-type <TYPE>]");
+ pw.println(" [" + PROFILER_OUTPUT_VERSION_FLAG + " VERSION]");
pw.println(" [--sampling INTERVAL | --streaming] <PROCESS> <FILE>");
pw.println(" Start profiler on a process. The given <PROCESS> argument");
pw.println(" may be either a process name or pid. Options are:");
@@ -4307,6 +4324,8 @@
pw.println(" --clock-type <TYPE>: use the specified clock to report timestamps.");
pw.println(" The type can be one of wall | thread-cpu | dual. The default");
pw.println(" value is dual.");
+ pw.println(" " + PROFILER_OUTPUT_VERSION_FLAG + "VERSION: specifies the output");
+ pw.println(" format version");
pw.println(" --sampling INTERVAL: use sample profiling with INTERVAL microseconds");
pw.println(" between samples.");
pw.println(" --streaming: stream the profiling output to the specified file.");
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 6c16fba0..dda48ad 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -2414,8 +2414,8 @@
}
}
} else if (instr != null && instr.mProfileFile != null) {
- profilerInfo = new ProfilerInfo(instr.mProfileFile, null, 0, false, false,
- null, false, 0);
+ profilerInfo = new ProfilerInfo(instr.mProfileFile, null, 0, false, false, null,
+ false, 0, ProfilerInfo.OUTPUT_VERSION_DEFAULT);
}
if (mAppAgentMap != null && mAppAgentMap.containsKey(processName)) {
// We need to do a debuggable check here. See setAgentApp for why the check is
@@ -2425,7 +2425,8 @@
// Do not overwrite already requested agent.
if (profilerInfo == null) {
profilerInfo = new ProfilerInfo(null, null, 0, false, false,
- mAppAgentMap.get(processName), true, 0);
+ mAppAgentMap.get(processName), true, 0,
+ ProfilerInfo.OUTPUT_VERSION_DEFAULT);
} else if (profilerInfo.agent == null) {
profilerInfo = profilerInfo.setAgent(mAppAgentMap.get(processName), true);
}
@@ -2552,14 +2553,16 @@
if (mProfileData.getProfilerInfo() != null) {
pw.println(" mProfileFile=" + mProfileData.getProfilerInfo().profileFile
+ " mProfileFd=" + mProfileData.getProfilerInfo().profileFd);
- pw.println(" mSamplingInterval="
- + mProfileData.getProfilerInfo().samplingInterval
+ pw.println(
+ " mSamplingInterval=" + mProfileData.getProfilerInfo().samplingInterval
+ " mAutoStopProfiler="
+ mProfileData.getProfilerInfo().autoStopProfiler
+ " mStreamingOutput="
+ mProfileData.getProfilerInfo().streamingOutput
+ " mClockType="
- + mProfileData.getProfilerInfo().clockType);
+ + mProfileData.getProfilerInfo().clockType
+ + " mProfilerOutputVersion="
+ + mProfileData.getProfilerInfo().profilerOutputVersion);
pw.println(" mProfileType=" + mProfileType);
}
}
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index ef015ee..c5cad14 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -23,11 +23,20 @@
import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED;
-import static android.app.ActivityManager.RESTRICTION_LEVEL_HIBERNATION;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED;
import static android.app.ActivityManager.RESTRICTION_LEVEL_MAX;
import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
import static android.app.ActivityManager.RESTRICTION_LEVEL_UNRESTRICTED;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_USER_LAUNCH_ONLY;
+import static android.app.ActivityManager.RESTRICTION_REASON_DEFAULT;
+import static android.app.ActivityManager.RESTRICTION_REASON_DORMANT;
+import static android.app.ActivityManager.RESTRICTION_REASON_REMOTE_TRIGGER;
+import static android.app.ActivityManager.RESTRICTION_REASON_SYSTEM_HEALTH;
+import static android.app.ActivityManager.RESTRICTION_REASON_USAGE;
+import static android.app.ActivityManager.RESTRICTION_REASON_USER;
+import static android.app.ActivityManager.RESTRICTION_REASON_USER_NUDGED;
+import static android.app.ActivityManager.RESTRICTION_SUBREASON_MAX_LENGTH;
import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
import static android.app.ActivityManager.UID_OBSERVER_GONE;
import static android.app.ActivityManager.UID_OBSERVER_IDLE;
@@ -93,6 +102,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManager.RestrictionLevel;
+import android.app.ActivityManager.RestrictionReason;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener;
import android.app.AppOpsManager;
@@ -334,6 +344,8 @@
final ActivityManagerService mActivityManagerService;
+ private volatile boolean mLockedBootCompleted = false;
+
static final int TRACKER_TYPE_UNKNOWN = 0;
static final int TRACKER_TYPE_BATTERY = 1;
static final int TRACKER_TYPE_BATTERY_EXEMPTION = 2;
@@ -342,6 +354,7 @@
static final int TRACKER_TYPE_PERMISSION = 5;
static final int TRACKER_TYPE_BROADCAST_EVENTS = 6;
static final int TRACKER_TYPE_BIND_SERVICE_EVENTS = 7;
+
@IntDef(prefix = { "TRACKER_TYPE_" }, value = {
TRACKER_TYPE_UNKNOWN,
TRACKER_TYPE_BATTERY,
@@ -1712,7 +1725,7 @@
String packageName, @UsageStatsManager.StandbyBuckets int standbyBucket,
boolean allowRequestBgRestricted, boolean calcTrackers) {
if (mInjector.getAppHibernationInternal().isHibernatingForUser(packageName, userId)) {
- return new Pair<>(RESTRICTION_LEVEL_HIBERNATION, mEmptyTrackerInfo);
+ return new Pair<>(RESTRICTION_LEVEL_FORCE_STOPPED, mEmptyTrackerInfo);
}
@RestrictionLevel int level;
TrackerInfo trackerInfo = null;
@@ -1721,8 +1734,10 @@
level = RESTRICTION_LEVEL_EXEMPTED;
break;
case STANDBY_BUCKET_NEVER:
- level = RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
- break;
+ if (!android.app.Flags.appRestrictionsApi()) {
+ level = RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+ break;
+ }
case STANDBY_BUCKET_ACTIVE:
case STANDBY_BUCKET_WORKING_SET:
case STANDBY_BUCKET_FREQUENT:
@@ -1802,7 +1817,9 @@
case STANDBY_BUCKET_EXEMPTED:
return RESTRICTION_LEVEL_EXEMPTED;
case STANDBY_BUCKET_NEVER:
- return RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+ if (!android.app.Flags.appRestrictionsApi()) {
+ return RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+ }
case STANDBY_BUCKET_ACTIVE:
case STANDBY_BUCKET_WORKING_SET:
case STANDBY_BUCKET_FREQUENT:
@@ -2028,7 +2045,7 @@
return AppBackgroundRestrictionsInfo.LEVEL_RESTRICTED_BUCKET;
case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
return AppBackgroundRestrictionsInfo.LEVEL_BACKGROUND_RESTRICTED;
- case RESTRICTION_LEVEL_HIBERNATION:
+ case RESTRICTION_LEVEL_FORCE_STOPPED:
return AppBackgroundRestrictionsInfo.LEVEL_HIBERNATION;
default:
return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN;
@@ -2214,7 +2231,8 @@
}
}
- if (doItNow && android.app.Flags.appRestrictionsApi()) {
+ if (doItNow && android.app.Flags.appRestrictionsApi()
+ && curLevel != RESTRICTION_LEVEL_UNKNOWN) {
logAppBackgroundRestrictionInfo(pkgName, uid, curLevel, level, trackerInfo,
reason);
}
@@ -2308,6 +2326,9 @@
private void handleAppStandbyBucketChanged(int bucket, String packageName,
@UserIdInt int userId) {
+ // Ignore spurious changes to standby bucket during early boot
+ if (android.app.Flags.appRestrictionsApi() && !mLockedBootCompleted) return;
+
final int uid = mInjector.getPackageManagerInternal().getPackageUid(
packageName, STOCK_PM_FLAGS, userId);
final Pair<Integer, TrackerInfo> levelTypePair = calcAppRestrictionLevel(
@@ -2344,6 +2365,85 @@
}
}
+ /**
+ * Log a change in restriction state with a reason and threshold.
+ * @param packageName
+ * @param uid
+ * @param restrictionType
+ * @param enabled
+ * @param reason
+ * @param subReason Eg: settings, cli, long_wakelock, crash, binder_spam, cpu, threads
+ * Length should not exceed RESTRICTON_SUBREASON_MAX_LENGTH
+ * @param threshold
+ */
+ public void noteAppRestrictionEnabled(String packageName, int uid,
+ @RestrictionLevel int restrictionType, boolean enabled,
+ @RestrictionReason int reason, String subReason, long threshold) {
+ if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+ Slog.i(TAG, (enabled ? "restricted " : "unrestricted ") + packageName + " to "
+ + restrictionType + " reason=" + reason + ", subReason=" + subReason
+ + ", threshold=" + threshold);
+ }
+
+ // Limit the length of the free-form subReason string
+ if (subReason != null && subReason.length() > RESTRICTION_SUBREASON_MAX_LENGTH) {
+ subReason = subReason.substring(0, RESTRICTION_SUBREASON_MAX_LENGTH);
+ Slog.e(TAG, "Subreason is too long, truncating: " + subReason);
+ }
+
+ // Log the restriction reason
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED, uid,
+ getRestrictionTypeStatsd(restrictionType),
+ enabled,
+ getRestrictionChangeReasonStatsd(reason, subReason),
+ subReason,
+ threshold);
+ }
+
+ private int getRestrictionTypeStatsd(@RestrictionLevel int level) {
+ return switch (level) {
+ case RESTRICTION_LEVEL_UNKNOWN ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_UNKNOWN;
+ case RESTRICTION_LEVEL_UNRESTRICTED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_UNRESTRICTED;
+ case RESTRICTION_LEVEL_EXEMPTED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_EXEMPTED;
+ case RESTRICTION_LEVEL_ADAPTIVE_BUCKET ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_ADAPTIVE;
+ case RESTRICTION_LEVEL_RESTRICTED_BUCKET ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_RESTRICTED_BUCKET;
+ case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_BACKGROUND_RESTRICTED;
+ case RESTRICTION_LEVEL_FORCE_STOPPED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_FORCE_STOPPED;
+ case RESTRICTION_LEVEL_USER_LAUNCH_ONLY ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_USER_LAUNCH_ONLY;
+ default ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_CUSTOM;
+ };
+ }
+
+ private int getRestrictionChangeReasonStatsd(int reason, String subReason) {
+ return switch (reason) {
+ case RESTRICTION_REASON_DEFAULT ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_DEFAULT;
+ case RESTRICTION_REASON_DORMANT ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_DORMANT;
+ case RESTRICTION_REASON_USAGE ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USAGE;
+ case RESTRICTION_REASON_USER ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USER;
+ case RESTRICTION_REASON_USER_NUDGED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USER_NUDGED;
+ case RESTRICTION_REASON_SYSTEM_HEALTH ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_SYSTEM_HEALTH;
+ case RESTRICTION_REASON_REMOTE_TRIGGER ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_REMOTE_TRIGGER;
+ default ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_OTHER;
+ };
+ }
+
static class NotificationHelper {
static final String PACKAGE_SCHEME = "package";
static final String GROUP_KEY = "com.android.app.abusive_bg_apps";
@@ -3391,6 +3491,7 @@
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
mAppStateTrackers.get(i).onLockedBootCompleted();
}
+ mLockedBootCompleted = true;
}
boolean isBgAutoRestrictedBucketFeatureFlagEnabled() {
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index ddf1d5f..2be1fe2 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -196,6 +196,8 @@
start.setIntent(intent);
start.setStartType(ApplicationStartInfo.START_TYPE_UNSET);
start.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_LAUNCH, timestampNanos);
+
+ // TODO: handle possible alarm activity start.
if (intent != null && intent.getCategories() != null
&& intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
@@ -313,7 +315,7 @@
}
public void handleProcessServiceStart(long startTimeNs, ProcessRecord app,
- ServiceRecord serviceRecord, boolean cold) {
+ ServiceRecord serviceRecord) {
synchronized (mLock) {
if (!mEnabled) {
return;
@@ -323,8 +325,9 @@
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
- : ApplicationStartInfo.START_TYPE_WARM);
+ start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
+
+ // TODO: handle possible alarm service start.
start.setReason(serviceRecord.permission != null
&& serviceRecord.permission.contains("android.permission.BIND_JOB_SERVICE")
? ApplicationStartInfo.START_REASON_JOB
@@ -336,8 +339,9 @@
}
}
- public void handleProcessBroadcastStart(long startTimeNs, ProcessRecord app,
- BroadcastRecord broadcast, boolean cold) {
+ /** Process a broadcast triggered app start. */
+ public void handleProcessBroadcastStart(long startTimeNs, ProcessRecord app, Intent intent,
+ boolean isAlarm) {
synchronized (mLock) {
if (!mEnabled) {
return;
@@ -347,26 +351,19 @@
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
- : ApplicationStartInfo.START_TYPE_WARM);
- if (broadcast == null) {
- start.setReason(ApplicationStartInfo.START_REASON_BROADCAST);
- } else if (broadcast.alarm) {
+ start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
+ if (isAlarm) {
start.setReason(ApplicationStartInfo.START_REASON_ALARM);
- } else if (broadcast.pushMessage || broadcast.pushMessageOverQuota) {
- start.setReason(ApplicationStartInfo.START_REASON_PUSH);
- } else if (Intent.ACTION_BOOT_COMPLETED.equals(broadcast.intent.getAction())) {
- start.setReason(ApplicationStartInfo.START_REASON_BOOT_COMPLETE);
} else {
start.setReason(ApplicationStartInfo.START_REASON_BROADCAST);
}
- start.setIntent(broadcast != null ? broadcast.intent : null);
+ start.setIntent(intent);
addStartInfoLocked(start);
}
}
- public void handleProcessContentProviderStart(long startTimeNs, ProcessRecord app,
- boolean cold) {
+ /** Process a content provider triggered app start. */
+ public void handleProcessContentProviderStart(long startTimeNs, ProcessRecord app) {
synchronized (mLock) {
if (!mEnabled) {
return;
@@ -376,8 +373,7 @@
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
- : ApplicationStartInfo.START_TYPE_WARM);
+ start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
start.setReason(ApplicationStartInfo.START_REASON_CONTENT_PROVIDER);
addStartInfoLocked(start);
}
@@ -464,7 +460,7 @@
addTimestampToStart(app.info.packageName, app.uid, timeNs, key);
}
- private void addTimestampToStart(String packageName, int uid, long timeNs, int key) {
+ void addTimestampToStart(String packageName, int uid, long timeNs, int key) {
synchronized (mLock) {
AppStartInfoContainer container = mData.get(packageName, uid);
if (container == null) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index c082889..48dd039 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1030,6 +1030,10 @@
"startProcessLocked failed");
return true;
}
+ // TODO: b/335420031 - cache receiver intent to avoid multiple calls to getReceiverIntent.
+ mService.mProcessList.getAppStartInfoTracker().handleProcessBroadcastStart(
+ SystemClock.elapsedRealtimeNanos(), queue.app, r.getReceiverIntent(receiver),
+ r.alarm /* isAlarm */);
return false;
}
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index f76bf37..28fd197 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -179,6 +179,7 @@
final int expectedUserId = userId;
synchronized (mService) {
long startTime = SystemClock.uptimeMillis();
+ long startElapsedTimeNs = SystemClock.elapsedRealtimeNanos();
ProcessRecord r = null;
if (caller != null) {
@@ -585,6 +586,8 @@
callingProcessState, ActivityManager.PROCESS_STATE_NONEXISTENT,
firstLaunch,
0L /* TODO: stoppedDuration */);
+ mService.mProcessList.getAppStartInfoTracker()
+ .handleProcessContentProviderStart(startElapsedTimeNs, proc);
}
cpr.launchingApp = proc;
mLaunchingProviders.add(cpr);
diff --git a/services/core/java/com/android/server/am/OomAdjProfiler.java b/services/core/java/com/android/server/am/OomAdjProfiler.java
deleted file mode 100644
index 0869114..0000000
--- a/services/core/java/com/android/server/am/OomAdjProfiler.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * 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.server.am;
-
-import android.os.Message;
-import android.os.PowerManagerInternal;
-import android.os.Process;
-import android.os.SystemClock;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.BackgroundThread;
-import com.android.internal.os.ProcessCpuTracker;
-import com.android.internal.util.RingBuffer;
-import com.android.internal.util.function.pooled.PooledLambda;
-
-import java.io.PrintWriter;
-
-public class OomAdjProfiler {
- private static final int MSG_UPDATE_CPU_TIME = 42;
-
- @GuardedBy("this")
- private boolean mOnBattery;
- @GuardedBy("this")
- private boolean mScreenOff;
-
- /** The value of {@link #mOnBattery} when the CPU time update was last scheduled. */
- @GuardedBy("this")
- private boolean mLastScheduledOnBattery;
- /** The value of {@link #mScreenOff} when the CPU time update was last scheduled. */
- @GuardedBy("this")
- private boolean mLastScheduledScreenOff;
-
- @GuardedBy("this")
- private long mOomAdjStartTimeUs;
- @GuardedBy("this")
- private boolean mOomAdjStarted;
-
- @GuardedBy("this")
- private CpuTimes mOomAdjRunTime = new CpuTimes();
- @GuardedBy("this")
- private CpuTimes mSystemServerCpuTime = new CpuTimes();
-
- @GuardedBy("this")
- private long mLastSystemServerCpuTimeMs;
- @GuardedBy("this")
- private boolean mSystemServerCpuTimeUpdateScheduled;
- private final ProcessCpuTracker mProcessCpuTracker = new ProcessCpuTracker(false);
-
- @GuardedBy("this")
- final RingBuffer<CpuTimes> mOomAdjRunTimesHist = new RingBuffer<>(CpuTimes.class, 10);
- @GuardedBy("this")
- final RingBuffer<CpuTimes> mSystemServerCpuTimesHist = new RingBuffer<>(CpuTimes.class, 10);
-
- @GuardedBy("this")
- private long mTotalOomAdjRunTimeUs;
- @GuardedBy("this")
- private int mTotalOomAdjCalls;
-
- void batteryPowerChanged(boolean onBattery) {
- synchronized (this) {
- scheduleSystemServerCpuTimeUpdate();
- mOnBattery = onBattery;
- }
- }
-
- void onWakefulnessChanged(int wakefulness) {
- synchronized (this) {
- scheduleSystemServerCpuTimeUpdate();
- mScreenOff = wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE;
- }
- }
-
- void oomAdjStarted() {
- synchronized (this) {
- mOomAdjStartTimeUs = SystemClock.currentThreadTimeMicro();
- mOomAdjStarted = true;
- }
- }
-
- void oomAdjEnded() {
- synchronized (this) {
- if (!mOomAdjStarted) {
- return;
- }
- long elapsedUs = SystemClock.currentThreadTimeMicro() - mOomAdjStartTimeUs;
- mOomAdjRunTime.addCpuTimeUs(elapsedUs);
- mTotalOomAdjRunTimeUs += elapsedUs;
- mTotalOomAdjCalls++;
- }
- }
-
- private void scheduleSystemServerCpuTimeUpdate() {
- synchronized (this) {
- if (mSystemServerCpuTimeUpdateScheduled) {
- return;
- }
- mLastScheduledOnBattery = mOnBattery;
- mLastScheduledScreenOff = mScreenOff;
- mSystemServerCpuTimeUpdateScheduled = true;
- Message scheduledMessage = PooledLambda.obtainMessage(
- OomAdjProfiler::updateSystemServerCpuTime,
- this, mLastScheduledOnBattery, mLastScheduledScreenOff, true);
- scheduledMessage.setWhat(MSG_UPDATE_CPU_TIME);
-
- BackgroundThread.getHandler().sendMessage(scheduledMessage);
- }
- }
-
- private void updateSystemServerCpuTime(boolean onBattery, boolean screenOff,
- boolean onlyIfScheduled) {
- final long cpuTimeMs = mProcessCpuTracker.getCpuTimeForPid(Process.myPid());
- synchronized (this) {
- if (onlyIfScheduled && !mSystemServerCpuTimeUpdateScheduled) {
- return;
- }
- mSystemServerCpuTime.addCpuTimeMs(
- cpuTimeMs - mLastSystemServerCpuTimeMs, onBattery, screenOff);
- mLastSystemServerCpuTimeMs = cpuTimeMs;
- mSystemServerCpuTimeUpdateScheduled = false;
- }
- }
-
- void reset() {
- synchronized (this) {
- if (mSystemServerCpuTime.isEmpty()) {
- return;
- }
- mOomAdjRunTimesHist.append(mOomAdjRunTime);
- mSystemServerCpuTimesHist.append(mSystemServerCpuTime);
- mOomAdjRunTime = new CpuTimes();
- mSystemServerCpuTime = new CpuTimes();
- }
- }
-
- void dump(PrintWriter pw) {
- synchronized (this) {
- if (mSystemServerCpuTimeUpdateScheduled) {
- // Cancel the scheduled update since we're going to update it here instead.
- BackgroundThread.getHandler().removeMessages(MSG_UPDATE_CPU_TIME);
- // Make sure the values are attributed to the right states.
- updateSystemServerCpuTime(mLastScheduledOnBattery, mLastScheduledScreenOff, false);
- } else {
- updateSystemServerCpuTime(mOnBattery, mScreenOff, false);
- }
-
- pw.println("System server and oomAdj runtimes (ms) in recent battery sessions "
- + "(most recent first):");
- if (!mSystemServerCpuTime.isEmpty()) {
- pw.print(" ");
- pw.print("system_server=");
- pw.print(mSystemServerCpuTime);
- pw.print(" ");
- pw.print("oom_adj=");
- pw.println(mOomAdjRunTime);
- }
- final CpuTimes[] systemServerCpuTimes = mSystemServerCpuTimesHist.toArray();
- final CpuTimes[] oomAdjRunTimes = mOomAdjRunTimesHist.toArray();
- for (int i = oomAdjRunTimes.length - 1; i >= 0; --i) {
- pw.print(" ");
- pw.print("system_server=");
- pw.print(systemServerCpuTimes[i]);
- pw.print(" ");
- pw.print("oom_adj=");
- pw.println(oomAdjRunTimes[i]);
- }
- if (mTotalOomAdjCalls != 0) {
- pw.println("System server total oomAdj runtimes (us) since boot:");
- pw.print(" cpu time spent=");
- pw.print(mTotalOomAdjRunTimeUs);
- pw.print(" number of calls=");
- pw.print(mTotalOomAdjCalls);
- pw.print(" average=");
- pw.println(mTotalOomAdjRunTimeUs / mTotalOomAdjCalls);
- }
- }
- }
-
- private class CpuTimes {
- private long mOnBatteryTimeUs;
- private long mOnBatteryScreenOffTimeUs;
-
- public void addCpuTimeMs(long cpuTimeMs) {
- addCpuTimeUs(cpuTimeMs * 1000, mOnBattery, mScreenOff);
- }
-
- public void addCpuTimeMs(long cpuTimeMs, boolean onBattery, boolean screenOff) {
- addCpuTimeUs(cpuTimeMs * 1000, onBattery, screenOff);
- }
-
- public void addCpuTimeUs(long cpuTimeUs) {
- addCpuTimeUs(cpuTimeUs, mOnBattery, mScreenOff);
- }
-
- public void addCpuTimeUs(long cpuTimeUs, boolean onBattery, boolean screenOff) {
- if (onBattery) {
- mOnBatteryTimeUs += cpuTimeUs;
- if (screenOff) {
- mOnBatteryScreenOffTimeUs += cpuTimeUs;
- }
- }
- }
-
- public boolean isEmpty() {
- return mOnBatteryTimeUs == 0 && mOnBatteryScreenOffTimeUs == 0;
- }
-
- public String toString() {
- return "[" + (mOnBatteryTimeUs / 1000) + ","
- + (mOnBatteryScreenOffTimeUs / 1000) + "]";
- }
- }
-}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index ea7a21d..9b72db8 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -600,7 +600,6 @@
mLastReason = oomAdjReason;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
final ProcessStateRecord state = app.mState;
@@ -630,7 +629,6 @@
}
mTmpProcessList.clear();
mService.clearPendingTopAppLocked();
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return true;
}
@@ -849,7 +847,6 @@
mLastReason = oomAdjReason;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
mProcessStateCurTop = enqueuePendingTopAppIfNecessaryLSP();
final ArrayList<ProcessRecord> processes = mTmpProcessList;
@@ -862,7 +859,6 @@
processes.clear();
mService.clearPendingTopAppLocked();
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -895,7 +891,6 @@
mLastReason = oomAdjReason;
if (startProfiling) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
}
final long now = SystemClock.uptimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
@@ -989,7 +984,6 @@
postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true);
if (startProfiling) {
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 00e1482..3268b66 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -744,11 +744,9 @@
mLastReason = oomAdjReason;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
fullUpdateLSP(oomAdjReason);
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -766,14 +764,12 @@
mLastReason = oomAdjReason;
mProcessStateCurTop = enqueuePendingTopAppIfNecessaryLSP();
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
synchronized (mProcLock) {
partialUpdateLSP(oomAdjReason, mPendingProcessSet);
}
mPendingProcessSet.clear();
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index fb0d695..f336120 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -22,6 +22,8 @@
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANCELED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED;
import android.annotation.Nullable;
import android.app.Activity;
@@ -54,6 +56,7 @@
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.AlarmManagerInternal;
import com.android.server.LocalServices;
+import com.android.server.am.PendingIntentRecord.CancellationReason;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.SafeActivityOptions;
@@ -191,7 +194,7 @@
}
return rec;
}
- makeIntentSenderCanceled(rec);
+ makeIntentSenderCanceled(rec, CANCEL_REASON_SUPERSEDED);
mIntentSenderRecords.remove(key);
decrementUidStatLocked(rec);
}
@@ -206,7 +209,7 @@
}
boolean removePendingIntentsForPackage(String packageName, int userId, int appId,
- boolean doIt) {
+ boolean doIt, @CancellationReason int cancelReason) {
boolean didSomething = false;
synchronized (mLock) {
@@ -256,7 +259,7 @@
}
didSomething = true;
it.remove();
- makeIntentSenderCanceled(pir);
+ makeIntentSenderCanceled(pir, cancelReason);
decrementUidStatLocked(pir);
if (pir.key.activity != null) {
final Message m = PooledLambda.obtainMessage(
@@ -289,13 +292,14 @@
} catch (RemoteException e) {
throw new SecurityException(e);
}
- cancelIntentSender(rec, true);
+ cancelIntentSender(rec, true, CANCEL_REASON_OWNER_CANCELED);
}
}
- public void cancelIntentSender(PendingIntentRecord rec, boolean cleanActivity) {
+ public void cancelIntentSender(PendingIntentRecord rec, boolean cleanActivity,
+ @CancellationReason int cancelReason) {
synchronized (mLock) {
- makeIntentSenderCanceled(rec);
+ makeIntentSenderCanceled(rec, cancelReason);
mIntentSenderRecords.remove(rec.key);
decrementUidStatLocked(rec);
if (cleanActivity && rec.key.activity != null) {
@@ -359,8 +363,10 @@
}
}
- private void makeIntentSenderCanceled(PendingIntentRecord rec) {
+ private void makeIntentSenderCanceled(PendingIntentRecord rec,
+ @CancellationReason int cancelReason) {
rec.canceled = true;
+ rec.cancelReason = cancelReason;
final RemoteCallbackList<IResultReceiver> callbacks = rec.detachCancelListenersLocked();
if (callbacks != null) {
final Message m = PooledLambda.obtainMessage(
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 95e130e..da45a77 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -16,11 +16,13 @@
package com.android.server.am;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.START_SUCCESS;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
@@ -51,11 +53,15 @@
import android.util.Slog;
import android.util.TimeUtils;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.expresslog.Counter;
import com.android.server.wm.SafeActivityOptions;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.Objects;
@@ -71,12 +77,35 @@
public static final int FLAG_BROADCAST_SENDER = 1 << 1;
public static final int FLAG_SERVICE_SENDER = 1 << 2;
+ public static final int CANCEL_REASON_NULL = 0;
+ public static final int CANCEL_REASON_USER_STOPPED = 1 << 0;
+ public static final int CANCEL_REASON_OWNER_UNINSTALLED = 1 << 1;
+ public static final int CANCEL_REASON_OWNER_FORCE_STOPPED = 1 << 2;
+ public static final int CANCEL_REASON_OWNER_CANCELED = 1 << 3;
+ public static final int CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED = 1 << 4;
+ public static final int CANCEL_REASON_SUPERSEDED = 1 << 5;
+ public static final int CANCEL_REASON_ONE_SHOT_SENT = 1 << 6;
+
+ @IntDef({
+ CANCEL_REASON_NULL,
+ CANCEL_REASON_USER_STOPPED,
+ CANCEL_REASON_OWNER_UNINSTALLED,
+ CANCEL_REASON_OWNER_FORCE_STOPPED,
+ CANCEL_REASON_OWNER_CANCELED,
+ CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED,
+ CANCEL_REASON_SUPERSEDED,
+ CANCEL_REASON_ONE_SHOT_SENT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CancellationReason {}
+
final PendingIntentController controller;
final Key key;
final int uid;
public final WeakReference<PendingIntentRecord> ref;
boolean sent = false;
boolean canceled = false;
+ @CancellationReason int cancelReason = CANCEL_REASON_NULL;
/**
* Map IBinder to duration specified as Pair<Long, Integer>, Long is allowlist duration in
* milliseconds, Integer is allowlist type defined at
@@ -419,12 +448,22 @@
SafeActivityOptions mergedOptions = null;
synchronized (controller.mLock) {
if (canceled) {
+ if (cancelReason == CANCEL_REASON_OWNER_FORCE_STOPPED
+ && controller.mAmInternal.getUidProcessState(callingUid)
+ == PROCESS_STATE_TOP) {
+ Counter.logIncrementWithUid(
+ "app.value_force_stop_cancelled_pi_sent_from_top_per_caller",
+ callingUid);
+ Counter.logIncrementWithUid(
+ "app.value_force_stop_cancelled_pi_sent_from_top_per_owner",
+ uid);
+ }
return ActivityManager.START_CANCELED;
}
sent = true;
if ((key.flags & PendingIntent.FLAG_ONE_SHOT) != 0) {
- controller.cancelIntentSender(this, true);
+ controller.cancelIntentSender(this, true, CANCEL_REASON_ONE_SHOT_SENT);
}
finalIntent = key.requestIntent != null ? new Intent(key.requestIntent) : new Intent();
@@ -687,6 +726,21 @@
}
}
+ @VisibleForTesting
+ static String cancelReasonToString(@CancellationReason int cancelReason) {
+ return switch (cancelReason) {
+ case CANCEL_REASON_NULL -> "NULL";
+ case CANCEL_REASON_USER_STOPPED -> "USER_STOPPED";
+ case CANCEL_REASON_OWNER_UNINSTALLED -> "OWNER_UNINSTALLED";
+ case CANCEL_REASON_OWNER_FORCE_STOPPED -> "OWNER_FORCE_STOPPED";
+ case CANCEL_REASON_OWNER_CANCELED -> "OWNER_CANCELED";
+ case CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED -> "HOSTING_ACTIVITY_DESTROYED";
+ case CANCEL_REASON_SUPERSEDED -> "SUPERSEDED";
+ case CANCEL_REASON_ONE_SHOT_SENT -> "ONE_SHOT_SENT";
+ default -> "UNKNOWN";
+ };
+ }
+
public void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("uid="); pw.print(uid);
pw.print(" packageName="); pw.print(key.packageName);
@@ -707,7 +761,8 @@
}
if (sent || canceled) {
pw.print(prefix); pw.print("sent="); pw.print(sent);
- pw.print(" canceled="); pw.println(canceled);
+ pw.print(" canceled="); pw.print(canceled);
+ pw.print(" cancelReason="); pw.println(cancelReasonToString(cancelReason));
}
if (mAllowlistDuration != null) {
pw.print(prefix);
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 5834dcd..045d137 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -32,6 +32,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UptimeMillisLong;
import android.app.BackgroundStartPrivileges;
import android.app.IApplicationThread;
import android.app.Notification;
@@ -56,7 +57,6 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArrayMap;
-import android.util.Pair;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -237,8 +237,6 @@
boolean mFgsNotificationShown;
// Whether FGS package has permissions to show notifications.
boolean mFgsHasNotificationPermission;
- // Whether the FGS contains a type that is time limited.
- private boolean mFgsIsTimeLimited;
// allow the service becomes foreground service? Service started from background may not be
// allowed to become a foreground service.
@@ -675,6 +673,62 @@
*/
private ShortFgsInfo mShortFgsInfo;
+ /**
+ * Data container class to help track certain fgs info for time-restricted types.
+ */
+ static class TimeLimitedFgsInfo {
+ @UptimeMillisLong
+ private long mFirstFgsStartTime;
+ @UptimeMillisLong
+ private long mLastFgsStartTime;
+ @UptimeMillisLong
+ private long mTimeLimitExceededAt = Long.MIN_VALUE;
+ private long mTotalRuntime = 0;
+
+ TimeLimitedFgsInfo(@UptimeMillisLong long startTime) {
+ mFirstFgsStartTime = startTime;
+ mLastFgsStartTime = startTime;
+ }
+
+ @UptimeMillisLong
+ public long getFirstFgsStartTime() {
+ return mFirstFgsStartTime;
+ }
+
+ public void setLastFgsStartTime(@UptimeMillisLong long startTime) {
+ mLastFgsStartTime = startTime;
+ }
+
+ @UptimeMillisLong
+ public long getLastFgsStartTime() {
+ return mLastFgsStartTime;
+ }
+
+ public void updateTotalRuntime() {
+ mTotalRuntime += SystemClock.uptimeMillis() - mLastFgsStartTime;
+ }
+
+ public long getTotalRuntime() {
+ return mTotalRuntime;
+ }
+
+ public void setTimeLimitExceededAt(@UptimeMillisLong long timeLimitExceededAt) {
+ mTimeLimitExceededAt = timeLimitExceededAt;
+ }
+
+ @UptimeMillisLong
+ public long getTimeLimitExceededAt() {
+ return mTimeLimitExceededAt;
+ }
+
+ public void reset() {
+ mFirstFgsStartTime = 0;
+ mLastFgsStartTime = 0;
+ mTotalRuntime = 0;
+ mTimeLimitExceededAt = Long.MIN_VALUE;
+ }
+ }
+
void dumpStartList(PrintWriter pw, String prefix, List<StartItem> list, long now) {
final int N = list.size();
for (int i=0; i<N; i++) {
@@ -927,7 +981,6 @@
pw.print(prefix); pw.print("isForeground="); pw.print(isForeground);
pw.print(" foregroundId="); pw.print(foregroundId);
pw.printf(" types=%08X", foregroundServiceType);
- pw.print(" fgsHasTimeLimitedType="); pw.print(mFgsIsTimeLimited);
pw.print(" foregroundNoti="); pw.println(foregroundNoti);
if (isShortFgs() && mShortFgsInfo != null) {
@@ -1803,80 +1856,41 @@
}
/**
+ * Called when a time-limited FGS starts.
+ */
+ public TimeLimitedFgsInfo createTimeLimitedFgsInfo(long nowUptime) {
+ return new TimeLimitedFgsInfo(nowUptime);
+ }
+
+ /**
* @return true if one of the types of this FGS has a time limit.
*/
public boolean isFgsTimeLimited() {
- return startRequested && isForeground && canFgsTypeTimeOut(foregroundServiceType);
+ return startRequested
+ && isForeground
+ && ams.mServices.getTimeLimitedFgsType(foregroundServiceType)
+ != ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
}
/**
- * Called when a FGS with a time-limited type starts ({@code true}) or stops ({@code false}).
+ * @return the next stop time for the given type, based on how long it has already ran for.
+ * The total runtime is automatically reset 24hrs after the first fgs start of this type
+ * or if the app has recently been in the TOP state when the app calls startForeground().
*/
- public void setIsFgsTimeLimited(boolean fgsIsTimeLimited) {
- this.mFgsIsTimeLimited = fgsIsTimeLimited;
- }
-
- /**
- * @return whether {@link #mFgsIsTimeLimited} was previously set or not.
- */
- public boolean wasFgsPreviouslyTimeLimited() {
- return mFgsIsTimeLimited;
- }
-
- /**
- * @return the FGS type if the service has reached its time limit, otherwise -1.
- */
- public int getTimedOutFgsType(long nowUptime) {
- if (!isAppAlive() || !isFgsTimeLimited()) {
- return -1;
+ long getNextFgsStopTime(int fgsType, TimeLimitedFgsInfo fgsInfo) {
+ final long timeLimit;
+ switch (ams.mServices.getTimeLimitedFgsType(fgsType)) {
+ case ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING:
+ timeLimit = ams.mConstants.mMediaProcessingFgsTimeoutDuration;
+ break;
+ case ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC:
+ timeLimit = ams.mConstants.mDataSyncFgsTimeoutDuration;
+ break;
+ // Add logic for time limits introduced in the future for other fgs types above.
+ default:
+ return Long.MAX_VALUE;
}
-
- final Pair<Integer, Long> fgsTypeAndStopTime = getEarliestStopTypeAndTime();
- if (fgsTypeAndStopTime.first != -1 && fgsTypeAndStopTime.second <= nowUptime) {
- return fgsTypeAndStopTime.first;
- }
- return -1; // no fgs type exceeded time limit
- }
-
- /**
- * @return a {@code Pair<fgs_type, stop_time>}, representing the earliest time at which the FGS
- * should be stopped (fgs start time + time limit for most restrictive type)
- */
- Pair<Integer, Long> getEarliestStopTypeAndTime() {
- int fgsType = -1;
- long timeout = 0;
- if ((foregroundServiceType & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING)
- == ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING) {
- fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING;
- timeout = ams.mConstants.mMediaProcessingFgsTimeoutDuration;
- }
- if ((foregroundServiceType & ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
- == ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) {
- // update the timeout and type if this type has a more restrictive time limit
- if (timeout == 0 || ams.mConstants.mDataSyncFgsTimeoutDuration < timeout) {
- fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC;
- timeout = ams.mConstants.mDataSyncFgsTimeoutDuration;
- }
- }
- // Add the logic for time limits introduced in the future for other fgs types here.
- return Pair.create(fgsType, timeout == 0 ? 0 : (mFgsEnterTime + timeout));
- }
-
- /**
- * Check if the given types contain a type which is time restricted.
- */
- boolean canFgsTypeTimeOut(int fgsType) {
- // The below conditionals are not simplified on purpose to help with readability.
- if ((fgsType & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING)
- == ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING) {
- return true;
- }
- if ((fgsType & ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
- == ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) {
- return true;
- }
- // Additional types which have time limits should be added here in the future.
- return false;
+ return fgsInfo.mLastFgsStartTime + Math.max(0, timeLimit - fgsInfo.mTotalRuntime);
}
private boolean isAppAlive() {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index dd4cee4..b703076 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -59,6 +59,7 @@
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOCKING_USER;
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND_VISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND;
import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
@@ -200,6 +201,7 @@
static final int START_USER_SWITCH_FG_MSG = 120;
static final int COMPLETE_USER_SWITCH_MSG = 130;
static final int USER_COMPLETED_EVENT_MSG = 140;
+ static final int SCHEDULED_STOP_BACKGROUND_USER_MSG = 150;
private static final int NO_ARG2 = 0;
@@ -251,6 +253,14 @@
@GuardedBy("mLock")
private int mMaxRunningUsers;
+ /**
+ * Number of seconds of uptime after a full user enters the background before we attempt
+ * to stop it due to inactivity. Set to -1 to disable scheduling stopping background users.
+ *
+ * Typically set by config_backgroundUserScheduledStopTimeSecs.
+ */
+ private int mBackgroundUserScheduledStopTimeSecs = -1;
+
// Lock for internal state.
private final Object mLock = new Object();
@@ -453,11 +463,12 @@
}
void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers,
- boolean delayUserDataLocking) {
+ boolean delayUserDataLocking, int backgroundUserScheduledStopTimeSecs) {
synchronized (mLock) {
mUserSwitchUiEnabled = userSwitchUiEnabled;
mMaxRunningUsers = maxRunningUsers;
mDelayUserDataLocking = delayUserDataLocking;
+ mBackgroundUserScheduledStopTimeSecs = backgroundUserScheduledStopTimeSecs;
mInitialized = true;
}
}
@@ -1091,6 +1102,10 @@
final IStopUserCallback stopUserCallback,
KeyEvictedCallback keyEvictedCallback) {
Slogf.i(TAG, "stopSingleUserLU userId=" + userId);
+ if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
+ mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG,
+ Integer.valueOf(userId));
+ }
final UserState uss = mStartedUsers.get(userId);
if (uss == null) { // User is not started
// If canDelayDataLockingForUser() is true and allowDelayedLocking is false, we need
@@ -1879,6 +1894,10 @@
// No matter what, the fact that we're requested to start the user (even if it is
// already running) puts it towards the end of the mUserLru list.
addUserToUserLru(userId);
+ if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
+ mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG,
+ Integer.valueOf(userId));
+ }
if (unlockListener != null) {
uss.mUnlockProgress.addListener(unlockListener);
@@ -1923,6 +1942,9 @@
// of mUserLru, so we need to ensure that the foreground user isn't displaced.
addUserToUserLru(mCurrentUserId);
}
+ if (userStartMode == USER_START_MODE_BACKGROUND && !userInfo.isProfile()) {
+ scheduleStopOfBackgroundUser(userId);
+ }
t.traceEnd();
// Make sure user is in the started state. If it is currently
@@ -2294,6 +2316,65 @@
}
}
+ /**
+ * Possibly schedules the user to be stopped at a future point. To be used to stop background
+ * users that haven't been actively used in a long time.
+ * This is only intended for full users that are currently in the background.
+ */
+ private void scheduleStopOfBackgroundUser(@UserIdInt int oldUserId) {
+ if (!android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
+ return;
+ }
+ final int delayUptimeSecs = mBackgroundUserScheduledStopTimeSecs;
+ if (delayUptimeSecs <= 0 || UserManager.isVisibleBackgroundUsersEnabled()) {
+ // Feature is not enabled on this device.
+ return;
+ }
+ if (oldUserId == UserHandle.USER_SYSTEM) {
+ // Never stop system user
+ return;
+ }
+ if (oldUserId == mInjector.getUserManagerInternal().getMainUserId()) {
+ // MainUser is currently special for things like Docking, so we'll exempt it for now.
+ Slogf.i(TAG, "Exempting user %d from being stopped due to inactivity by virtue "
+ + "of it being the main user", oldUserId);
+ return;
+ }
+ Slogf.d(TAG, "Scheduling to stop user %d in %d seconds", oldUserId, delayUptimeSecs);
+ final int delayUptimeMs = delayUptimeSecs * 1000;
+ final Object msgObj = oldUserId;
+ mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, msgObj);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(SCHEDULED_STOP_BACKGROUND_USER_MSG, msgObj),
+ delayUptimeMs);
+ }
+
+ /**
+ * Possibly stops the given full user due to it having been in the background for a long time.
+ * There is no guarantee of stopping the user; it is done discretionarily.
+ *
+ * This should never be called for background visible users; devices that support this should
+ * not use {@link #scheduleStopOfBackgroundUser(int)}.
+ *
+ * @param userIdInteger a full user to be stopped if it is still in the background
+ */
+ @VisibleForTesting
+ void processScheduledStopOfBackgroundUser(Integer userIdInteger) {
+ final int userId = userIdInteger;
+ Slogf.d(TAG, "Considering stopping background user %d due to inactivity", userId);
+ synchronized (mLock) {
+ if (getCurrentOrTargetUserIdLU() == userId) {
+ return;
+ }
+ if (mPendingTargetUserIds.contains(userIdInteger)) {
+ // We'll soon want to switch to this user, so don't kill it now.
+ return;
+ }
+ Slogf.i(TAG, "Stopping background user %d due to inactivity", userId);
+ stopUsersLU(userId, /* allowDelayedLocking= */ true, null, null);
+ }
+ }
+
private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
t.traceBegin("timeoutUserSwitch-" + oldUserId + "-to-" + newUserId);
@@ -2428,6 +2509,7 @@
uss.switching = false;
stopGuestOrEphemeralUserIfBackground(oldUserId);
stopUserOnSwitchIfEnforced(oldUserId);
+ scheduleStopOfBackgroundUser(oldUserId);
t.traceEnd(); // end continueUserSwitch
}
@@ -3309,6 +3391,8 @@
pw.println(" shouldStopUserOnSwitch():" + shouldStopUserOnSwitch());
pw.println(" mStopUserOnSwitch:" + mStopUserOnSwitch);
pw.println(" mMaxRunningUsers:" + mMaxRunningUsers);
+ pw.println(" mBackgroundUserScheduledStopTimeSecs:"
+ + mBackgroundUserScheduledStopTimeSecs);
pw.println(" mUserSwitchUiEnabled:" + mUserSwitchUiEnabled);
pw.println(" mInitialized:" + mInitialized);
pw.println(" mIsBroadcastSentForSystemUserStarted:"
@@ -3435,6 +3519,9 @@
case COMPLETE_USER_SWITCH_MSG:
completeUserSwitch(msg.arg1, msg.arg2);
break;
+ case SCHEDULED_STOP_BACKGROUND_USER_MSG:
+ processScheduledStopOfBackgroundUser((Integer) msg.obj);
+ break;
}
return false;
}
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index a17b3d5..ce41079 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -461,6 +461,9 @@
packageName, PACKAGE_MATCH_FLAGS, userId);
StorageStats stats = mStorageStatsManager.queryStatsForPackage(
info.storageUuid, packageName, new UserHandle(userId));
+ if (android.app.Flags.appRestrictionsApi()) {
+ noteHibernationChange(packageName, info.uid, true);
+ }
mIActivityManager.forceStopPackage(packageName, userId);
mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId,
null /* observer */);
@@ -490,6 +493,11 @@
// Deliver LOCKED_BOOT_COMPLETE AND BOOT_COMPLETE broadcast so app can re-register
// their alarms/jobs/etc.
try {
+ if (android.app.Flags.appRestrictionsApi()) {
+ ApplicationInfo info = mIPackageManager.getApplicationInfo(
+ packageName, PACKAGE_MATCH_FLAGS, userId);
+ noteHibernationChange(packageName, info.uid, false);
+ }
Intent lockedBcIntent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
.setPackage(packageName);
final String[] requiredPermissions = {Manifest.permission.RECEIVE_BOOT_COMPLETED};
@@ -555,6 +563,26 @@
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
+ /** Inform ActivityManager that the app being stopped or unstopped due to hibernation */
+ private void noteHibernationChange(String packageName, int uid, boolean hibernated) {
+ try {
+ if (hibernated) {
+ // TODO: Switch to an ActivityManagerInternal API
+ mIActivityManager.noteAppRestrictionEnabled(
+ packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED,
+ true, ActivityManager.RESTRICTION_REASON_DORMANT, null,
+ /* TODO: fetch actual timeout - 90 days */ 90 * 24 * 60 * 60_000L);
+ } else {
+ mIActivityManager.noteAppRestrictionEnabled(
+ packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED,
+ false, ActivityManager.RESTRICTION_REASON_USAGE, null,
+ 0L);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Couldn't set restriction state change");
+ }
+ }
+
/**
* Initializes in-memory store of user-level hibernation states for the given user
*
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index be39778..1bd93e4 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -70,6 +70,7 @@
import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
+import static android.permission.flags.Flags.runtimePermissionAppopsMappingEnabled;
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
@@ -248,6 +249,7 @@
Process.ROOT_UID,
Process.PHONE_UID,
Process.BLUETOOTH_UID,
+ Process.AUDIOSERVER_UID,
Process.NFC_UID,
Process.NETWORK_STACK_UID,
Process.SHELL_UID};
@@ -2682,6 +2684,15 @@
}
}
+ /**
+ * When querying the mode these should always be allowed and the checking service might not
+ * have information on them.
+ */
+ private static boolean isOpAllowedForUid(int uid) {
+ return runtimePermissionAppopsMappingEnabled()
+ && (uid == Process.ROOT_UID || uid == Process.SYSTEM_UID);
+ }
+
@Override
public int checkOperationRaw(int code, int uid, String packageName,
@Nullable String attributionTag) {
@@ -2757,6 +2768,9 @@
pvr.bypass, true)) {
return AppOpsManager.MODE_IGNORED;
}
+ if (isOpAllowedForUid(uid)) {
+ return MODE_ALLOWED;
+ }
code = AppOpsManager.opToSwitch(code);
UidState uidState = getUidStateLocked(uid, false);
if (uidState != null
@@ -3071,9 +3085,12 @@
return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
packageName);
}
- // If there is a non-default per UID policy (we set UID op mode only if
- // non-default) it takes over, otherwise use the per package policy.
- if (mAppOpsCheckingService.getUidMode(
+ if (isOpAllowedForUid(uid)) {
+ // Op is always allowed for the UID, do nothing.
+
+ // If there is a non-default per UID policy (we set UID op mode only if
+ // non-default) it takes over, otherwise use the per package policy.
+ } else if (mAppOpsCheckingService.getUidMode(
uidState.uid, getPersistentId(virtualDeviceId), switchCode)
!= AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode =
@@ -3665,10 +3682,13 @@
isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag,
virtualDeviceId, pvr.bypass, false);
final int switchCode = AppOpsManager.opToSwitch(code);
- // If there is a non-default per UID policy (we set UID op mode only if
- // non-default) it takes over, otherwise use the per package policy.
- if (mAppOpsCheckingService.getUidMode(
- uidState.uid, getPersistentId(virtualDeviceId), switchCode)
+ if (isOpAllowedForUid(uid)) {
+ // Op is always allowed for the UID, do nothing.
+
+ // If there is a non-default per UID policy (we set UID op mode only if
+ // non-default) it takes over, otherwise use the per package policy.
+ } else if (mAppOpsCheckingService.getUidMode(
+ uidState.uid, getPersistentId(virtualDeviceId), switchCode)
!= AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode =
uidState.evalMode(
@@ -5523,6 +5543,20 @@
}
return 0;
}
+ case "note": {
+ int res = shell.parseUserPackageOp(true, err);
+ if (res < 0) {
+ return res;
+ }
+ if (shell.packageName != null) {
+ shell.mInterface.noteOperation(shell.op, shell.packageUid,
+ shell.packageName, shell.attributionTag, true,
+ "appops note shell command", true);
+ } else {
+ return -1;
+ }
+ return 0;
+ }
case "start": {
int res = shell.parseUserPackageOp(true, err);
if (res < 0) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index f38b381..9bdc51e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -987,9 +987,9 @@
}
if (di.mPeerDeviceAddress.equals("")) {
for (Pair<String, String> addr : addresses) {
- if (!addr.first.equals(di.mDeviceAddress)) {
- di.mPeerDeviceAddress = addr.first;
- di.mPeerIdentityDeviceAddress = addr.second;
+ if (!di.mDeviceAddress.equals(addr.first)) {
+ di.mPeerDeviceAddress = TextUtils.emptyIfNull(addr.first);
+ di.mPeerIdentityDeviceAddress = TextUtils.emptyIfNull(addr.second);
break;
}
}
@@ -1000,8 +1000,8 @@
}
if (di.mDeviceIdentityAddress.equals("")) {
for (Pair<String, String> addr : addresses) {
- if (addr.first.equals(di.mDeviceAddress)) {
- di.mDeviceIdentityAddress = addr.second;
+ if (di.mDeviceAddress.equals(addr.first)) {
+ di.mDeviceIdentityAddress = TextUtils.emptyIfNull(addr.second);
break;
}
}
diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
index 570d4e9..e330ed5 100644
--- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -16,6 +16,11 @@
package com.android.server.audio;
+import static android.media.AudioManager.ADJUST_LOWER;
+import static android.media.AudioManager.ADJUST_MUTE;
+import static android.media.AudioManager.ADJUST_RAISE;
+import static android.media.AudioManager.ADJUST_UNMUTE;
+
import android.content.Context;
import android.media.AudioManager;
import android.os.ShellCommand;
@@ -59,6 +64,12 @@
return adjMute();
case "adj-unmute":
return adjUnmute();
+ case "adj-volume":
+ return adjVolume();
+ case "set-group-volume":
+ return setGroupVolume();
+ case "adj-group-volume":
+ return adjGroupVolume();
}
return 0;
}
@@ -90,6 +101,12 @@
pw.println(" mutes the STREAM_TYPE");
pw.println(" adj-unmute STREAM_TYPE");
pw.println(" unmutes the STREAM_TYPE");
+ pw.println(" adj-volume STREAM_TYPE <RAISE|LOWER|MUTE|UNMUTE>");
+ pw.println(" Adjusts the STREAM_TYPE volume given the specified direction");
+ pw.println(" set-group-volume GROUP_ID VOLUME_INDEX");
+ pw.println(" Sets the volume for GROUP_ID to VOLUME_INDEX");
+ pw.println(" adj-group-volume GROUP_ID <RAISE|LOWER|MUTE|UNMUTE>");
+ pw.println(" Adjusts the group volume for GROUP_ID given the specified direction");
}
private int setSurroundFormatEnabled() {
@@ -260,15 +277,48 @@
return 0;
}
+ private int adjVolume() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ final int stream = readIntArg();
+ final int direction = readDirectionArg();
+ getOutPrintWriter().println("calling AudioManager.adjustStreamVolume("
+ + stream + ", " + direction + ", 0)");
+ am.adjustStreamVolume(stream, direction, 0);
+ return 0;
+ }
+
+ private int setGroupVolume() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ final int groupId = readIntArg();
+ final int index = readIntArg();
+ getOutPrintWriter().println("calling AudioManager.setVolumeGroupVolumeIndex("
+ + groupId + ", " + index + ", 0)");
+ am.setVolumeGroupVolumeIndex(groupId, index, 0);
+ return 0;
+ }
+
+ private int adjGroupVolume() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ final int groupId = readIntArg();
+ final int direction = readDirectionArg();
+ getOutPrintWriter().println("calling AudioManager.adjustVolumeGroupVolume("
+ + groupId + ", " + direction + ", 0)");
+ am.adjustVolumeGroupVolume(groupId, direction, 0);
+ return 0;
+ }
+
private int readIntArg() throws IllegalArgumentException {
- String argText = getNextArg();
+ final String argText = getNextArg();
if (argText == null) {
getErrPrintWriter().println("Error: no argument provided");
throw new IllegalArgumentException("No argument provided");
}
- int argIntVal = Integer.MIN_VALUE;
+ int argIntVal;
try {
argIntVal = Integer.parseInt(argText);
} catch (NumberFormatException e) {
@@ -278,4 +328,21 @@
return argIntVal;
}
+
+ private int readDirectionArg() throws IllegalArgumentException {
+ final String argText = getNextArg();
+
+ if (argText == null) {
+ getErrPrintWriter().println("Error: no argument provided");
+ throw new IllegalArgumentException("No argument provided");
+ }
+
+ return switch (argText) {
+ case "RAISE" -> ADJUST_RAISE;
+ case "LOWER" -> ADJUST_LOWER;
+ case "MUTE" -> ADJUST_MUTE;
+ case "UNMUTE" -> ADJUST_UNMUTE;
+ default -> throw new IllegalArgumentException("Wrong direction argument: " + argText);
+ };
+ }
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0e22ef1..add8491 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -176,6 +176,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.HwBinder;
import android.os.IBinder;
import android.os.Looper;
@@ -686,6 +687,9 @@
private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+ // Handler for broadcast receiver
+ // TODO(b/335513647) combine handlers
+ private final HandlerThread mBroadcastHandlerThread;
// Broadcast receiver for device connections intent broadcasts
private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
@@ -1121,6 +1125,9 @@
mAudioPolicy = audioPolicy;
mPlatformType = AudioSystem.getPlatformType(context);
+ mBroadcastHandlerThread = new HandlerThread("AudioService Broadcast");
+ mBroadcastHandlerThread.start();
+
mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
mIsSingleVolume = AudioSystem.isSingleVolume(context);
@@ -1507,7 +1514,8 @@
intentFilter.addAction(ACTION_CHECK_MUSIC_ACTIVE);
intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null,
+ mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null,
+ mBroadcastHandlerThread.getThreadHandler(),
Context.RECEIVER_EXPORTED);
SubscriptionManager subscriptionManager = mContext.getSystemService(
@@ -7910,6 +7918,7 @@
DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADSET);
DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_LINE);
+ DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_HEARING_AID);
DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_BLE_SET);
DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET);
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index edeabdc..a649d34 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -1110,6 +1110,12 @@
return mLeAudio.getGroupId(device);
}
+ /**
+ * Returns all addresses and identity addresses for LE Audio devices a group.
+ * @param groupId The ID of the group from which to get addresses.
+ * @return A List of Pair(String main_address, String identity_address). Note that the
+ * addresses returned by BluetoothDevice can be null.
+ */
/*package*/ List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) {
List<Pair<String, String>> addresses = new ArrayList<>();
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java
index 85b3b49..ba45310 100644
--- a/services/core/java/com/android/server/audio/MusicFxHelper.java
+++ b/services/core/java/com/android/server/audio/MusicFxHelper.java
@@ -90,7 +90,6 @@
* observer will also be removed, and observer token reset to null
*/
private class MySparseArray extends SparseArray<PackageSessions> {
- private final String mMusicFxPackageName = "com.android.musicfx";
@RequiresPermission(anyOf = {
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
@@ -229,6 +228,10 @@
if (ril != null && ril.size() != 0) {
ResolveInfo ri = ril.get(0);
final String senderPackageName = intent.getStringExtra(AudioEffect.EXTRA_PACKAGE_NAME);
+ if (senderPackageName == null) {
+ Log.w(TAG, "Intent package name must not be null");
+ return;
+ }
try {
if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) {
final int senderUid = pm.getPackageUidAsUser(senderPackageName,
@@ -265,7 +268,7 @@
+ senderUid + ", package: " + senderPackageName + ", abort");
return false;
}
- if (pkgSessions.mPackageName != senderPackageName) {
+ if (!pkgSessions.mPackageName.equals(senderPackageName)) {
Log.w(TAG, "Inconsistency package names for UID open: " + senderUid + " prev: "
+ pkgSessions.mPackageName + ", now: " + senderPackageName);
return false;
@@ -297,7 +300,7 @@
Log.e(TAG, senderPackageName + " UID " + senderUid + " does not exist in map, abort");
return false;
}
- if (pkgSessions.mPackageName != senderPackageName) {
+ if (!pkgSessions.mPackageName.equals(senderPackageName)) {
Log.w(TAG, "Inconsistency package names for UID " + senderUid + " close, prev: "
+ pkgSessions.mPackageName + ", now: " + senderPackageName);
return false;
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 7df63b1..11cca66 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -25,15 +25,12 @@
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -778,13 +775,7 @@
hidlConfigs = null;
}
- if (com.android.server.biometrics.Flags.deHidl()) {
- registerAuthenticators();
- } else {
- // Registers HIDL and AIDL authenticators, but only HIDL configs need to be provided.
- registerAuthenticators(hidlConfigs);
- }
-
+ registerAuthenticators();
mInjector.publishBinderService(this, mImpl);
}
@@ -874,7 +865,7 @@
if (faceService != null) {
try {
- faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ faceService.registerAuthenticators(mFaceSensorConfigurations);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException when registering face authenticators", e);
}
@@ -912,8 +903,7 @@
if (fingerprintService != null) {
try {
- fingerprintService.registerAuthenticatorsLegacy(
- mFingerprintSensorConfigurations);
+ fingerprintService.registerAuthenticators(mFingerprintSensorConfigurations);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
}
@@ -948,78 +938,6 @@
return configStrings;
}
- /**
- * Registers HIDL and AIDL authenticators for all of the available modalities.
- *
- * @param hidlSensors Array of {@link SensorConfig} configuration for all of the HIDL sensors
- * available on the device. This array may contain configuration for
- * different modalities and different sensors of the same modality in
- * arbitrary order. Can be null if no HIDL sensors exist on the device.
- */
- private void registerAuthenticators(@Nullable SensorConfig[] hidlSensors) {
- List<FingerprintSensorPropertiesInternal> hidlFingerprintSensors = new ArrayList<>();
- List<FaceSensorPropertiesInternal> hidlFaceSensors = new ArrayList<>();
- // Iris doesn't have IrisSensorPropertiesInternal, using SensorPropertiesInternal instead.
- List<SensorPropertiesInternal> hidlIrisSensors = new ArrayList<>();
-
- if (hidlSensors != null) {
- for (SensorConfig sensor : hidlSensors) {
- Slog.d(TAG, "Registering HIDL ID: " + sensor.id + " Modality: " + sensor.modality
- + " Strength: " + sensor.strength);
- switch (sensor.modality) {
- case TYPE_FINGERPRINT:
- hidlFingerprintSensors.add(
- getHidlFingerprintSensorProps(sensor.id, sensor.strength));
- break;
-
- case TYPE_FACE:
- hidlFaceSensors.add(getHidlFaceSensorProps(sensor.id, sensor.strength));
- break;
-
- case TYPE_IRIS:
- hidlIrisSensors.add(getHidlIrisSensorProps(sensor.id, sensor.strength));
- break;
-
- default:
- Slog.e(TAG, "Unknown modality: " + sensor.modality);
- }
- }
- }
-
- final IFingerprintService fingerprintService = mInjector.getFingerprintService();
- if (fingerprintService != null) {
- try {
- fingerprintService.registerAuthenticators(hidlFingerprintSensors);
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
- }
- } else if (hidlFingerprintSensors.size() > 0) {
- Slog.e(TAG, "HIDL fingerprint configuration exists, but FingerprintService is null.");
- }
-
- final IFaceService faceService = mInjector.getFaceService();
- if (faceService != null) {
- try {
- faceService.registerAuthenticators(hidlFaceSensors);
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException when registering face authenticators", e);
- }
- } else if (hidlFaceSensors.size() > 0) {
- Slog.e(TAG, "HIDL face configuration exists, but FaceService is null.");
- }
-
- final IIrisService irisService = mInjector.getIrisService();
- if (irisService != null) {
- try {
- irisService.registerAuthenticators(hidlIrisSensors);
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException when registering iris authenticators", e);
- }
- } else if (hidlIrisSensors.size() > 0) {
- Slog.e(TAG, "HIDL iris configuration exists, but IrisService is null.");
- }
- }
-
private void checkInternalPermission() {
getContext().enforceCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL,
"Must have USE_BIOMETRIC_INTERNAL permission");
diff --git a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
index e578861..91cabb5 100644
--- a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
+++ b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
@@ -21,7 +21,6 @@
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.Looper;
/**
* This class provides the handler to process biometric operations.
@@ -76,11 +75,8 @@
}
private Handler getNewHandler(String tag, int priority) {
- if (Flags.deHidl()) {
- HandlerThread handlerThread = new HandlerThread(tag, priority);
- handlerThread.start();
- return new Handler(handlerThread.getLooper());
- }
- return new Handler(Looper.getMainLooper());
+ HandlerThread handlerThread = new HandlerThread(tag, priority);
+ handlerThread.start();
+ return new Handler(handlerThread.getLooper());
}
}
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index 7a9491e..92fd9cb 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -9,8 +9,8 @@
}
flag {
- name: "de_hidl"
- namespace: "biometrics_framework"
- description: "feature flag for biometrics de-hidl"
- bug: "287332354"
-}
\ No newline at end of file
+ name: "use_vhal_for_testing"
+ namespace: "biometrics_framework"
+ description: "This flag controls whether virtual HAL is used for testing instead of TestHal "
+ bug: "294254230"
+}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
index 7f04628..7a8e25b 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContext.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
@@ -81,22 +81,6 @@
boolean isHardwareIgnoringTouches();
/**
- * Subscribe to context changes.
- *
- * Note that this method only notifies for properties that are visible to the HAL.
- *
- * @param context context that will be modified when changed
- * @param consumer callback when the context is modified
- *
- * @deprecated instead use {@link BiometricContext#subscribe(OperationContextExt, Consumer,
- * Consumer, AuthenticateOptions)}
- * TODO (b/294161627): Delete this API once Flags.DE_HIDL is removed.
- */
- @Deprecated
- void subscribe(@NonNull OperationContextExt context,
- @NonNull Consumer<OperationContext> consumer);
-
- /**
* Subscribe to context changes and start the HAL operation.
*
* Note that this method only notifies for properties that are visible to the HAL.
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
index d8dfa60..a17de3d 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -229,16 +229,6 @@
@Override
public void subscribe(@NonNull OperationContextExt context,
- @NonNull Consumer<OperationContext> consumer) {
- mSubscribers.put(context, consumer);
- // TODO(b/294161627) Combine the getContext/subscribe APIs to avoid race
- if (context.getDisplayState() != getDisplayState()) {
- consumer.accept(context.update(this, context.isCrypto()).toAidlContext());
- }
- }
-
- @Override
- public void subscribe(@NonNull OperationContextExt context,
@NonNull Consumer<OperationContext> startHalConsumer,
@NonNull Consumer<OperationContext> updateContextConsumer,
@Nullable AuthenticateOptions options) {
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 4fa8741..1e2451c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -35,7 +35,6 @@
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;
@@ -117,24 +116,20 @@
@LockoutTracker.LockoutMode
public int handleFailedAttempt(int userId) {
- 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;
+ 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;
}
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 89e08c1..82d5d4d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -19,9 +19,9 @@
import static com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED;
import android.annotation.IntDef;
-import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.WorkerThread;
import android.content.Context;
import android.hardware.biometrics.IBiometricService;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -38,7 +38,6 @@
import com.android.modules.expresslog.Counter;
import com.android.server.biometrics.BiometricSchedulerProto;
import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import java.io.PrintWriter;
@@ -65,9 +64,8 @@
* @param <T> Hal instance for starting the user.
* @param <U> Session associated with the current user id.
*
- * TODO: (b/304604965) Update thread annotation when FLAGS_DE_HIDL is removed.
*/
-@MainThread
+@WorkerThread
public class BiometricScheduler<T, U> {
private static final String TAG = "BiometricScheduler";
@@ -176,7 +174,7 @@
Slog.w(TAG, "operation is already null or different (reset?): "
+ mCurrentOperation);
}
- startNextOperationIfIdle();
+ checkCurrentUserAndStartNextOperation();
});
}
}
@@ -219,7 +217,7 @@
mRecentOperations.add(mCurrentOperation.getProtoEnum());
mCurrentOperation = null;
mTotalOperationsHandled++;
- startNextOperationIfIdle();
+ checkCurrentUserAndStartNextOperation();
});
}
};
@@ -304,15 +302,7 @@
return mInternalCallback;
}
- protected void startNextOperationIfIdle() {
- if (Flags.deHidl()) {
- startNextOperation();
- } else {
- startNextOperationIfIdleLegacy();
- }
- }
-
- protected void startNextOperation() {
+ protected void checkCurrentUserAndStartNextOperation() {
if (mCurrentOperation != null) {
Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation);
return;
@@ -326,7 +316,7 @@
final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation()) {
- startNextOperationIfIdleLegacy();
+ startNextOperationIfIdle();
} else if (currentUserId == UserHandle.USER_NULL && mUserSwitchProvider != null) {
final BaseClientMonitor startClient =
mUserSwitchProvider.getStartUserClient(nextUserId);
@@ -357,7 +347,7 @@
}
}
- protected void startNextOperationIfIdleLegacy() {
+ protected void startNextOperationIfIdle() {
if (mCurrentOperation != null) {
Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation);
return;
@@ -422,7 +412,7 @@
// run these. A single request from the manager layer to the service layer may
// actually be multiple operations (i.e. updateActiveUser + authenticate).
mCurrentOperation = null;
- startNextOperationIfIdle();
+ checkCurrentUserAndStartNextOperation();
}
} else {
try {
@@ -459,7 +449,7 @@
} else {
Slog.e(TAG, "[Unable To Start] Prepared client: " + mCurrentOperation);
mCurrentOperation = null;
- startNextOperationIfIdle();
+ checkCurrentUserAndStartNextOperation();
}
}
@@ -504,7 +494,7 @@
Slog.d(TAG, "[Cancelling Interruptable]: " + mCurrentOperation);
mCurrentOperation.cancel(mHandler, mInternalCallback);
} else {
- startNextOperationIfIdle();
+ checkCurrentUserAndStartNextOperation();
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
deleted file mode 100644
index 7ca10e3..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ /dev/null
@@ -1,183 +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.biometrics.sensors;
-
-import static com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.IBiometricService;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
-
-/**
- * A user-aware scheduler that requests user-switches based on scheduled operation's targetUserId.
- * TODO (b/304604965): Remove class when Flags.FLAG_DE_HIDL is removed.
- *
- * @param <T> Hal instance for starting the user.
- * @param <U> Session associated with the current user id.
- */
-public class UserAwareBiometricScheduler<T, U> extends BiometricScheduler<T, U> {
-
- private static final String TAG = "UaBiometricScheduler";
-
- /**
- * Interface to retrieve the owner's notion of the current userId. Note that even though
- * the scheduler can determine this based on its history of processed clients, we should still
- * query the owner since it may be cleared due to things like HAL death, etc.
- */
- public interface CurrentUserRetriever {
- int getCurrentUserId();
- }
-
- public interface UserSwitchCallback {
- @NonNull StopUserClient<?> getStopUserClient(int userId);
- @NonNull StartUserClient<?, ?> getStartUserClient(int newUserId);
- }
-
- @NonNull private final CurrentUserRetriever mCurrentUserRetriever;
- @NonNull private final UserSwitchCallback mUserSwitchCallback;
- @Nullable private StopUserClient<?> mStopUserClient;
-
- private class ClientFinishedCallback implements ClientMonitorCallback {
- @NonNull private final BaseClientMonitor mOwner;
-
- ClientFinishedCallback(@NonNull BaseClientMonitor owner) {
- mOwner = owner;
- }
-
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
- mHandler.post(() -> {
- Slog.d(TAG, "[Client finished] " + clientMonitor + ", success: " + success);
-
- // Set mStopUserClient to null when StopUserClient fails. Otherwise it's possible
- // for that the queue will wait indefinitely until the field is cleared.
- if (clientMonitor instanceof StopUserClient<?>) {
- if (!success) {
- Slog.w(TAG, "StopUserClient failed(), is the HAL stuck? "
- + "Clearing mStopUserClient");
- }
- mStopUserClient = null;
- }
- if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) {
- mCurrentOperation = null;
- } else {
- // can happen if the hal dies and is usually okay
- // do not unset the current operation that may be newer
- Slog.w(TAG, "operation is already null or different (reset?): "
- + mCurrentOperation);
- }
- startNextOperationIfIdle();
- });
- }
- }
-
- @VisibleForTesting
- public UserAwareBiometricScheduler(@NonNull String tag,
- @NonNull Handler handler,
- @SensorType int sensorType,
- @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
- @NonNull IBiometricService biometricService,
- @NonNull CurrentUserRetriever currentUserRetriever,
- @NonNull UserSwitchCallback userSwitchCallback) {
- super(handler, sensorType, gestureAvailabilityDispatcher, biometricService,
- LOG_NUM_RECENT_OPERATIONS);
-
- mCurrentUserRetriever = currentUserRetriever;
- mUserSwitchCallback = userSwitchCallback;
- }
-
- public UserAwareBiometricScheduler(@NonNull String tag,
- @SensorType int sensorType,
- @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
- @NonNull CurrentUserRetriever currentUserRetriever,
- @NonNull UserSwitchCallback userSwitchCallback) {
- this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher,
- IBiometricService.Stub.asInterface(
- ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
- currentUserRetriever, userSwitchCallback);
- }
-
- @Override
- protected void startNextOperationIfIdle() {
- if (mCurrentOperation != null) {
- Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation);
- return;
- }
- if (mPendingOperations.isEmpty()) {
- Slog.d(TAG, "No operations, returning to idle");
- return;
- }
-
- final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
- final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
-
- if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation()) {
- super.startNextOperationIfIdle();
- } else if (currentUserId == UserHandle.USER_NULL) {
- final BaseClientMonitor startClient =
- mUserSwitchCallback.getStartUserClient(nextUserId);
- final ClientFinishedCallback finishedCallback =
- new ClientFinishedCallback(startClient);
-
- Slog.d(TAG, "[Starting User] " + startClient);
- mCurrentOperation = new BiometricSchedulerOperation(
- startClient, finishedCallback, STATE_STARTED);
- startClient.start(finishedCallback);
- } else {
- if (mStopUserClient != null) {
- Slog.d(TAG, "[Waiting for StopUser] " + mStopUserClient);
- } else {
- mStopUserClient = mUserSwitchCallback
- .getStopUserClient(currentUserId);
- final ClientFinishedCallback finishedCallback =
- new ClientFinishedCallback(mStopUserClient);
-
- Slog.d(TAG, "[Stopping User] current: " + currentUserId
- + ", next: " + nextUserId + ". " + mStopUserClient);
- mCurrentOperation = new BiometricSchedulerOperation(
- mStopUserClient, finishedCallback, STATE_STARTED);
- mStopUserClient.start(finishedCallback);
- }
- }
- }
-
- @Override
- public void onUserStopped() {
- if (mStopUserClient == null) {
- Slog.e(TAG, "Unexpected onUserStopped");
- return;
- }
-
- Slog.d(TAG, "[OnUserStopped]: " + mStopUserClient);
- mStopUserClient.onUserStopped();
- mStopUserClient = null;
- }
-
- @VisibleForTesting
- @Nullable public StopUserClient<?> getStopUserClient() {
- return mStopUserClient;
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index a946af8..bd6d593 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -72,9 +72,6 @@
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
-import com.android.server.biometrics.sensors.face.hidl.Face10;
-
-import com.google.android.collect.Lists;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -664,60 +661,11 @@
provider.second.scheduleGetFeature(provider.first, token, userId, feature,
new ClientMonitorCallbackConverter(receiver), opPackageName);
}
- @NonNull
- private List<ServiceProvider> getHidlProviders(
- @NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
- final List<ServiceProvider> providers = new ArrayList<>();
-
- for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
- providers.add(
- Face10.newInstance(getContext(), mBiometricStateCallback,
- mAuthenticationStateListeners, hidlSensor,
- mLockoutResetDispatcher));
- }
-
- return providers;
- }
-
- @NonNull
- private List<ServiceProvider> getAidlProviders(@NonNull List<String> instances) {
- final List<ServiceProvider> providers = new ArrayList<>();
-
- for (String instance : instances) {
- final FaceProvider provider = mFaceProvider.apply(instance);
- Slog.i(TAG, "Adding AIDL provider: " + instance);
- providers.add(provider);
- }
-
- return providers;
- }
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
public void registerAuthenticators(
- @NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
- super.registerAuthenticators_enforcePermission();
-
- mRegistry.registerAll(() -> {
- List<String> aidlSensors = new ArrayList<>();
- final String[] instances = mAidlInstanceNameSupplier.get();
- if (instances != null) {
- aidlSensors.addAll(Lists.newArrayList(instances));
- }
-
- final Pair<List<FaceSensorPropertiesInternal>, List<String>>
- filteredInstances = filterAvailableHalInstances(hidlSensors, aidlSensors);
-
- final List<ServiceProvider> providers = new ArrayList<>();
- providers.addAll(getHidlProviders(filteredInstances.first));
- providers.addAll(getAidlProviders(filteredInstances.second));
- return providers;
- });
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
- public void registerAuthenticatorsLegacy(
FaceSensorConfigurations faceSensorConfigurations) {
- super.registerAuthenticatorsLegacy_enforcePermission();
+ super.registerAuthenticators_enforcePermission();
if (!faceSensorConfigurations.hasSensorConfigurations()) {
Slog.d(TAG, "No face sensors to register.");
@@ -771,40 +719,6 @@
.getSensorPropForInstance(finalSensorInstance));
}
- private Pair<List<FaceSensorPropertiesInternal>, List<String>>
- filterAvailableHalInstances(
- @NonNull List<FaceSensorPropertiesInternal> hidlInstances,
- @NonNull List<String> aidlInstances) {
- if ((hidlInstances.size() + aidlInstances.size()) <= 1) {
- return new Pair(hidlInstances, aidlInstances);
- }
-
- if (Flags.faceVhalFeature()) {
- Slog.i(TAG, "Face VHAL feature is on");
- } else {
- Slog.i(TAG, "Face VHAL feature is off");
- }
-
- final int virtualAt = aidlInstances.indexOf("virtual");
- if (Flags.faceVhalFeature() && Utils.isFaceVirtualEnabled(getContext())) {
- if (virtualAt != -1) {
- //only virtual instance should be returned
- Slog.i(TAG, "virtual hal is used");
- return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt)));
- } else {
- Slog.e(TAG, "Could not find virtual interface while it is enabled");
- return new Pair(hidlInstances, aidlInstances);
- }
- } else {
- //remove virtual instance
- aidlInstances = new ArrayList<>(aidlInstances);
- if (virtualAt != -1) {
- aidlInstances.remove(virtualAt);
- }
- return new Pair(hidlInstances, aidlInstances);
- }
- }
-
@Override
public void addAuthenticatorsRegisteredCallback(
IFaceAuthenticatorsRegisteredCallback callback) {
@@ -883,17 +797,13 @@
return null;
};
- if (Flags.deHidl()) {
- mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
- ((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
- getContext(), mBiometricStateCallback, mAuthenticationStateListeners,
- filteredSensorProps.second,
- filteredSensorProps.first, mLockoutResetDispatcher,
- BiometricContext.getInstance(getContext()),
- resetLockoutRequiresChallenge));
- } else {
- mFaceProviderFunction = ((filteredSensorProps, resetLockoutRequiresChallenge) -> null);
- }
+ mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
+ ((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
+ getContext(), mBiometricStateCallback, mAuthenticationStateListeners,
+ filteredSensorProps.second,
+ filteredSensorProps.first, mLockoutResetDispatcher,
+ BiometricContext.getInstance(getContext()),
+ resetLockoutRequiresChallenge));
}
@Override
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
index 098be21..cf677d5 100644
--- 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
@@ -27,7 +27,6 @@
import android.hardware.keymaster.HardwareAuthToken;
import android.util.Slog;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -53,16 +52,6 @@
/**
* Interface to send results to the AidlResponseHandler's owner.
*/
- public interface HardwareUnavailableCallback {
- /**
- * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
- */
- void onHardwareUnavailable();
- }
-
- /**
- * Interface to send results to the AidlResponseHandler's owner.
- */
public interface AidlResponseHandlerCallback {
/**
* Invoked when enrollment is successful.
@@ -90,8 +79,6 @@
@NonNull
private final AuthSessionCoordinator mAuthSessionCoordinator;
@NonNull
- private final HardwareUnavailableCallback mHardwareUnavailableCallback;
- @NonNull
private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
public AidlResponseHandler(@NonNull Context context,
@@ -99,24 +86,6 @@
@NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
- @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
- this(context, scheduler, sensorId, userId, lockoutTracker, lockoutResetDispatcher,
- authSessionCoordinator, hardwareUnavailableCallback,
- new AidlResponseHandlerCallback() {
- @Override
- public void onEnrollSuccess() {}
-
- @Override
- public void onHardwareUnavailable() {}
- });
- }
-
- public AidlResponseHandler(@NonNull Context context,
- @NonNull BiometricScheduler scheduler, int sensorId, int userId,
- @NonNull LockoutTracker lockoutTracker,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull AuthSessionCoordinator authSessionCoordinator,
- @NonNull HardwareUnavailableCallback hardwareUnavailableCallback,
@NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
mContext = context;
mScheduler = scheduler;
@@ -125,7 +94,6 @@
mLockoutTracker = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mAuthSessionCoordinator = authSessionCoordinator;
- mHardwareUnavailableCallback = hardwareUnavailableCallback;
mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
}
@@ -185,11 +153,7 @@
handleResponse(ErrorConsumer.class, (c) -> {
c.onError(error, vendorCode);
if (error == Error.HW_UNAVAILABLE) {
- if (Flags.deHidl()) {
- mAidlResponseHandlerCallback.onHardwareUnavailable();
- } else {
- mHardwareUnavailableCallback.onHardwareUnavailable();
- }
+ mAidlResponseHandlerCallback.onHardwareUnavailable();
}
});
}
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 415d294..c43c7d9 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
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.NotificationManager;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
@@ -39,7 +38,6 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
-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;
@@ -70,8 +68,6 @@
private final UsageStats mUsageStats;
@NonNull
private final AuthSessionCoordinator mAuthSessionCoordinator;
- @Nullable
- private final NotificationManager mNotificationManager;
private final int[] mBiometricPromptIgnoreList;
private final int[] mBiometricPromptIgnoreListVendor;
private final int[] mKeyguardIgnoreList;
@@ -123,7 +119,6 @@
biometricStrength);
setRequestId(requestId);
mUsageStats = usageStats;
- mNotificationManager = context.getSystemService(NotificationManager.class);
mSensorPrivacyManager = sensorPrivacyManager;
mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
mAuthenticationStateListeners = authenticationStateListeners;
@@ -163,11 +158,7 @@
0 /* vendorCode */);
mCallback.onClientFinished(this, false /* success */);
} else {
- if (Flags.deHidl()) {
- startAuthenticate();
- } else {
- mCancellationSignal = doAuthenticate();
- }
+ doAuthenticate();
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting auth", e);
@@ -176,27 +167,7 @@
}
}
- private ICancellationSignal doAuthenticate() throws RemoteException {
- final AidlSession session = getFreshDaemon();
-
- if (session.hasContextMethods()) {
- final OperationContextExt opContext = getOperationContext();
- final ICancellationSignal cancel = session.getSession().authenticateWithContext(
- mOperationId, opContext.toAidlContext(getOptions()));
- getBiometricContext().subscribe(opContext, ctx -> {
- try {
- session.getSession().onContextChanged(ctx);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify context changed", e);
- }
- });
- return cancel;
- } else {
- return session.getSession().authenticate(mOperationId);
- }
- }
-
- private void startAuthenticate() throws RemoteException {
+ private void doAuthenticate() throws RemoteException {
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 5ddddda..dcd94896 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -29,7 +29,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
-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.OperationContextExt;
@@ -112,38 +111,14 @@
}
try {
- if (Flags.deHidl()) {
- startDetect();
- } else {
- mCancellationSignal = doDetectInteraction();
- }
+ doDetectInteraction();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting face detect", e);
mCallback.onClientFinished(this, false /* success */);
}
}
- private ICancellationSignal doDetectInteraction() throws RemoteException {
- final AidlSession session = getFreshDaemon();
-
- if (session.hasContextMethods()) {
- final OperationContextExt opContext = getOperationContext();
- final ICancellationSignal cancel = session.getSession().detectInteractionWithContext(
- opContext.toAidlContext(mOptions));
- getBiometricContext().subscribe(opContext, ctx -> {
- try {
- session.getSession().onContextChanged(ctx);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify context changed", e);
- }
- });
- return cancel;
- } else {
- return session.getSession().detectInteraction();
- }
- }
-
- private void startDetect() throws RemoteException {
+ private void doDetectInteraction() throws RemoteException {
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
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 781e3f4..73e8ece 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
@@ -36,7 +36,6 @@
import android.view.Surface;
import com.android.internal.R;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
@@ -193,11 +192,7 @@
features[i] = featureList.get(i);
}
- if (Flags.deHidl()) {
- startEnroll(features);
- } else {
- mCancellationSignal = doEnroll(features);
- }
+ doEnroll(features);
} catch (RemoteException | IllegalArgumentException e) {
Slog.e(TAG, "Exception when requesting enroll", e);
onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
@@ -205,43 +200,7 @@
}
}
- private ICancellationSignal doEnroll(byte[] features) throws RemoteException {
- final AidlSession session = getFreshDaemon();
- final HardwareAuthToken hat =
- HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
-
- if (session.hasContextMethods()) {
- final OperationContextExt opContext = getOperationContext();
- ICancellationSignal cancel;
- if (session.supportsFaceEnrollOptions()) {
- FaceEnrollOptions options = new FaceEnrollOptions();
- options.hardwareAuthToken = hat;
- options.enrollmentType = EnrollmentType.DEFAULT;
- options.features = features;
- options.nativeHandlePreview = null;
- options.context = opContext.toAidlContext();
- options.surfacePreview = mPreviewSurface;
- cancel = session.getSession().enrollWithOptions(options);
- } else {
- cancel = session.getSession().enrollWithContext(
- hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle,
- opContext.toAidlContext());
- }
- getBiometricContext().subscribe(opContext, ctx -> {
- try {
- session.getSession().onContextChanged(ctx);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify context changed", e);
- }
- });
- return cancel;
- } else {
- return session.getSession().enroll(hat, EnrollmentType.DEFAULT, features,
- mHwPreviewHandle);
- }
- }
-
- private void startEnroll(byte[] features) throws RemoteException {
+ private void doEnroll(byte[] features) throws RemoteException {
final AidlSession session = getFreshDaemon();
final HardwareAuthToken hat =
HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
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 11db183..75b4fd3 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
@@ -27,11 +27,9 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.common.ComponentInfo;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.SensorProps;
import android.hardware.face.Face;
@@ -43,7 +41,6 @@
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -56,7 +53,6 @@
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.BiometricHandlerProvider;
-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;
@@ -193,11 +189,7 @@
mAuthenticationStateListeners = authenticationStateListeners;
mHalInstanceName = halInstanceName;
mFaceSensors = new SensorList<>(ActivityManager.getService());
- if (Flags.deHidl()) {
- mHandler = biometricHandlerProvider.getFaceHandler();
- } else {
- mHandler = new Handler(Looper.getMainLooper());
- }
+ mHandler = biometricHandlerProvider.getFaceHandler();
mUsageStats = new UsageStats(context);
mLockoutResetDispatcher = lockoutResetDispatcher;
mActivityTaskManager = ActivityTaskManager.getInstance();
@@ -223,50 +215,15 @@
}
private void initSensors(boolean resetLockoutRequiresChallenge, SensorProps[] props) {
- if (Flags.deHidl()) {
- if (resetLockoutRequiresChallenge) {
- Slog.d(getTag(), "Adding HIDL configs");
- for (SensorProps prop : props) {
- addHidlSensors(prop, resetLockoutRequiresChallenge);
- }
- } else {
- Slog.d(getTag(), "Adding AIDL configs");
- for (SensorProps prop : props) {
- addAidlSensors(prop, resetLockoutRequiresChallenge);
- }
+ if (resetLockoutRequiresChallenge) {
+ Slog.d(getTag(), "Adding HIDL configs");
+ for (SensorProps prop : props) {
+ addHidlSensors(prop, resetLockoutRequiresChallenge);
}
} else {
+ Slog.d(getTag(), "Adding AIDL configs");
for (SensorProps prop : props) {
- final int sensorId = prop.commonProps.sensorId;
-
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- if (prop.commonProps.componentInfo != null) {
- for (ComponentInfo info : prop.commonProps.componentInfo) {
- componentInfo.add(new ComponentInfoInternal(info.componentId,
- info.hardwareVersion, info.firmwareVersion, info.serialNumber,
- info.softwareVersion));
- }
- }
-
- final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
- prop.commonProps.sensorId, prop.commonProps.sensorStrength,
- prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
- prop.supportsDetectInteraction, prop.halControlsPreview,
- false /* resetLockoutRequiresChallenge */);
- final Sensor sensor = new Sensor(this,
- mContext, mHandler, internalProp,
- mBiometricContext);
- sensor.init(mLockoutResetDispatcher, this);
- final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
- sensor.getLazySession().get().getUserId();
- mFaceSensors.addSensor(sensorId, sensor, userId,
- new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
- }
- });
- Slog.d(getTag(), "Added: " + internalProp);
+ addAidlSensors(prop, resetLockoutRequiresChallenge);
}
}
}
@@ -477,12 +434,7 @@
@Override
public int getLockoutModeForUser(int sensorId, int userId) {
- if (Flags.deHidl()) {
- return mFaceSensors.get(sensorId).getLockoutModeForUser(userId);
- } else {
- return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
- Utils.getCurrentStrength(sensorId));
- }
+ return mFaceSensors.get(sensorId).getLockoutModeForUser(userId);
}
@Override
@@ -492,11 +444,7 @@
@Override
public boolean isHardwareDetected(int sensorId) {
- if (Flags.deHidl()) {
- return mFaceSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
- } else {
- return hasHalInstance();
- }
+ return mFaceSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
}
@Override
@@ -549,23 +497,7 @@
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext, maxTemplatesPerUser, debugConsent, options);
- if (Flags.deHidl()) {
- scheduleForSensor(sensorId, client, mBiometricStateCallback);
- } else {
- scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
- mBiometricStateCallback, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- ClientMonitorCallback.super.onClientFinished(clientMonitor,
- success);
- if (success) {
- scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
- }
- }));
- }
+ scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
return id;
}
@@ -614,12 +546,8 @@
final int sensorId = options.getSensorId();
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
- final LockoutTracker lockoutTracker;
- if (Flags.deHidl()) {
- lockoutTracker = mFaceSensors.get(sensorId).getLockoutTracker(true /* forAuth */);
- } else {
- lockoutTracker = null;
- }
+ final LockoutTracker lockoutTracker = mFaceSensors.get(sensorId).getLockoutTracker(
+ true /* forAuth */);
final FaceAuthenticationClient client = new FaceAuthenticationClient(
mContext, mFaceSensors.get(sensorId).getLazySession(), token, requestId,
callback, operationId, restricted, options, cookie,
@@ -634,29 +562,18 @@
@Override
public void onClientStarted(
BaseClientMonitor clientMonitor) {
- if (Flags.deHidl()) {
- mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
- mAuthSessionCoordinator.authStartedFor(userId, sensorId,
- requestId));
- } else {
- mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
- }
+ mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+ mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId));
}
@Override
public void onClientFinished(
BaseClientMonitor clientMonitor,
boolean success) {
- if (Flags.deHidl()) {
- mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
- mAuthSessionCoordinator.authEndedFor(userId,
- Utils.getCurrentStrength(sensorId), sensorId, requestId,
- client.wasAuthSuccessful()));
- } else {
- mAuthSessionCoordinator.authEndedFor(userId,
- Utils.getCurrentStrength(sensorId),
- sensorId, requestId, client.wasAuthSuccessful());
- }
+ mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+ mAuthSessionCoordinator.authEndedFor(userId,
+ Utils.getCurrentStrength(sensorId), sensorId, requestId,
+ client.wasAuthSuccessful()));
}
});
});
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 635e79a..6b99493 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
@@ -42,7 +42,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -57,7 +56,6 @@
import com.android.server.biometrics.sensors.LockoutTracker;
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.UserSwitchProvider;
import com.android.server.biometrics.sensors.face.FaceUtils;
@@ -89,7 +87,6 @@
@Nullable AidlSession mCurrentSession;
@NonNull BiometricContext mBiometricContext;
-
Sensor(@NonNull FaceProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
@NonNull BiometricContext biometricContext) {
@@ -116,11 +113,7 @@
*/
public void init(@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull FaceProvider provider) {
- if (Flags.deHidl()) {
- setScheduler(getBiometricSchedulerForInit(lockoutResetDispatcher, provider));
- } else {
- setScheduler(getUserAwareBiometricSchedulerForInit(lockoutResetDispatcher, provider));
- }
+ setScheduler(getBiometricSchedulerForInit(lockoutResetDispatcher, provider));
mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
mLockoutTracker = new LockoutCache();
}
@@ -149,8 +142,7 @@
final AidlResponseHandler resultController = new AidlResponseHandler(
mContext, mScheduler, sensorId, newUserId,
mLockoutTracker, lockoutResetDispatcher,
- mBiometricContext.getAuthSessionCoordinator(), () -> {
- },
+ mBiometricContext.getAuthSessionCoordinator(),
new AidlResponseHandler.AidlResponseHandlerCallback() {
@Override
public void onEnrollSuccess() {
@@ -173,40 +165,6 @@
});
}
- private UserAwareBiometricScheduler<IFace, ISession> getUserAwareBiometricSchedulerForInit(
- LockoutResetDispatcher lockoutResetDispatcher,
- FaceProvider provider) {
- return new UserAwareBiometricScheduler<>(TAG,
- BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
- () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
- new UserAwareBiometricScheduler.UserSwitchCallback() {
- @NonNull
- @Override
- public StopUserClient<ISession> getStopUserClient(int userId) {
- return new FaceStopUserClient(mContext,
- () -> mLazySession.get().getSession(), mToken, userId,
- mSensorProperties.sensorId, BiometricLogger.ofUnknown(mContext),
- mBiometricContext, () -> mCurrentSession = null);
- }
-
- @NonNull
- @Override
- public StartUserClient<IFace, ISession> getStartUserClient(int newUserId) {
- final int sensorId = mSensorProperties.sensorId;
- final AidlResponseHandler resultController = new AidlResponseHandler(
- mContext, mScheduler, sensorId, newUserId,
- mLockoutTracker, lockoutResetDispatcher,
- mBiometricContext.getAuthSessionCoordinator(), () -> {
- Slog.e(TAG, "Face sensor hardware unavailable.");
- mCurrentSession = null;
- });
-
- return Sensor.this.getStartUserClient(resultController, sensorId,
- newUserId, provider);
- }
- });
- }
-
private FaceStartUserClient getStartUserClient(@NonNull AidlResponseHandler resultController,
int sensorId, int newUserId, @NonNull FaceProvider provider) {
final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
deleted file mode 100644
index 0e2367a..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ /dev/null
@@ -1,239 +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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.ITestSession;
-import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.face.Face;
-import android.hardware.face.FaceAuthenticationFrame;
-import android.hardware.face.FaceEnrollFrame;
-import android.hardware.face.FaceEnrollOptions;
-import android.hardware.face.IFaceServiceReceiver;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.face.FaceUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-
-public class BiometricTestSessionImpl extends ITestSession.Stub {
- private static final String TAG = "BiometricTestSessionImpl";
-
- @NonNull private final Context mContext;
- private final int mSensorId;
- @NonNull private final ITestSessionCallback mCallback;
- @NonNull private final Face10 mFace10;
- @NonNull private final Face10.HalResultController mHalResultController;
- @NonNull private final Set<Integer> mEnrollmentIds;
- @NonNull private final Random mRandom;
-
-
- private final IFaceServiceReceiver mReceiver = new IFaceServiceReceiver.Stub() {
- @Override
- public void onEnrollResult(Face face, int remaining) {
-
- }
-
- @Override
- public void onAcquired(int acquiredInfo, int vendorCode) {
-
- }
-
- @Override
- public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) {
-
- }
-
- @Override
- public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
-
- }
-
- @Override
- public void onAuthenticationFailed() {
-
- }
-
- @Override
- public void onError(int error, int vendorCode) {
-
- }
-
- @Override
- public void onRemoved(Face face, int remaining) {
-
- }
-
- @Override
- public void onFeatureSet(boolean success, int feature) {
-
- }
-
- @Override
- public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
-
- }
-
- @Override
- public void onChallengeGenerated(int sensorId, int userId, long challenge) {
-
- }
-
- @Override
- public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
-
- }
-
- @Override
- public void onEnrollmentFrame(FaceEnrollFrame frame) {
-
- }
- };
-
- BiometricTestSessionImpl(@NonNull Context context, int sensorId,
- @NonNull ITestSessionCallback callback,
- @NonNull Face10 face10,
- @NonNull Face10.HalResultController halResultController) {
- mContext = context;
- mSensorId = sensorId;
- mCallback = callback;
- mFace10 = face10;
- mHalResultController = halResultController;
- mEnrollmentIds = new HashSet<>();
- mRandom = new Random();
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void setTestHalEnabled(boolean enabled) {
-
- super.setTestHalEnabled_enforcePermission();
-
- mFace10.setTestHalEnabled(enabled);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void startEnroll(int userId) {
-
- super.startEnroll_enforcePermission();
-
- mFace10.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
- mContext.getOpPackageName(), new int[0] /* disabledFeatures */,
- null /* previewSurface */, false /* debugConsent */,
- (new FaceEnrollOptions.Builder()).build());
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void finishEnroll(int userId) {
-
- super.finishEnroll_enforcePermission();
-
- int nextRandomId = mRandom.nextInt();
- while (mEnrollmentIds.contains(nextRandomId)) {
- nextRandomId = mRandom.nextInt();
- }
-
- mEnrollmentIds.add(nextRandomId);
- mHalResultController.onEnrollResult(0 /* deviceId */,
- nextRandomId /* faceId */, userId, 0);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void acceptAuthentication(int userId) {
-
- // Fake authentication with any of the existing fingers
- super.acceptAuthentication_enforcePermission();
-
- List<Face> faces = FaceUtils.getLegacyInstance(mSensorId)
- .getBiometricsForUser(mContext, userId);
- if (faces.isEmpty()) {
- Slog.w(TAG, "No faces, returning");
- return;
- }
- final int fid = faces.get(0).getBiometricId();
- final ArrayList<Byte> hat = new ArrayList<>(Collections.nCopies(69, (byte) 0));
- mHalResultController.onAuthenticated(0 /* deviceId */, fid, userId, hat);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void rejectAuthentication(int userId) {
-
- super.rejectAuthentication_enforcePermission();
-
- mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* faceId */, userId, null);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void notifyAcquired(int userId, int acquireInfo) {
-
- super.notifyAcquired_enforcePermission();
-
- mHalResultController.onAcquired(0 /* deviceId */, userId, acquireInfo, 0 /* vendorCode */);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void notifyError(int userId, int errorCode) {
-
- super.notifyError_enforcePermission();
-
- mHalResultController.onError(0 /* deviceId */, userId, errorCode, 0 /* vendorCode */);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void cleanupInternalState(int userId) {
-
- super.cleanupInternalState_enforcePermission();
-
- mFace10.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
- @Override
- public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- try {
- mCallback.onCleanupStarted(clientMonitor.getTargetUserId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }
-
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- try {
- mCallback.onCleanupFinished(clientMonitor.getTargetUserId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }
- });
- }
-}
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
deleted file mode 100644
index 306ddfa..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ /dev/null
@@ -1,1342 +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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.SynchronousUserSwitchObserver;
-import android.app.UserSwitchObserver;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.ITestSession;
-import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
-import android.hardware.face.Face;
-import android.hardware.face.FaceAuthenticateOptions;
-import android.hardware.face.FaceEnrollOptions;
-import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.face.IFaceServiceReceiver;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IHwBinder;
-import android.os.Looper;
-import android.os.NativeHandle;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.Surface;
-
-import com.android.internal.annotations.VisibleForTesting;
-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;
-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.AuthenticationStateListeners;
-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.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-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.LockoutResetDispatcher;
-import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.RemovalConsumer;
-import com.android.server.biometrics.sensors.face.FaceUtils;
-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;
-import org.json.JSONObject;
-
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.time.Clock;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.function.Supplier;
-
-/**
- * Supports a single instance of the {@link android.hardware.biometrics.face.V1_0} or its extended
- * minor versions.
- */
-public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
-
- private static final String TAG = "Face10";
-
- private static final int ENROLL_TIMEOUT_SEC = 75;
- private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000;
- private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS =
- FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC * 1000;
- @VisibleForTesting
- public static Clock sSystemClock = Clock.systemUTC();
-
- private boolean mTestHalEnabled;
-
- @NonNull private final FaceSensorPropertiesInternal mSensorProperties;
- @NonNull private final BiometricStateCallback mBiometricStateCallback;
- @NonNull
- private final AuthenticationStateListeners mAuthenticationStateListeners;
- @NonNull private final Context mContext;
- @NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler;
- @NonNull private final Handler mHandler;
- @NonNull private final Supplier<IBiometricsFace> mLazyDaemon;
- @NonNull private final LockoutHalImpl mLockoutTracker;
- @NonNull private final UsageStats mUsageStats;
- @NonNull private final Map<Integer, Long> mAuthenticatorIds;
- @Nullable private IBiometricsFace mDaemon;
- @NonNull private final HalResultController mHalResultController;
- @NonNull private final BiometricContext mBiometricContext;
- @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector;
- // for requests that do not use biometric prompt
- @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
- 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
- public void onUserSwitching(int newUserId) {
- scheduleInternalCleanup(newUserId, null /* callback */);
- scheduleGetFeature(mSensorId, new Binder(), newUserId,
- BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION,
- null, mContext.getOpPackageName());
- }
- };
-
- public static class HalResultController extends IBiometricsFaceClientCallback.Stub {
- /**
- * Interface to sends results to the HalResultController's owner.
- */
- public interface Callback {
- /**
- * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
- */
- void onHardwareUnavailable();
- }
-
- private final int mSensorId;
- @NonNull private final Context mContext;
- @NonNull private final Handler mHandler;
- @NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler;
- @Nullable private Callback mCallback;
- @NonNull private final LockoutHalImpl mLockoutTracker;
- @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
-
-
- HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
- @NonNull BiometricScheduler<IBiometricsFace, AidlSession> scheduler,
- @NonNull LockoutHalImpl lockoutTracker,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
- mSensorId = sensorId;
- mContext = context;
- mHandler = handler;
- mScheduler = scheduler;
- mLockoutTracker = lockoutTracker;
- mLockoutResetDispatcher = lockoutResetDispatcher;
- }
-
- public void setCallback(@Nullable Callback callback) {
- mCallback = callback;
- }
-
- @Override
- public void onEnrollResult(long deviceId, int faceId, int userId, int remaining) {
- mHandler.post(() -> {
- final CharSequence name = FaceUtils.getLegacyInstance(mSensorId)
- .getUniqueName(mContext, userId);
- final Face face = new Face(name, faceId, deviceId);
-
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceEnrollClient)) {
- Slog.e(TAG, "onEnrollResult for non-enroll client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FaceEnrollClient enrollClient = (FaceEnrollClient) client;
- enrollClient.onEnrollResult(face, remaining);
- });
- }
-
- @Override
- public void onAuthenticated(long deviceId, int faceId, int userId,
- ArrayList<Byte> token) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(TAG, "onAuthenticated for non-authentication consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- final boolean authenticated = faceId != 0;
- final Face face = new Face("", faceId, deviceId);
- authenticationConsumer.onAuthenticated(face, authenticated, token);
- });
- }
-
- @Override
- public void onAcquired(long deviceId, int userId, int acquiredInfo,
- int vendorCode) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AcquisitionClient)) {
- Slog.e(TAG, "onAcquired for non-acquire client: "
- + Utils.getClientName(client));
- return;
- }
-
- final AcquisitionClient<?> acquisitionClient =
- (AcquisitionClient<?>) client;
- acquisitionClient.onAcquired(acquiredInfo, vendorCode);
- });
- }
-
- @Override
- public void onError(long deviceId, int userId, int error, int vendorCode) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- Slog.d(TAG, "handleError"
- + ", client: " + (client != null ? client.getOwnerString() : null)
- + ", error: " + error
- + ", vendorCode: " + vendorCode);
- if (!(client instanceof ErrorConsumer)) {
- Slog.e(TAG, "onError for non-error consumer: " + Utils.getClientName(
- client));
- return;
- }
-
- final ErrorConsumer errorConsumer = (ErrorConsumer) client;
- errorConsumer.onError(error, vendorCode);
-
- if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
- Slog.e(TAG, "Got ERROR_HW_UNAVAILABLE");
- if (mCallback != null) {
- mCallback.onHardwareUnavailable();
- }
- }
- });
- }
-
- @Override
- public void onRemoved(long deviceId, ArrayList<Integer> removed, int userId) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof RemovalConsumer)) {
- Slog.e(TAG, "onRemoved for non-removal consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final RemovalConsumer removalConsumer = (RemovalConsumer) client;
-
- if (!removed.isEmpty()) {
- // Convert to old fingerprint-like behavior, where remove() receives
- // one removal at a time. This way, remove can share some more common code.
- for (int i = 0; i < removed.size(); i++) {
- final int id = removed.get(i);
- final Face face = new Face("", id, deviceId);
- final int remaining = removed.size() - i - 1;
- Slog.d(TAG, "Removed, faceId: " + id + ", remaining: " + remaining);
- removalConsumer.onRemoved(face, remaining);
- }
- } else {
- removalConsumer.onRemoved(null, 0 /* remaining */);
- }
-
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, UserHandle.USER_CURRENT);
- });
- }
-
- @Override
- public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof EnumerateConsumer)) {
- Slog.e(TAG, "onEnumerate for non-enumerate consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;
-
- if (!faceIds.isEmpty()) {
- // Convert to old fingerprint-like behavior, where enumerate() receives one
- // template at a time. This way, enumerate can share some more common code.
- for (int i = 0; i < faceIds.size(); i++) {
- final Face face = new Face("", faceIds.get(i), deviceId);
- enumerateConsumer.onEnumerationResult(face, faceIds.size() - i - 1);
- }
- } else {
- // For face, the HIDL contract is to receive an empty list when there are no
- // templates enrolled. Send a null identifier since we don't consume them
- // anywhere, and send remaining == 0 so this code can be shared with [email protected]
- enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
- }
- });
- }
-
- @Override
- public void onLockoutChanged(long duration) {
- mHandler.post(() -> {
- Slog.d(TAG, "onLockoutChanged: " + duration);
- final @LockoutTracker.LockoutMode 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;
- }
-
- mLockoutTracker.setCurrentUserLockoutMode(lockoutMode);
-
- if (duration == 0) {
- mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
- }
- });
- }
- }
-
- @VisibleForTesting
- Face10(@NonNull Context context,
- @NonNull BiometricStateCallback biometricStateCallback,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- @NonNull FaceSensorPropertiesInternal sensorProps,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull Handler handler,
- @NonNull BiometricScheduler<IBiometricsFace, AidlSession> scheduler,
- @NonNull BiometricContext biometricContext) {
- mSensorProperties = sensorProps;
- mContext = context;
- mBiometricStateCallback = biometricStateCallback;
- mAuthenticationStateListeners = authenticationStateListeners;
- mSensorId = sensorProps.sensorId;
- mScheduler = scheduler;
- mHandler = handler;
- mBiometricContext = biometricContext;
- mUsageStats = new UsageStats(context);
- mAuthenticatorIds = new HashMap<>();
- mLazyDaemon = Face10.this::getDaemon;
- mLockoutTracker = new LockoutHalImpl();
- mHalResultController = new HalResultController(sensorProps.sensorId, context, mHandler,
- mScheduler, mLockoutTracker, lockoutResetDispatcher);
- mLockoutResetDispatcher = lockoutResetDispatcher;
- mHalResultController.setCallback(() -> {
- mDaemon = null;
- mCurrentUserId = UserHandle.USER_NULL;
- });
-
- AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
- new AuthenticationStatsBroadcastReceiver(
- mContext,
- BiometricsProtoEnums.MODALITY_FACE,
- (AuthenticationStatsCollector collector) -> {
- Slog.d(TAG, "Initializing AuthenticationStatsCollector");
- mAuthenticationStatsCollector = collector;
- });
-
- try {
- ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to register user switch observer");
- }
- }
-
- public static Face10 newInstance(@NonNull Context context,
- @NonNull BiometricStateCallback biometricStateCallback,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- @NonNull FaceSensorPropertiesInternal sensorProps,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
- final Handler handler = new Handler(Looper.getMainLooper());
- return new Face10(context, biometricStateCallback, authenticationStateListeners,
- sensorProps, lockoutResetDispatcher, handler, new BiometricScheduler<>(
- BiometricScheduler.SENSOR_TYPE_FACE,
- null /* gestureAvailabilityTracker */),
- BiometricContext.getInstance(context));
- }
-
- @Override
- public void serviceDied(long cookie) {
- Slog.e(TAG, "HAL died");
- mHandler.post(() -> {
- PerformanceTracker.getInstanceForSensorId(mSensorId)
- .incrementHALDeathCount();
- mDaemon = null;
- mCurrentUserId = UserHandle.USER_NULL;
-
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (client instanceof ErrorConsumer) {
- Slog.e(TAG, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
- final ErrorConsumer errorConsumer = (ErrorConsumer) client;
- errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
-
- FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
- BiometricsProtoEnums.MODALITY_FACE,
- BiometricsProtoEnums.ISSUE_HAL_DEATH,
- -1 /* sensorId */);
- }
-
- mScheduler.recordCrashState();
- mScheduler.reset();
- });
- }
-
- 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);
- testHal.setCallback(mHalResultController);
- return testHal;
- }
-
- if (mDaemon != null) {
- return mDaemon;
- }
-
- Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
- + mScheduler.getCurrentClient());
-
- try {
- mDaemon = IBiometricsFace.getService();
- } catch (java.util.NoSuchElementException e) {
- // Service doesn't exist or cannot be opened.
- Slog.w(TAG, "NoSuchElementException", e);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to get face HAL", e);
- }
-
- if (mDaemon == null) {
- Slog.w(TAG, "Face HAL not available");
- return null;
- }
-
- mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
-
- // HAL ID for these HIDL versions are only used to determine if callbacks have been
- // successfully set.
- long halId = 0;
- try {
- halId = mDaemon.setCallback(mHalResultController).value;
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to set callback for face HAL", e);
- mDaemon = null;
- }
-
- Slog.d(TAG, "Face HAL ready, HAL ID: " + halId);
- if (halId != 0) {
- scheduleLoadAuthenticatorIds();
- scheduleInternalCleanup(ActivityManager.getCurrentUser(), null /* callback */);
- scheduleGetFeature(mSensorId, new Binder(),
- ActivityManager.getCurrentUser(),
- BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION, null,
- mContext.getOpPackageName());
- } else {
- Slog.e(TAG, "Unable to set callback");
- mDaemon = null;
- }
-
- return mDaemon;
- }
-
- @Override
- public boolean containsSensor(int sensorId) {
- return mSensorId == sensorId;
- }
-
- @Override
- @NonNull
- public List<FaceSensorPropertiesInternal> getSensorProperties() {
- final List<FaceSensorPropertiesInternal> properties = new ArrayList<>();
- properties.add(mSensorProperties);
- return properties;
- }
-
- @NonNull
- @Override
- public FaceSensorPropertiesInternal getSensorProperties(int sensorId) {
- return mSensorProperties;
- }
-
- @Override
- @NonNull
- public List<Face> getEnrolledFaces(int sensorId, int userId) {
- return FaceUtils.getLegacyInstance(mSensorId).getBiometricsForUser(mContext, userId);
- }
-
- @Override
- public boolean hasEnrollments(int sensorId, int userId) {
- return !getEnrolledFaces(sensorId, userId).isEmpty();
- }
-
- @Override
- @LockoutTracker.LockoutMode
- public int getLockoutModeForUser(int sensorId, int userId) {
- return mLockoutTracker.getLockoutModeForUser(userId);
- }
-
- @Override
- public long getAuthenticatorId(int sensorId, int userId) {
- return mAuthenticatorIds.getOrDefault(userId, 0L);
- }
-
- @Override
- public boolean isHardwareDetected(int sensorId) {
- return getDaemon() != null;
- }
-
- private boolean isGeneratedChallengeCacheValid() {
- return mGeneratedChallengeCache != null
- && sSystemClock.millis() - mGeneratedChallengeCache.getCreatedAt()
- < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
- }
-
- private void incrementChallengeCount() {
- mGeneratedChallengeCount.add(0, sSystemClock.millis());
- }
-
- private int decrementChallengeCount() {
- final long now = sSystemClock.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();
- }
-
- /**
- * {@link IBiometricsFace} only supports a single in-flight challenge but there are cases where
- * two callers both need challenges (e.g. resetLockout right before enrollment).
- */
- @Override
- public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
- @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- 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);
- }
- }
- });
- }
-
- @Override
- public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
- @NonNull String opPackageName, long challenge) {
- mHandler.post(() -> {
- 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.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);
- }
- }
- });
- }
-
- @Override
- public long scheduleEnroll(int sensorId, @NonNull IBinder token,
- @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
- @NonNull String opPackageName, @NonNull int[] disabledFeatures,
- @Nullable Surface previewSurface, boolean debugConsent,
- @NonNull FaceEnrollOptions options) {
- final long id = mRequestCounter.incrementAndGet();
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
- if (Flags.deHidl()) {
- scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver,
- opPackageName, disabledFeatures, previewSurface, id, options);
- } else {
- scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver,
- opPackageName, disabledFeatures, previewSurface, id, options);
- }
- });
- 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,
- @NonNull FaceEnrollOptions options) {
- 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, options);
-
- 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, FaceEnrollOptions options) {
- final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, 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, options);
- 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());
- }
- }
- });
- }
-
- @Override
- public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
- mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
- }
-
- @Override
- public long scheduleFaceDetect(@NonNull IBinder token,
- @NonNull ClientMonitorCallbackConverter callback,
- @NonNull FaceAuthenticateOptions options, int statsClient) {
- throw new IllegalStateException("Face detect not supported by [email protected]. Did you"
- + "forget to check the supportsFaceDetection flag?");
- }
-
- @Override
- public void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId) {
- throw new IllegalStateException("Face detect not supported by [email protected]. Did you"
- + "forget to check the supportsFaceDetection flag?");
- }
-
- @Override
- public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
- int cookie, @NonNull ClientMonitorCallbackConverter receiver,
- @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
- int statsClient, boolean allowBackgroundAuthentication) {
- mHandler.post(() -> {
- final int userId = options.getUserId();
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
- 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),
- mAuthenticationStateListeners);
- 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),
- mAuthenticationStateListeners);
- mScheduler.scheduleClientMonitor(client);
- }
-
- @Override
- public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
- int cookie, @NonNull ClientMonitorCallbackConverter receiver,
- @NonNull FaceAuthenticateOptions options, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication) {
- final long id = mRequestCounter.incrementAndGet();
-
- scheduleAuthenticate(token, operationId, cookie, receiver,
- options, id, restricted, statsClient, allowBackgroundAuthentication);
-
- return id;
- }
-
- @Override
- public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
- mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token, requestId));
- }
-
- @Override
- public void scheduleRemove(int sensorId, @NonNull IBinder token, int faceId, int userId,
- @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- if (Flags.deHidl()) {
- scheduleRemoveAidl(token, userId, receiver, opPackageName, faceId);
- } else {
- scheduleRemoveHidl(token, userId, receiver, opPackageName, faceId);
- }
- });
- }
-
- @Override
- public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId,
- @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- // For [email protected], remove(0) means remove all enrollments
- 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(() -> {
- if (getEnrolledFaces(sensorId, userId).isEmpty()) {
- Slog.w(TAG, "Ignoring lockout reset, no templates enrolled for user: " + userId);
- return;
- }
-
- scheduleUpdateActiveUserWithoutHandler(userId);
- 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(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
- 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(() -> {
- scheduleUpdateActiveUserWithoutHandler(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);
- 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) {
- scheduleInternalCleanup(userId, mBiometricStateCallback);
- }
-
- @Override
- public void scheduleInternalCleanup(int sensorId, int userId,
- @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
- scheduleInternalCleanup(userId, callback);
- }
-
- @Override
- public void startPreparedClient(int sensorId, int cookie) {
- mHandler.post(() -> {
- mScheduler.startPreparedClient(cookie);
- });
- }
-
- @Override
- public void dumpProtoState(int sensorId, ProtoOutputStream proto,
- boolean clearSchedulerBuffer) {
- final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
-
- proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
- proto.write(SensorStateProto.MODALITY, SensorStateProto.FACE);
- proto.write(SensorStateProto.CURRENT_STRENGTH,
- Utils.getCurrentStrength(mSensorProperties.sensorId));
- proto.write(SensorStateProto.SCHEDULER, mScheduler.dumpProtoState(clearSchedulerBuffer));
-
- for (UserInfo user : UserManager.get(mContext).getUsers()) {
- final int userId = user.getUserHandle().getIdentifier();
-
- final long userToken = proto.start(SensorStateProto.USER_STATES);
- proto.write(UserStateProto.USER_ID, userId);
- proto.write(UserStateProto.NUM_ENROLLED, FaceUtils.getLegacyInstance(mSensorId)
- .getBiometricsForUser(mContext, userId).size());
- proto.end(userToken);
- }
-
- proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_HARDWARE_AUTH_TOKEN,
- mSensorProperties.resetLockoutRequiresHardwareAuthToken);
- proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_CHALLENGE,
- mSensorProperties.resetLockoutRequiresChallenge);
-
- proto.end(sensorToken);
- }
-
- @Override
- public void dumpProtoMetrics(int sensorId, FileDescriptor fd) {
- }
-
- @Override
- public void dumpInternal(int sensorId, PrintWriter pw) {
- PerformanceTracker performanceTracker =
- PerformanceTracker.getInstanceForSensorId(mSensorId);
-
- JSONObject dump = new JSONObject();
- try {
- dump.put("service", TAG);
-
- JSONArray sets = new JSONArray();
- for (UserInfo user : UserManager.get(mContext).getUsers()) {
- final int userId = user.getUserHandle().getIdentifier();
- final int c = FaceUtils.getLegacyInstance(mSensorId)
- .getBiometricsForUser(mContext, userId).size();
- JSONObject set = new JSONObject();
- set.put("id", userId);
- set.put("count", c);
- set.put("accept", performanceTracker.getAcceptForUser(userId));
- set.put("reject", performanceTracker.getRejectForUser(userId));
- set.put("acquire", performanceTracker.getAcquireForUser(userId));
- set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
- set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
- // cryptoStats measures statistics about secure face transactions
- // (e.g. to unlock password storage, make secure purchases, etc.)
- set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
- set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
- set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
- sets.put(set);
- }
-
- dump.put("prints", sets);
- } catch (JSONException e) {
- Slog.e(TAG, "dump formatting failure", e);
- }
- pw.println(dump);
- pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
-
- mScheduler.dump(pw);
- mUsageStats.print(pw);
- }
-
- private void scheduleLoadAuthenticatorIds() {
- // Note that this can be performed on the scheduler (as opposed to being done immediately
- // when the HAL is (re)loaded, since
- // 1) If this is truly the first time it's being performed (e.g. system has just started),
- // this will be run very early and way before any applications need to generate keys.
- // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has
- // just been reloaded), the framework already has a cache of the authenticatorIds. This
- // is safe because authenticatorIds only change when A) new template has been enrolled,
- // or B) all templates are removed.
- mHandler.post(() -> {
- for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
- final int targetUserId = user.id;
- if (!mAuthenticatorIds.containsKey(targetUserId)) {
- scheduleUpdateActiveUserWithoutHandler(targetUserId);
- }
- }
- });
- }
-
- /**
- * Schedules the {@link FaceUpdateActiveUserClient} without posting the work onto the handler.
- * Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
- * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
- * this operation on the same lambda/runnable as those operations so that the ordering is
- * correct.
- */
- private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
- final boolean hasEnrolled = !getEnrolledFaces(mSensorId, targetUserId).isEmpty();
- final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
- mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, hasEnrolled, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- 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);
- }
- }
- });
- }
-
- private BiometricLogger createLogger(int statsAction, int statsClient,
- AuthenticationStatsCollector authenticationStatsCollector) {
- return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
- statsAction, statsClient, authenticationStatsCollector);
- }
-
- /**
- * Sends a debug message to the HAL with the provided FileDescriptor and arguments.
- */
- public void dumpHal(int sensorId, @NonNull FileDescriptor fd, @NonNull String[] args) {
- // WARNING: CDD restricts image data from leaving TEE unencrypted on
- // production devices:
- // [C-1-10] MUST not allow unencrypted access to identifiable biometric
- // data or any data derived from it (such as embeddings) to the
- // Application Processor outside the context of the TEE.
- // As such, this API should only be enabled for testing purposes on
- // engineering and userdebug builds. All modules in the software stack
- // MUST enforce final build products do NOT have this functionality.
- // Additionally, the following check MUST NOT be removed.
- if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
- return;
- }
-
- // Additionally, this flag allows turning off face for a device
- // (either permanently through the build or on an individual device).
- if (SystemProperties.getBoolean("ro.face.disable_debug_data", false)
- || SystemProperties.getBoolean("persist.face.disable_debug_data", false)) {
- return;
- }
-
- // The debug method takes two file descriptors. The first is for text
- // output, which we will drop. The second is for binary data, which
- // will be the protobuf data.
- final IBiometricsFace daemon = getDaemon();
- if (daemon != null) {
- FileOutputStream devnull = null;
- try {
- devnull = new FileOutputStream("/dev/null");
- final NativeHandle handle = new NativeHandle(
- new FileDescriptor[]{devnull.getFD(), fd},
- new int[0], false);
- daemon.debug(handle, new ArrayList<String>(Arrays.asList(args)));
- } catch (IOException | RemoteException ex) {
- Slog.d(TAG, "error while reading face debugging data", ex);
- } finally {
- if (devnull != null) {
- try {
- devnull.close();
- } catch (IOException ex) {
- }
- }
- }
- }
- }
-
- void setTestHalEnabled(boolean enabled) {
- mTestHalEnabled = enabled;
- }
-
- @NonNull
- @Override
- public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
- @NonNull String opPackageName) {
- return new BiometricTestSessionImpl(mContext, mSensorId, callback,
- this, mHalResultController);
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
deleted file mode 100644
index e44b263..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ /dev/null
@@ -1,254 +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.biometrics.sensors.face.hidl;
-
-import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.SensorPrivacyManager;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricManager.Authenticators;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.face.FaceAuthenticateOptions;
-import android.hardware.face.FaceManager;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.internal.R;
-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.AuthenticationClient;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricNotificationUtils;
-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.LockoutTracker;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.face.UsageStats;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Face-specific authentication client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-class FaceAuthenticationClient
- extends AuthenticationClient<IBiometricsFace, FaceAuthenticateOptions> {
-
- private static final String TAG = "FaceAuthenticationClient";
-
- private final UsageStats mUsageStats;
-
- private final int[] mBiometricPromptIgnoreList;
- private final int[] mBiometricPromptIgnoreListVendor;
- private final int[] mKeyguardIgnoreList;
- private final int[] mKeyguardIgnoreListVendor;
-
- private int mLastAcquire;
- private SensorPrivacyManager mSensorPrivacyManager;
- @NonNull
- private final AuthenticationStateListeners mAuthenticationStateListeners;
-
- FaceAuthenticationClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFace> lazyDaemon,
- @NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener, long operationId,
- boolean restricted, @NonNull FaceAuthenticateOptions options, int cookie,
- boolean requireConfirmation,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
- @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
- @Authenticators.Types int sensorStrength,
- @NonNull AuthenticationStateListeners authenticationStateListeners) {
- super(context, lazyDaemon, token, listener, operationId, restricted,
- options, cookie, requireConfirmation, logger, biometricContext,
- isStrongBiometric, null /* taskStackListener */,
- lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */,
- sensorStrength);
- setRequestId(requestId);
- mUsageStats = usageStats;
- mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
- mAuthenticationStateListeners = authenticationStateListeners;
-
- final Resources resources = getContext().getResources();
- mBiometricPromptIgnoreList = resources.getIntArray(
- R.array.config_face_acquire_biometricprompt_ignorelist);
- mBiometricPromptIgnoreListVendor = resources.getIntArray(
- R.array.config_face_acquire_vendor_biometricprompt_ignorelist);
- mKeyguardIgnoreList = resources.getIntArray(
- R.array.config_face_acquire_keyguard_ignorelist);
- mKeyguardIgnoreListVendor = resources.getIntArray(
- R.array.config_face_acquire_vendor_keyguard_ignorelist);
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
- mState = STATE_STARTED;
- }
-
- @NonNull
- @Override
- protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
- return new ClientMonitorCompositeCallback(
- getLogger().getAmbientLightProbe(true /* startWithClient */), callback);
- }
-
- @Override
- protected void startHalOperation() {
-
- if (mSensorPrivacyManager != null
- && mSensorPrivacyManager
- .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE,
- SensorPrivacyManager.Sensors.CAMERA)) {
- onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- return;
- }
-
- try {
- getFreshDaemon().authenticate(mOperationId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting auth", e);
- onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- protected void stopHalOperation() {
- try {
- getFreshDaemon().cancel();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting cancel", e);
- onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- public boolean wasUserDetected() {
- // Do not provide haptic feedback if the user was not detected, and an error (usually
- // ERROR_TIMEOUT) is received.
- return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED
- && mLastAcquire != FaceManager.FACE_ACQUIRED_SENSOR_DIRTY;
- }
-
- @Override
- protected void handleLifecycleAfterAuth(boolean authenticated) {
- // For face, the authentication lifecycle ends either when
- // 1) Authenticated == true
- // 2) Error occurred
- // 3) Authenticated == false
- mCallback.onClientFinished(this, true /* success */);
- }
-
- @Override
- public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
- @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;
- }
-
- @Override
- public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
- boolean authenticated, ArrayList<Byte> token) {
- super.onAuthenticated(identifier, authenticated, token);
-
- mState = STATE_STOPPED;
- mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
- getStartTimeMs(),
- System.currentTimeMillis() - getStartTimeMs() /* latency */,
- authenticated,
- 0 /* error */,
- 0 /* vendorError */,
- getTargetUserId()));
-
- if (reportBiometricAuthAttempts()) {
- if (authenticated) {
- mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
- getTargetUserId());
- } else {
- mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
- getTargetUserId());
- }
- }
- }
-
- @Override
- public void onError(@BiometricConstants.Errors int error, int vendorCode) {
- mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
- getStartTimeMs(),
- System.currentTimeMillis() - getStartTimeMs() /* latency */,
- false /* authenticated */,
- error,
- vendorCode,
- getTargetUserId()));
-
- super.onError(error, vendorCode);
- }
-
- private int[] getAcquireIgnorelist() {
- return isBiometricPrompt() ? mBiometricPromptIgnoreList : mKeyguardIgnoreList;
- }
-
- private int[] getAcquireVendorIgnorelist() {
- return isBiometricPrompt() ? mBiometricPromptIgnoreListVendor : mKeyguardIgnoreListVendor;
- }
-
- private boolean shouldSend(int acquireInfo, int vendorCode) {
- if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
- return !Utils.listContains(getAcquireVendorIgnorelist(), vendorCode);
- } else {
- return !Utils.listContains(getAcquireIgnorelist(), acquireInfo);
- }
- }
-
- @Override
- public void onAcquired(int acquireInfo, int vendorCode) {
- mLastAcquire = acquireInfo;
-
- if (acquireInfo == FaceManager.FACE_ACQUIRED_RECALIBRATE) {
- BiometricNotificationUtils.showReEnrollmentNotification(getContext());
- }
- @LockoutTracker.LockoutMode final int lockoutMode =
- getLockoutTracker().getLockoutModeForUser(getTargetUserId());
- if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
- PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
- pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
- }
-
- final boolean shouldSend = shouldSend(acquireInfo, vendorCode);
- onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
deleted file mode 100644
index 815cf91..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ /dev/null
@@ -1,155 +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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.biometrics.face.V1_0.Status;
-import android.hardware.face.Face;
-import android.hardware.face.FaceEnrollOptions;
-import android.hardware.face.FaceManager;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-import android.view.Surface;
-
-import com.android.internal.R;
-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.BiometricNotificationUtils;
-import com.android.server.biometrics.sensors.BiometricUtils;
-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.EnrollClient;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.function.Supplier;
-
-/**
- * Face-specific enroll client supporting the {@link android.hardware.biometrics.face.V1_0} HIDL
- * interface.
- */
-public class FaceEnrollClient extends EnrollClient<IBiometricsFace> {
-
- private static final String TAG = "FaceEnrollClient";
-
- @NonNull private final int[] mDisabledFeatures;
- @NonNull private final int[] mEnrollIgnoreList;
- @NonNull private final int[] mEnrollIgnoreListVendor;
-
- FaceEnrollClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
- @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId,
- @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
- @Nullable Surface previewSurface, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull FaceEnrollOptions options) {
- super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
- timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext,
- BiometricFaceConstants.reasonToMetric(options.getEnrollReason()));
- setRequestId(requestId);
- mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
- mEnrollIgnoreList = getContext().getResources()
- .getIntArray(R.array.config_face_acquire_enroll_ignorelist);
- mEnrollIgnoreListVendor = getContext().getResources()
- .getIntArray(R.array.config_face_acquire_vendor_enroll_ignorelist);
-
- Slog.w(TAG, "EnrollOptions "
- + FaceEnrollOptions.enrollReasonToString(options.getEnrollReason()));
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
-
- BiometricNotificationUtils.cancelFaceEnrollNotification(getContext());
- BiometricNotificationUtils.cancelFaceReEnrollNotification(getContext());
- }
-
- @NonNull
- @Override
- protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
- return new ClientMonitorCompositeCallback(
- getLogger().getAmbientLightProbe(true /* startWithClient */), callback);
- }
-
- @Override
- protected boolean hasReachedEnrollmentLimit() {
- final int limit = getContext().getResources().getInteger(
- com.android.internal.R.integer.config_faceMaxTemplatesPerUser);
- final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId())
- .size();
- if (enrolled >= limit) {
- Slog.w(TAG, "Too many faces registered, user: " + getTargetUserId());
- return true;
- }
- return false;
- }
-
- @Override
- public void onAcquired(int acquireInfo, int vendorCode) {
- final boolean shouldSend;
- if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
- shouldSend = !Utils.listContains(mEnrollIgnoreListVendor, vendorCode);
- } else {
- shouldSend = !Utils.listContains(mEnrollIgnoreList, acquireInfo);
- }
- onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
- }
-
- @Override
- protected void startHalOperation() {
- final ArrayList<Byte> token = new ArrayList<>();
- for (byte b : mHardwareAuthToken) {
- token.add(b);
- }
- final ArrayList<Integer> disabledFeatures = new ArrayList<>();
- for (int disabledFeature : mDisabledFeatures) {
- disabledFeatures.add(disabledFeature);
- }
-
- try {
- final int status = getFreshDaemon().enroll(token, mTimeoutSec, disabledFeatures);
-
- if (status != Status.OK) {
- onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting enroll", e);
- onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- protected void stopHalOperation() {
- try {
- getFreshDaemon().cancel();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting cancel", e);
- onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
deleted file mode 100644
index 97838a7..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
+++ /dev/null
@@ -1,113 +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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.face.IFaceServiceReceiver;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.internal.util.Preconditions;
-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 com.android.server.biometrics.sensors.GenerateChallengeClient;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Supplier;
-
-/**
- * Face-specific generateChallenge client supporting the
- * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
- */
-public class FaceGenerateChallengeClient extends GenerateChallengeClient<IBiometricsFace> {
-
- private static final String TAG = "FaceGenerateChallengeClient";
- static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes
- private static final ClientMonitorCallback EMPTY_CALLBACK = new ClientMonitorCallback() {
- };
-
- private final long mCreatedAt;
- private List<IFaceServiceReceiver> mWaiting;
- private Long mChallengeResult;
-
- FaceGenerateChallengeClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
- @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
- int sensorId, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext, long now) {
- super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
- biometricContext);
- mCreatedAt = now;
- mWaiting = new ArrayList<>();
- }
-
- @Override
- protected void startHalOperation() {
- mChallengeResult = null;
- try {
- mChallengeResult = getFreshDaemon().generateChallenge(CHALLENGE_TIMEOUT_SEC).value;
- // send the result to the original caller via mCallback and any waiting callers
- // that called reuseResult
- sendChallengeResult(getListener(), mCallback);
- for (IFaceServiceReceiver receiver : mWaiting) {
- sendChallengeResult(new ClientMonitorCallbackConverter(receiver), EMPTY_CALLBACK);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "generateChallenge failed", e);
- mCallback.onClientFinished(this, false /* success */);
- } finally {
- mWaiting = null;
- }
- }
-
- /** @return An arbitrary time value for caching provided to the constructor. */
- public long getCreatedAt() {
- return mCreatedAt;
- }
-
- /**
- * Reuse the result of this operation when it is available. The receiver will be notified
- * immediately if a challenge has already been generated.
- *
- * @param receiver receiver to be notified of challenge result
- */
- public void reuseResult(@NonNull IFaceServiceReceiver receiver) {
- if (mWaiting != null) {
- mWaiting.add(receiver);
- } else {
- sendChallengeResult(new ClientMonitorCallbackConverter(receiver), EMPTY_CALLBACK);
- }
- }
-
- private void sendChallengeResult(@NonNull ClientMonitorCallbackConverter receiver,
- @NonNull ClientMonitorCallback ownerCallback) {
- Preconditions.checkState(mChallengeResult != null, "result not available");
- try {
- receiver.onChallengeGenerated(getSensorId(), getTargetUserId(), mChallengeResult);
- ownerCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- ownerCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
deleted file mode 100644
index 47aaeec..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
+++ /dev/null
@@ -1,102 +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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-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.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.BiometricsProto;
-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 com.android.server.biometrics.sensors.HalClientMonitor;
-
-import java.util.function.Supplier;
-
-/**
- * Face-specific getFeature client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-public class FaceGetFeatureClient extends HalClientMonitor<IBiometricsFace> {
-
- private static final String TAG = "FaceGetFeatureClient";
-
- private final int mFeature;
- private final int mFaceId;
- private boolean mValue;
-
- FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
- @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- int feature, int faceId) {
- super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- logger, biometricContext);
- mFeature = feature;
- mFaceId = faceId;
- }
-
- @Override
- public void unableToStart() {
- try {
- getListener().onFeatureGet(false /* success */, new int[0], new boolean[0]);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to send error", e);
- }
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
- startHalOperation();
- }
-
- @Override
- protected void startHalOperation() {
- try {
- final OptionalBool result = getFreshDaemon().getFeature(mFeature, mFaceId);
- int[] features = new int[1];
- boolean[] featureState = new boolean[1];
- features[0] = mFeature;
- featureState[0] = result.value;
- mValue = result.value;
-
- getListener().onFeatureGet(result.status == Status.OK, features, featureState);
- mCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to getFeature", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- boolean getValue() {
- return mValue;
- }
-
- @Override
- public int getProtoEnum() {
- return BiometricsProto.CM_GET_FEATURE;
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
deleted file mode 100644
index 89a17c6..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
+++ /dev/null
@@ -1,72 +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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.face.Face;
-import android.os.IBinder;
-
-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.InternalCleanupClient;
-import com.android.server.biometrics.sensors.InternalEnumerateClient;
-import com.android.server.biometrics.sensors.RemovalClient;
-
-import java.util.List;
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * Face-specific internal cleanup client supporting the
- * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
- */
-class FaceInternalCleanupClient extends InternalCleanupClient<Face, IBiometricsFace> {
-
- FaceInternalCleanupClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
- int sensorId, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext,
- @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
- utils, authenticatorIds);
- }
-
- @Override
- protected InternalEnumerateClient<IBiometricsFace> getEnumerateClient(Context context,
- Supplier<IBiometricsFace> lazyDaemon, IBinder token, int userId, String owner,
- List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
- return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
- enrolledList, utils, sensorId, logger, biometricContext);
- }
-
- @Override
- protected RemovalClient<Face, IBiometricsFace> getRemovalClient(Context context,
- Supplier<IBiometricsFace> lazyDaemon, IBinder token,
- int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- Map<Integer, Long> authenticatorIds) {
- // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
- // is all done internally.
- return new FaceRemovalClient(context, lazyDaemon, token,
- null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
- sensorId, logger, biometricContext, authenticatorIds);
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
deleted file mode 100644
index 250dd7e..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
+++ /dev/null
@@ -1,60 +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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.face.Face;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-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.InternalEnumerateClient;
-
-import java.util.List;
-import java.util.function.Supplier;
-
-/**
- * Face-specific internal enumerate client supporting the
- * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
- */
-class FaceInternalEnumerateClient extends InternalEnumerateClient<IBiometricsFace> {
- private static final String TAG = "FaceInternalEnumerateClient";
-
- FaceInternalEnumerateClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token, int userId,
- @NonNull String owner, @NonNull List<Face> enrolledList,
- @NonNull BiometricUtils<Face> utils, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
- super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
- logger, biometricContext);
- }
-
- @Override
- protected void startHalOperation() {
- try {
- getFreshDaemon().enumerate();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting enumerate", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
deleted file mode 100644
index 0ee7a35..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
+++ /dev/null
@@ -1,65 +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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.face.Face;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-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.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.RemovalClient;
-
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * Face-specific removal client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-class FaceRemovalClient extends RemovalClient<Face, IBiometricsFace> {
- private static final String TAG = "FaceRemovalClient";
-
- private final int mBiometricId;
-
- FaceRemovalClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
- @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
- int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils,
- int sensorId, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext,
- @NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, logger,
- biometricContext, authenticatorIds);
- mBiometricId = biometricId;
- }
-
- @Override
- protected void startHalOperation() {
- try {
- getFreshDaemon().remove(mBiometricId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting remove", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
deleted file mode 100644
index f29b9e8..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
+++ /dev/null
@@ -1,87 +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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.BiometricsProto;
-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.HalClientMonitor;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Face-specific resetLockout client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-public class FaceResetLockoutClient extends HalClientMonitor<IBiometricsFace> {
-
- private static final String TAG = "FaceResetLockoutClient";
-
- private final ArrayList<Byte> mHardwareAuthToken;
-
- FaceResetLockoutClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, String owner, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull byte[] hardwareAuthToken) {
- super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, logger, biometricContext);
-
- mHardwareAuthToken = new ArrayList<>();
- for (byte b : hardwareAuthToken) {
- mHardwareAuthToken.add(b);
- }
- }
-
- @Override
- public void unableToStart() {
- // Nothing to do here
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
- startHalOperation();
- }
-
- public boolean interruptsPrecedingClients() {
- return true;
- }
-
- @Override
- protected void startHalOperation() {
- try {
- getFreshDaemon().resetLockout(mHardwareAuthToken);
- mCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to reset lockout", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- public int getProtoEnum() {
- return BiometricsProto.CM_RESET_LOCKOUT;
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
deleted file mode 100644
index b7b0dc04..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
+++ /dev/null
@@ -1,57 +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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.RevokeChallengeClient;
-
-import java.util.function.Supplier;
-
-/**
- * Face-specific revokeChallenge client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-public class FaceRevokeChallengeClient extends RevokeChallengeClient<IBiometricsFace> {
-
- private static final String TAG = "FaceRevokeChallengeClient";
-
- FaceRevokeChallengeClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
- int userId, @NonNull String owner, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
- super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
- }
-
- @Override
- protected void startHalOperation() {
- try {
- getFreshDaemon().revokeChallenge();
- mCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "revokeChallenge failed", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
deleted file mode 100644
index 3c82f9c..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
+++ /dev/null
@@ -1,100 +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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.biometrics.face.V1_0.Status;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.BiometricsProto;
-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 com.android.server.biometrics.sensors.HalClientMonitor;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Face-specific setFeature client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-public class FaceSetFeatureClient extends HalClientMonitor<IBiometricsFace> {
-
- private static final String TAG = "FaceSetFeatureClient";
-
- private final int mFeature;
- private final boolean mEnabled;
- private final ArrayList<Byte> mHardwareAuthToken;
- private final int mFaceId;
-
- FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
- @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- int feature, boolean enabled, byte[] hardwareAuthToken, int faceId) {
- super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- logger, biometricContext);
- mFeature = feature;
- mEnabled = enabled;
- mFaceId = faceId;
-
- mHardwareAuthToken = new ArrayList<>();
- for (byte b : hardwareAuthToken) {
- mHardwareAuthToken.add(b);
- }
- }
-
- @Override
- public void unableToStart() {
- try {
- getListener().onFeatureSet(false /* success */, mFeature);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to send error", e);
- }
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
-
- startHalOperation();
- }
-
- @Override
- protected void startHalOperation() {
- try {
- final int result = getFreshDaemon()
- .setFeature(mFeature, mEnabled, mHardwareAuthToken, mFaceId);
- getListener().onFeatureSet(result == Status.OK, mFeature);
- mCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to set feature: " + mFeature + " to enabled: " + mEnabled, e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- public int getProtoEnum() {
- return BiometricsProto.CM_SET_FEATURE;
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
index a004cae4..9a4c29d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
@@ -173,9 +173,14 @@
}
private AidlResponseHandler getAidlResponseHandler() {
- return new AidlResponseHandler(getContext(), getScheduler(), getSensorProperties().sensorId,
- mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher,
- mAuthSessionCoordinator, () -> {}, mAidlResponseHandlerCallback);
+ return new AidlResponseHandler(getContext(),
+ getScheduler(),
+ getSensorProperties().sensorId,
+ mCurrentUserId,
+ mLockoutTracker,
+ mLockoutResetDispatcher,
+ mAuthSessionCoordinator,
+ mAidlResponseHandlerCallback);
}
private IBiometricsFace getIBiometricsFace() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
index fa95361..45d0cfe 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
@@ -53,7 +53,7 @@
private static final String TAG = "HidlToAidlSessionAdapter";
- private static final int CHALLENGE_TIMEOUT_SEC = 600;
+ @VisibleForTesting static final int CHALLENGE_TIMEOUT_SEC = 600;
@DurationMillisLong
private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000;
@DurationMillisLong
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index d762914..deda93c7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -72,7 +72,6 @@
import android.os.ShellCallback;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.Settings;
import android.util.EventLog;
import android.util.Pair;
import android.util.Slog;
@@ -83,7 +82,6 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.AuthenticationStateListeners;
@@ -94,12 +92,8 @@
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
-import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21;
-import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21UdfpsMock;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
-import com.google.android.collect.Lists;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -886,9 +880,9 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
- public void registerAuthenticatorsLegacy(
+ public void registerAuthenticators(
@NonNull FingerprintSensorConfigurations fingerprintSensorConfigurations) {
- super.registerAuthenticatorsLegacy_enforcePermission();
+ super.registerAuthenticators_enforcePermission();
if (!fingerprintSensorConfigurations.hasSensorConfigurations()) {
Slog.d(TAG, "No fingerprint sensors available.");
return;
@@ -897,30 +891,6 @@
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
- @Override // Binder call
- public void registerAuthenticators(
- @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
- super.registerAuthenticators_enforcePermission();
-
- mRegistry.registerAll(() -> {
- List<String> aidlSensors = new ArrayList<>();
- final String[] instances = mAidlInstanceNameSupplier.get();
- if (instances != null) {
- aidlSensors.addAll(Lists.newArrayList(instances));
- }
-
- final Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
- filteredInstances = filterAvailableHalInstances(hidlSensors, aidlSensors);
-
- final List<ServiceProvider> providers = new ArrayList<>();
- providers.addAll(getHidlProviders(filteredInstances.first));
- providers.addAll(getAidlProviders(filteredInstances.second));
-
- return providers;
- });
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void addAuthenticatorsRegisteredCallback(
IFingerprintAuthenticatorsRegisteredCallback callback) {
@@ -1086,22 +1056,15 @@
return null;
};
- if (Flags.deHidl()) {
- mFingerprintProviderFunction = fingerprintProviderFunction == null
- ? (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) ->
- new FingerprintProvider(
- getContext(), mBiometricStateCallback,
- mAuthenticationStateListeners,
- filteredSensorProps.second,
- filteredSensorProps.first, mLockoutResetDispatcher,
- mGestureAvailabilityDispatcher,
- mBiometricContext,
- resetLockoutRequiresHardwareAuthToken)
- : fingerprintProviderFunction;
- } else {
- mFingerprintProviderFunction =
- (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) -> null;
- }
+ mFingerprintProviderFunction = fingerprintProviderFunction != null
+ ? fingerprintProviderFunction :
+ (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) ->
+ new FingerprintProvider(getContext(), mBiometricStateCallback,
+ mAuthenticationStateListeners, filteredSensorProps.second,
+ filteredSensorProps.first, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher, mBiometricContext,
+ resetLockoutRequiresHardwareAuthToken);
+
mHandler = new Handler(Looper.getMainLooper());
mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier);
mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -1160,74 +1123,6 @@
.getSensorPropForInstance(finalSensorInstance));
}
- private Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
- filterAvailableHalInstances(
- @NonNull List<FingerprintSensorPropertiesInternal> hidlInstances,
- @NonNull List<String> aidlInstances) {
- if ((hidlInstances.size() + aidlInstances.size()) <= 1) {
- return new Pair(hidlInstances, aidlInstances);
- }
-
- final int virtualAt = aidlInstances.indexOf("virtual");
- if (Utils.isFingerprintVirtualEnabled(getContext())) {
- if (virtualAt != -1) {
- //only virtual instance should be returned
- Slog.i(TAG, "virtual hal is used");
- return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt)));
- } else {
- Slog.e(TAG, "Could not find virtual interface while it is enabled");
- return new Pair(hidlInstances, aidlInstances);
- }
- } else {
- //remove virtual instance
- aidlInstances = new ArrayList<>(aidlInstances);
- if (virtualAt != -1) {
- aidlInstances.remove(virtualAt);
- }
- return new Pair(hidlInstances, aidlInstances);
- }
- }
-
- @NonNull
- private List<ServiceProvider> getHidlProviders(
- @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
- final List<ServiceProvider> providers = new ArrayList<>();
-
- for (FingerprintSensorPropertiesInternal hidlSensor : hidlSensors) {
- final Fingerprint21 fingerprint21;
- if ((Build.IS_USERDEBUG || Build.IS_ENG)
- && getContext().getResources().getBoolean(R.bool.allow_test_udfps)
- && Settings.Secure.getIntForUser(getContext().getContentResolver(),
- Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */,
- UserHandle.USER_CURRENT) != 0) {
- fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(),
- mBiometricStateCallback, mAuthenticationStateListeners,
- hidlSensor, mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
- BiometricContext.getInstance(getContext()));
- } else {
- fingerprint21 = Fingerprint21.newInstance(getContext(),
- mBiometricStateCallback, mAuthenticationStateListeners, hidlSensor,
- mHandler, mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
- }
- providers.add(fingerprint21);
- }
-
- return providers;
- }
-
- @NonNull
- private List<ServiceProvider> getAidlProviders(@NonNull List<String> instances) {
- final List<ServiceProvider> providers = new ArrayList<>();
-
- for (String instance : instances) {
- final FingerprintProvider provider = mFingerprintProvider.apply(instance);
- Slog.i(TAG, "Adding AIDL provider: " + instance);
- providers.add(provider);
- }
-
- return providers;
- }
-
@Override
public void onStart() {
publishBinderService(Context.FINGERPRINT_SERVICE, mServiceWrapper);
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
index bd21cf4..6d1715f 100644
--- 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
@@ -25,7 +25,6 @@
import android.hardware.keymaster.HardwareAuthToken;
import android.util.Slog;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -53,16 +52,6 @@
/**
* Interface to send results to the AidlResponseHandler's owner.
*/
- public interface HardwareUnavailableCallback {
- /**
- * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
- */
- void onHardwareUnavailable();
- }
-
- /**
- * Interface to send results to the AidlResponseHandler's owner.
- */
public interface AidlResponseHandlerCallback {
/**
* Invoked when enrollment is successful.
@@ -90,8 +79,6 @@
@NonNull
private final AuthSessionCoordinator mAuthSessionCoordinator;
@NonNull
- private final HardwareUnavailableCallback mHardwareUnavailableCallback;
- @NonNull
private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
public AidlResponseHandler(@NonNull Context context,
@@ -99,24 +86,6 @@
@NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
- @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
- this(context, scheduler, sensorId, userId, lockoutTracker, lockoutResetDispatcher,
- authSessionCoordinator, hardwareUnavailableCallback,
- new AidlResponseHandlerCallback() {
- @Override
- public void onEnrollSuccess() {}
-
- @Override
- public void onHardwareUnavailable() {}
- });
- }
-
- public AidlResponseHandler(@NonNull Context context,
- @NonNull BiometricScheduler scheduler, int sensorId, int userId,
- @NonNull LockoutTracker lockoutTracker,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull AuthSessionCoordinator authSessionCoordinator,
- @NonNull HardwareUnavailableCallback hardwareUnavailableCallback,
@NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
mContext = context;
mScheduler = scheduler;
@@ -125,7 +94,6 @@
mLockoutTracker = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mAuthSessionCoordinator = authSessionCoordinator;
- mHardwareUnavailableCallback = hardwareUnavailableCallback;
mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
}
@@ -171,11 +139,7 @@
handleResponse(ErrorConsumer.class, (c) -> {
c.onError(error, vendorCode);
if (error == Error.HW_UNAVAILABLE) {
- if (Flags.deHidl()) {
- mAidlResponseHandlerCallback.onHardwareUnavailable();
- } else {
- mHardwareUnavailableCallback.onHardwareUnavailable();
- }
+ mAidlResponseHandlerCallback.onHardwareUnavailable();
}
});
}
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 93d1b6e..5edcbed 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
@@ -31,23 +31,16 @@
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationState;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.Build;
-import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
import android.util.Slog;
-import com.android.internal.R;
-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;
@@ -67,7 +60,6 @@
import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
-import java.time.Clock;
import java.util.ArrayList;
import java.util.function.Supplier;
@@ -79,30 +71,17 @@
extends AuthenticationClient<AidlSession, FingerprintAuthenticateOptions>
implements Udfps, LockoutConsumer, PowerPressHandler {
private static final String TAG = "FingerprintAuthenticationClient";
- private static final int MESSAGE_AUTH_SUCCESS = 2;
- private static final int MESSAGE_FINGER_UP = 3;
@NonNull
private final SensorOverlays mSensorOverlays;
@NonNull
private final FingerprintSensorPropertiesInternal mSensorProps;
@NonNull
private final CallbackWithProbe<Probe> mALSProbeCallback;
- private final Handler mHandler;
- private final int mSkipWaitForPowerAcquireMessage;
- private final int mSkipWaitForPowerVendorAcquireMessage;
- private final long mFingerUpIgnoresPower = 500;
private final AuthSessionCoordinator mAuthSessionCoordinator;
@NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
@Nullable
private ICancellationSignal mCancellationSignal;
private boolean mIsPointerDown;
- private long mWaitForAuthKeyguard;
- private long mWaitForAuthBp;
- private long mIgnoreAuthFor;
- private long mSideFpsLastAcquireStartTime;
- private Runnable mAuthSuccessRunnable;
- private final Clock mClock;
-
public FingerprintAuthenticationClient(
@NonNull Context context,
@@ -125,9 +104,7 @@
@NonNull AuthenticationStateListeners authenticationStateListeners,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
- @NonNull Handler handler,
@Authenticators.Types int biometricStrength,
- @NonNull Clock clock,
@Nullable LockoutTracker lockoutTracker) {
super(
context,
@@ -156,39 +133,7 @@
mAuthenticationStateListeners = authenticationStateListeners;
mSensorProps = sensorProps;
mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
- mHandler = handler;
-
- mWaitForAuthKeyguard =
- context.getResources()
- .getInteger(R.integer.config_sidefpsKeyguardPowerPressWindow);
- mWaitForAuthBp =
- context.getResources().getInteger(R.integer.config_sidefpsBpPowerPressWindow);
- mIgnoreAuthFor =
- context.getResources().getInteger(R.integer.config_sidefpsPostAuthDowntime);
-
- mSkipWaitForPowerAcquireMessage =
- context.getResources().getInteger(
- R.integer.config_sidefpsSkipWaitForPowerAcquireMessage);
- mSkipWaitForPowerVendorAcquireMessage =
- context.getResources().getInteger(
- R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage);
mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
- mSideFpsLastAcquireStartTime = -1;
- mClock = clock;
-
- if (mSensorProps.isAnySidefpsType()) {
- if (Build.isDebuggable()) {
- mWaitForAuthKeyguard = Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW,
- (int) mWaitForAuthKeyguard, UserHandle.USER_CURRENT);
- mWaitForAuthBp = Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW, (int) mWaitForAuthBp,
- UserHandle.USER_CURRENT);
- mIgnoreAuthFor = Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, (int) mIgnoreAuthFor,
- UserHandle.USER_CURRENT);
- }
- }
}
@Override
@@ -316,11 +261,7 @@
}
try {
- if (Flags.deHidl()) {
- startAuthentication();
- } else {
- mCancellationSignal = doAuthenticate();
- }
+ doAuthenticate();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
onError(
@@ -334,49 +275,7 @@
}
}
- private ICancellationSignal doAuthenticate() throws RemoteException {
- final AidlSession session = getFreshDaemon();
-
- final OperationContextExt opContext = getOperationContext();
- final ICancellationSignal cancel;
- if (session.hasContextMethods()) {
- cancel = session.getSession().authenticateWithContext(
- mOperationId, opContext.toAidlContext(getOptions()));
- } else {
- cancel = session.getSession().authenticate(mOperationId);
- }
-
- getBiometricContext().subscribe(opContext, ctx -> {
- if (session.hasContextMethods()) {
- try {
- session.getSession().onContextChanged(ctx);
- // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
- if (ctx.operationState != null && ctx.operationState.getTag()
- == OperationState.fingerprintOperationState) {
- session.getSession().setIgnoreDisplayTouches(
- ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify context changed", e);
- }
- }
-
- // TODO(b/243836005): this should come via ctx
- final boolean isAwake = getBiometricContext().isAwake();
- if (isAwake) {
- mALSProbeCallback.getProbe().enable();
- } else {
- mALSProbeCallback.getProbe().disable();
- }
- });
- if (getBiometricContext().isAwake()) {
- mALSProbeCallback.getProbe().enable();
- }
-
- return cancel;
- }
-
- private void startAuthentication() {
+ private void doAuthenticate() throws RemoteException {
final AidlSession session = getFreshDaemon();
final OperationContextExt opContext = getOperationContext();
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 8d2b46f..1db2fad 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
@@ -23,7 +23,6 @@
import android.content.Context;
import android.hardware.biometrics.BiometricRequestConstants;
import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationState;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
@@ -31,7 +30,6 @@
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
-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.OperationContextExt;
@@ -106,13 +104,8 @@
resetIgnoreDisplayTouches();
mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD,
this);
-
try {
- if (Flags.deHidl()) {
- startDetectInteraction();
- } else {
- mCancellationSignal = doDetectInteraction();
- }
+ doDetectInteraction();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting finger detect", e);
mSensorOverlays.hide(getSensorId());
@@ -120,33 +113,7 @@
}
}
- private ICancellationSignal doDetectInteraction() throws RemoteException {
- final AidlSession session = getFreshDaemon();
-
- if (session.hasContextMethods()) {
- final OperationContextExt opContext = getOperationContext();
- final ICancellationSignal cancel = session.getSession().detectInteractionWithContext(
- opContext.toAidlContext(mOptions));
- getBiometricContext().subscribe(opContext, ctx -> {
- try {
- session.getSession().onContextChanged(ctx);
- // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
- if (ctx.operationState != null && ctx.operationState.getTag()
- == OperationState.fingerprintOperationState) {
- session.getSession().setIgnoreDisplayTouches(
- ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify context changed", e);
- }
- });
- return cancel;
- } else {
- return session.getSession().detectInteraction();
- }
- }
-
- private void startDetectInteraction() throws RemoteException {
+ private void doDetectInteraction() throws RemoteException {
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
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 a24ab1d..86ebabe 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
@@ -26,7 +26,6 @@
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationState;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintEnrollOptions;
@@ -40,7 +39,6 @@
import android.util.Slog;
import android.view.accessibility.AccessibilityManager;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -209,11 +207,7 @@
BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
try {
- if (Flags.deHidl()) {
- startEnroll();
- } else {
- mCancellationSignal = doEnroll();
- }
+ doEnroll();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting enroll", e);
onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
@@ -222,35 +216,7 @@
}
}
- private ICancellationSignal doEnroll() throws RemoteException {
- final AidlSession session = getFreshDaemon();
- final HardwareAuthToken hat =
- HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
-
- if (session.hasContextMethods()) {
- final OperationContextExt opContext = getOperationContext();
- final ICancellationSignal cancel = session.getSession().enrollWithContext(
- hat, opContext.toAidlContext());
- getBiometricContext().subscribe(opContext, ctx -> {
- try {
- session.getSession().onContextChanged(ctx);
- // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
- if (ctx.operationState != null && ctx.operationState.getTag()
- == OperationState.fingerprintOperationState) {
- session.getSession().setIgnoreDisplayTouches(
- ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify context changed", e);
- }
- });
- return cancel;
- } else {
- return session.getSession().enroll(hat);
- }
- }
-
- private void startEnroll() throws RemoteException {
+ private void doEnroll() throws RemoteException {
final AidlSession session = getFreshDaemon();
final HardwareAuthToken hat =
HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
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 9290f8a..9c8d98d 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
@@ -29,13 +29,12 @@
import android.content.res.TypedArray;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.SensorLocationInternal;
-import android.hardware.biometrics.common.ComponentInfo;
import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.IVirtualHal;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.Fingerprint;
@@ -50,10 +49,8 @@
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
@@ -96,10 +93,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
-import java.util.stream.Collectors;
/**
* Provider for a single instance of the {@link IFingerprint} HAL.
@@ -140,6 +135,8 @@
@Nullable private ISidefpsController mSidefpsController;
private final AuthSessionCoordinator mAuthSessionCoordinator;
@Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector;
+ @Nullable private IVirtualHal mVhal;
+ @Nullable private String mHalInstanceNameCurrent;
private final class BiometricTaskStackListener extends TaskStackListener {
@Override
@@ -200,11 +197,7 @@
mAuthenticationStateListeners = authenticationStateListeners;
mHalInstanceName = halInstanceName;
mFingerprintSensors = new SensorList<>(ActivityManager.getService());
- if (Flags.deHidl()) {
- mHandler = biometricHandlerProvider.getFingerprintHandler();
- } else {
- mHandler = new Handler(Looper.getMainLooper());
- }
+ mHandler = biometricHandlerProvider.getFingerprintHandler();
mLockoutResetDispatcher = lockoutResetDispatcher;
mActivityTaskManager = ActivityTaskManager.getInstance();
mTaskStackListener = new BiometricTaskStackListener();
@@ -230,66 +223,19 @@
private void initSensors(boolean resetLockoutRequiresHardwareAuthToken, SensorProps[] props,
GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
- if (Flags.deHidl()) {
- if (!resetLockoutRequiresHardwareAuthToken) {
- Slog.d(getTag(), "Adding HIDL configs");
- for (SensorProps sensorConfig: props) {
- addHidlSensors(sensorConfig, gestureAvailabilityDispatcher,
- resetLockoutRequiresHardwareAuthToken);
- }
- } else {
- Slog.d(getTag(), "Adding AIDL configs");
- final List<SensorLocationInternal> workaroundLocations =
- getWorkaroundSensorProps(mContext);
- for (SensorProps prop : props) {
- addAidlSensors(prop, gestureAvailabilityDispatcher, workaroundLocations,
- resetLockoutRequiresHardwareAuthToken);
- }
+ if (!resetLockoutRequiresHardwareAuthToken) {
+ Slog.d(getTag(), "Adding HIDL configs");
+ for (SensorProps sensorConfig: props) {
+ addHidlSensors(sensorConfig, gestureAvailabilityDispatcher,
+ resetLockoutRequiresHardwareAuthToken);
}
} else {
+ Slog.d(getTag(), "Adding AIDL configs");
final List<SensorLocationInternal> workaroundLocations =
getWorkaroundSensorProps(mContext);
-
for (SensorProps prop : props) {
- final int sensorId = prop.commonProps.sensorId;
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- if (prop.commonProps.componentInfo != null) {
- for (ComponentInfo info : prop.commonProps.componentInfo) {
- componentInfo.add(new ComponentInfoInternal(info.componentId,
- info.hardwareVersion, info.firmwareVersion, info.serialNumber,
- info.softwareVersion));
- }
- }
- final FingerprintSensorPropertiesInternal internalProp =
- new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
- prop.commonProps.sensorStrength,
- prop.commonProps.maxEnrollmentsPerUser,
- componentInfo,
- prop.sensorType,
- prop.halControlsIllumination,
- true /* resetLockoutRequiresHardwareAuthToken */,
- !workaroundLocations.isEmpty() ? workaroundLocations :
- Arrays.stream(prop.sensorLocations).map(
- location -> new SensorLocationInternal(
- location.display,
- location.sensorLocationX,
- location.sensorLocationY,
- location.sensorRadius))
- .collect(Collectors.toList()));
- final Sensor sensor = new Sensor(this, mContext, mHandler, internalProp,
- mBiometricContext);
- sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
- final int sessionUserId =
- sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
- sensor.getLazySession().get().getUserId();
- mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
- new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
- }
- });
- Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+ addAidlSensors(prop, gestureAvailabilityDispatcher, workaroundLocations,
+ resetLockoutRequiresHardwareAuthToken);
}
}
}
@@ -351,10 +297,29 @@
@VisibleForTesting
synchronized IFingerprint getHalInstance() {
if (mTestHalEnabled) {
- // Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables
- // the test HAL for all sensors under that HAL. This can be updated in the future if
- // necessary.
- return new TestHal();
+ if (Flags.useVhalForTesting()) {
+ if (!mHalInstanceNameCurrent.contains("virtual")) {
+ Slog.i(getTag(), "Switching fingerprint hal from " + mHalInstanceName
+ + " to virtual hal");
+ mHalInstanceNameCurrent = "virtual";
+ mDaemon = null;
+ }
+ } else {
+ // Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables
+ // the test HAL for all sensors under that HAL. This can be updated in the future if
+ // necessary.
+ return new TestHal();
+ }
+ } else {
+ if (mHalInstanceNameCurrent == null) {
+ mHalInstanceNameCurrent = mHalInstanceName;
+ } else if (mHalInstanceNameCurrent.contains("virtual")
+ && mHalInstanceNameCurrent != mHalInstanceName) {
+ Slog.i(getTag(), "Switching fingerprint from virtual hal " + "to "
+ + mHalInstanceName);
+ mHalInstanceNameCurrent = mHalInstanceName;
+ mDaemon = null;
+ }
}
if (mDaemon != null) {
@@ -366,7 +331,7 @@
mDaemon = IFingerprint.Stub.asInterface(
Binder.allowBlocking(
ServiceManager.waitForDeclaredService(
- IFingerprint.DESCRIPTOR + "/" + mHalInstanceName)));
+ IFingerprint.DESCRIPTOR + "/" + mHalInstanceNameCurrent)));
if (mDaemon == null) {
Slog.e(getTag(), "Unable to get daemon");
return null;
@@ -546,23 +511,7 @@
mFingerprintSensors.get(sensorId).getSensorProperties(),
mUdfpsOverlayController, mSidefpsController,
mAuthenticationStateListeners, maxTemplatesPerUser, enrollReason, options);
- if (Flags.deHidl()) {
- scheduleForSensor(sensorId, client, mBiometricStateCallback);
- } else {
- scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
- mBiometricStateCallback, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- ClientMonitorCallback.super.onClientFinished(
- clientMonitor, success);
- if (success) {
- scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
- }
- }));
- }
+ scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
return id;
}
@@ -605,13 +554,8 @@
final int userId = options.getUserId();
final int sensorId = options.getSensorId();
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
- final LockoutTracker lockoutTracker;
- if (Flags.deHidl()) {
- lockoutTracker = mFingerprintSensors.get(sensorId)
- .getLockoutTracker(true /* forAuth */);
- } else {
- lockoutTracker = null;
- }
+ final LockoutTracker lockoutTracker = mFingerprintSensors.get(sensorId)
+ .getLockoutTracker(true /* forAuth */);
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, requestId,
callback, operationId, restricted, options, cookie,
@@ -622,22 +566,16 @@
mTaskStackListener,
mUdfpsOverlayController, mSidefpsController,
mAuthenticationStateListeners, allowBackgroundAuthentication,
- mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
+ mFingerprintSensors.get(sensorId).getSensorProperties(),
Utils.getCurrentStrength(sensorId),
- SystemClock.elapsedRealtimeClock(),
lockoutTracker);
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
mBiometricStateCallback.onClientStarted(clientMonitor);
- if (Flags.deHidl()) {
- mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
- mAuthSessionCoordinator.authStartedFor(userId, sensorId,
- requestId));
- } else {
- mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
- }
+ mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+ mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId));
}
@Override
@@ -649,15 +587,10 @@
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
mBiometricStateCallback.onClientFinished(clientMonitor, success);
- if (Flags.deHidl()) {
- mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
- mAuthSessionCoordinator.authEndedFor(userId,
- Utils.getCurrentStrength(sensorId), sensorId, requestId,
- success));
- } else {
- mAuthSessionCoordinator.authEndedFor(userId,
- Utils.getCurrentStrength(sensorId), sensorId, requestId, success);
- }
+ mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+ mAuthSessionCoordinator.authEndedFor(userId,
+ Utils.getCurrentStrength(sensorId), sensorId, requestId,
+ success));
}
});
@@ -764,10 +697,7 @@
@Override
public boolean isHardwareDetected(int sensorId) {
- if (Flags.deHidl()) {
- return mFingerprintSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
- }
- return hasHalInstance();
+ return mFingerprintSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
}
@Override
@@ -805,12 +735,7 @@
@Override
public int getLockoutModeForUser(int sensorId, int userId) {
- if (Flags.deHidl()) {
- return mFingerprintSensors.get(sensorId).getLockoutModeForUser(userId);
- } else {
- return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
- Utils.getCurrentStrength(sensorId));
- }
+ return mFingerprintSensors.get(sensorId).getLockoutModeForUser(userId);
}
@Override
@@ -1050,4 +975,26 @@
public void sendFingerprintReEnrollNotification() {
mAuthenticationStatsCollector.sendFingerprintReEnrollNotification();
}
+
+ /**
+ * Return virtual hal AIDL interface if it is used for testing
+ *
+ */
+ public IVirtualHal getVhal() throws RemoteException {
+ if (mVhal == null && useVhalForTesting()) {
+ mVhal = IVirtualHal.Stub.asInterface(mDaemon.asBinder().getExtension());
+ if (mVhal == null) {
+ Slog.e(getTag(), "Unable to get virtual hal interface");
+ }
+ }
+
+ return mVhal;
+ }
+
+ /**
+ * Return true if vhal_for_testing feature is enabled and test is active
+ */
+ public boolean useVhalForTesting() {
+ return (Flags.useVhalForTesting() && mTestHalEnabled);
+ }
}
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 af88c62..b7e3f70 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
@@ -42,7 +42,6 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -58,7 +57,6 @@
import com.android.server.biometrics.sensors.LockoutTracker;
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.UserSwitchProvider;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
@@ -110,13 +108,6 @@
}
Sensor(@NonNull FingerprintProvider provider, @NonNull Context context,
- @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
- @NonNull BiometricContext biometricContext) {
- this(provider, context, handler, sensorProperties,
- biometricContext, null);
- }
-
- Sensor(@NonNull FingerprintProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull SensorProps sensorProp,
@NonNull BiometricContext biometricContext,
@NonNull List<SensorLocationInternal> workaroundLocation,
@@ -131,13 +122,8 @@
*/
public void init(@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
- if (Flags.deHidl()) {
- setScheduler(getBiometricSchedulerForInit(gestureAvailabilityDispatcher,
- lockoutResetDispatcher));
- } else {
- setScheduler(getUserAwareBiometricSchedulerForInit(gestureAvailabilityDispatcher,
- lockoutResetDispatcher));
- }
+ setScheduler(getBiometricSchedulerForInit(gestureAvailabilityDispatcher,
+ lockoutResetDispatcher));
mLockoutTracker = new LockoutCache();
mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
}
@@ -168,7 +154,7 @@
final AidlResponseHandler resultController = new AidlResponseHandler(
mContext, mScheduler, sensorId, newUserId,
mLockoutTracker, lockoutResetDispatcher,
- mBiometricContext.getAuthSessionCoordinator(), () -> {},
+ mBiometricContext.getAuthSessionCoordinator(),
new AidlResponseHandler.AidlResponseHandlerCallback() {
@Override
public void onEnrollSuccess() {
@@ -192,45 +178,6 @@
});
}
- private UserAwareBiometricScheduler<ISession, AidlSession>
- getUserAwareBiometricSchedulerForInit(
- GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
- LockoutResetDispatcher lockoutResetDispatcher) {
- return new UserAwareBiometricScheduler<>(TAG,
- BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
- gestureAvailabilityDispatcher,
- () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
- new UserAwareBiometricScheduler.UserSwitchCallback() {
- @NonNull
- @Override
- public StopUserClient<ISession> getStopUserClient(int userId) {
- return new FingerprintStopUserClient(mContext,
- () -> mLazySession.get().getSession(), mToken,
- userId, mSensorProperties.sensorId,
- BiometricLogger.ofUnknown(mContext), mBiometricContext,
- () -> mCurrentSession = null);
- }
-
- @NonNull
- @Override
- public StartUserClient<IFingerprint, ISession> getStartUserClient(
- int newUserId) {
- final int sensorId = mSensorProperties.sensorId;
-
- final AidlResponseHandler resultController = new AidlResponseHandler(
- mContext, mScheduler, sensorId, newUserId,
- mLockoutTracker, lockoutResetDispatcher,
- mBiometricContext.getAuthSessionCoordinator(), () -> {
- Slog.e(TAG, "Fingerprint hardware unavailable.");
- mCurrentSession = null;
- });
-
- return Sensor.this.getStartUserClient(resultController, sensorId,
- newUserId);
- }
- });
- }
-
private FingerprintStartUserClient getStartUserClient(AidlResponseHandler resultController,
int sensorId, int newUserId) {
final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
deleted file mode 100644
index fc037ae..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ /dev/null
@@ -1,246 +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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.ITestSession;
-import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.FingerprintEnrollOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-
-/**
- * A test session implementation for the {@link Fingerprint21} provider. See
- * {@link android.hardware.biometrics.BiometricTestSession}.
- */
-public class BiometricTestSessionImpl extends ITestSession.Stub {
-
- private static final String TAG = "BiometricTestSessionImpl";
-
- @NonNull private final Context mContext;
- private final int mSensorId;
- @NonNull private final ITestSessionCallback mCallback;
- @NonNull private final BiometricStateCallback mBiometricStateCallback;
- @NonNull private final Fingerprint21 mFingerprint21;
- @NonNull private final Fingerprint21.HalResultController mHalResultController;
- @NonNull private final Set<Integer> mEnrollmentIds;
- @NonNull private final Random mRandom;
-
- /**
- * Internal receiver currently only used for enroll. Results do not need to be forwarded to the
- * test, since enrollment is a platform-only API. The authentication path is tested through
- * the public FingerprintManager APIs and does not use this receiver.
- */
- private final IFingerprintServiceReceiver mReceiver = new IFingerprintServiceReceiver.Stub() {
- @Override
- public void onEnrollResult(Fingerprint fp, int remaining) {
-
- }
-
- @Override
- public void onAcquired(int acquiredInfo, int vendorCode) {
-
- }
-
- @Override
- public void onAuthenticationSucceeded(Fingerprint fp, int userId,
- boolean isStrongBiometric) {
-
- }
-
- @Override
- public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
-
- }
-
- @Override
- public void onAuthenticationFailed() {
-
- }
-
- @Override
- public void onError(int error, int vendorCode) {
-
- }
-
- @Override
- public void onRemoved(Fingerprint fp, int remaining) {
-
- }
-
- @Override
- public void onChallengeGenerated(int sensorId, int userId, long challenge) {
-
- }
-
- @Override
- public void onUdfpsPointerDown(int sensorId) {
-
- }
-
- @Override
- public void onUdfpsPointerUp(int sensorId) {
-
- }
-
- @Override
- public void onUdfpsOverlayShown() {
-
- }
- };
-
- BiometricTestSessionImpl(@NonNull Context context, int sensorId,
- @NonNull ITestSessionCallback callback,
- @NonNull BiometricStateCallback biometricStateCallback,
- @NonNull Fingerprint21 fingerprint21,
- @NonNull Fingerprint21.HalResultController halResultController) {
- mContext = context;
- mSensorId = sensorId;
- mCallback = callback;
- mFingerprint21 = fingerprint21;
- mBiometricStateCallback = biometricStateCallback;
- mHalResultController = halResultController;
- mEnrollmentIds = new HashSet<>();
- mRandom = new Random();
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void setTestHalEnabled(boolean enabled) {
-
- super.setTestHalEnabled_enforcePermission();
-
- mFingerprint21.setTestHalEnabled(enabled);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void startEnroll(int userId) {
-
- super.startEnroll_enforcePermission();
-
- mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
- mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL,
- (new FingerprintEnrollOptions.Builder()).build());
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void finishEnroll(int userId) {
-
- super.finishEnroll_enforcePermission();
-
- int nextRandomId = mRandom.nextInt();
- while (mEnrollmentIds.contains(nextRandomId)) {
- nextRandomId = mRandom.nextInt();
- }
-
- mEnrollmentIds.add(nextRandomId);
- mHalResultController.onEnrollResult(0 /* deviceId */,
- nextRandomId /* fingerId */, userId, 0);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void acceptAuthentication(int userId) {
-
- // Fake authentication with any of the existing fingers
- super.acceptAuthentication_enforcePermission();
-
- List<Fingerprint> fingerprints = FingerprintUtils.getLegacyInstance(mSensorId)
- .getBiometricsForUser(mContext, userId);
- if (fingerprints.isEmpty()) {
- Slog.w(TAG, "No fingerprints, returning");
- return;
- }
- final int fid = fingerprints.get(0).getBiometricId();
- final ArrayList<Byte> hat = new ArrayList<>(Collections.nCopies(69, (byte) 0));
- mHalResultController.onAuthenticated(0 /* deviceId */, fid, userId, hat);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void rejectAuthentication(int userId) {
-
- super.rejectAuthentication_enforcePermission();
-
- mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* fingerId */, userId, null);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void notifyAcquired(int userId, int acquireInfo) {
-
- super.notifyAcquired_enforcePermission();
-
- mHalResultController.onAcquired(0 /* deviceId */, acquireInfo, 0 /* vendorCode */);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void notifyError(int userId, int errorCode) {
-
- super.notifyError_enforcePermission();
-
- mHalResultController.onError(0 /* deviceId */, errorCode, 0 /* vendorCode */);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void cleanupInternalState(int userId) {
-
- super.cleanupInternalState_enforcePermission();
-
- mFingerprint21.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
- @Override
- public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- try {
- mCallback.onCleanupStarted(clientMonitor.getTargetUserId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }
-
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- try {
- mCallback.onCleanupFinished(clientMonitor.getTargetUserId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }
- });
- }
-}
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
deleted file mode 100644
index 33e448b..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ /dev/null
@@ -1,1289 +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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.SynchronousUserSwitchObserver;
-import android.app.TaskStackListener;
-import android.app.UserSwitchObserver;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.IInvalidationCallback;
-import android.hardware.biometrics.ITestSession;
-import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.fingerprint.PointerContext;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
-import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.FingerprintEnrollOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IHwBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-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.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;
-import com.android.server.biometrics.Utils;
-import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto;
-import com.android.server.biometrics.fingerprint.FingerprintUserStatsProto;
-import com.android.server.biometrics.fingerprint.PerformanceStatsProto;
-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.AuthenticationStateListeners;
-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.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-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;
-import com.android.server.biometrics.sensors.RemovalConsumer;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
-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;
-import org.json.JSONObject;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.function.Supplier;
-
-/**
- * Supports a single instance of the {@link android.hardware.biometrics.fingerprint.V2_1} or
- * its extended minor versions.
- */
-public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider {
-
- private static final String TAG = "Fingerprint21";
- private static final int ENROLL_TIMEOUT_SEC = 60;
-
- private boolean mTestHalEnabled;
-
- final Context mContext;
- @NonNull private final BiometricStateCallback mBiometricStateCallback;
- @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
- private final ActivityTaskManager mActivityTaskManager;
- @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
- private final BiometricScheduler<IBiometricsFingerprint, AidlSession> mScheduler;
- private final Handler mHandler;
- private final LockoutResetDispatcher mLockoutResetDispatcher;
- private final LockoutFrameworkImpl mLockoutTracker;
- private final BiometricTaskStackListener mTaskStackListener;
- private final Supplier<IBiometricsFingerprint> mLazyDaemon;
- private final Map<Integer, Long> mAuthenticatorIds;
-
- @Nullable private IBiometricsFingerprint mDaemon;
- @NonNull private final HalResultController mHalResultController;
- @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
-
- // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
- @Nullable private ISidefpsController mSidefpsController;
- @NonNull private final BiometricContext mBiometricContext;
- @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector;
- // for requests that do not use biometric prompt
- @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
- private int mCurrentUserId = UserHandle.USER_NULL;
- private final boolean mIsUdfps;
- private final int mSensorId;
- private final boolean mIsPowerbuttonFps;
- private AidlSession mSession;
-
- private final class BiometricTaskStackListener extends TaskStackListener {
- @Override
- public void onTaskStackChanged() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationClient)) {
- Slog.e(TAG, "Task stack changed for client: " + client);
- return;
- }
- if (Utils.isKeyguard(mContext, client.getOwnerString())
- || Utils.isSystem(mContext, client.getOwnerString())) {
- return; // Keyguard is always allowed
- }
-
- if (Utils.isBackground(client.getOwnerString())
- && !client.isAlreadyDone()) {
- Slog.e(TAG, "Stopping background authentication,"
- + " currentClient: " + client);
- mScheduler.cancelAuthenticationOrDetection(
- client.getToken(), client.getRequestId());
- }
- });
- }
- }
-
- private final LockoutFrameworkImpl.LockoutResetCallback mLockoutResetCallback =
- new LockoutFrameworkImpl.LockoutResetCallback() {
- @Override
- public void onLockoutReset(int userId) {
- mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorProperties.sensorId);
- }
- };
-
- private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- scheduleInternalCleanup(newUserId, null /* callback */);
- }
- };
-
- public static class HalResultController extends IBiometricsFingerprintClientCallback.Stub {
-
- /**
- * Interface to sends results to the HalResultController's owner.
- */
- public interface Callback {
- /**
- * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
- */
- void onHardwareUnavailable();
- }
-
- private final int mSensorId;
- @NonNull private final Context mContext;
- @NonNull final Handler mHandler;
- @NonNull final BiometricScheduler<IBiometricsFingerprint, AidlSession> mScheduler;
- @Nullable private Callback mCallback;
-
- HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
- @NonNull BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler) {
- mSensorId = sensorId;
- mContext = context;
- mHandler = handler;
- mScheduler = scheduler;
- }
-
- public void setCallback(@Nullable Callback callback) {
- mCallback = callback;
- }
-
- @Override
- public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintEnrollClient)) {
- Slog.e(TAG, "onEnrollResult for non-enroll client: "
- + Utils.getClientName(client));
- return;
- }
-
- final int currentUserId = client.getTargetUserId();
- final CharSequence name = FingerprintUtils.getLegacyInstance(mSensorId)
- .getUniqueName(mContext, currentUserId);
- final Fingerprint fingerprint = new Fingerprint(name, groupId, fingerId, deviceId);
-
- final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
- enrollClient.onEnrollResult(fingerprint, 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) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AcquisitionClient)) {
- Slog.e(TAG, "onAcquired for non-acquisition client: "
- + Utils.getClientName(client));
- return;
- }
-
- final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
- acquisitionClient.onAcquired(acquiredInfo, vendorCode);
- });
- }
-
- @Override
- public void onAuthenticated(long deviceId, int fingerId, int groupId,
- ArrayList<Byte> token) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(TAG, "onAuthenticated for non-authentication consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- final boolean authenticated = fingerId != 0;
- final Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
- authenticationConsumer.onAuthenticated(fp, authenticated, token);
- });
- }
-
- @Override
- public void onError(long deviceId, int error, int vendorCode) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- Slog.d(TAG, "handleError"
- + ", client: " + Utils.getClientName(client)
- + ", error: " + error
- + ", vendorCode: " + vendorCode);
- if (!(client instanceof ErrorConsumer)) {
- Slog.e(TAG, "onError for non-error consumer: " + Utils.getClientName(client));
- return;
- }
-
- final ErrorConsumer errorConsumer = (ErrorConsumer) client;
- errorConsumer.onError(error, vendorCode);
-
- if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
- Slog.e(TAG, "Got ERROR_HW_UNAVAILABLE");
- if (mCallback != null) {
- mCallback.onHardwareUnavailable();
- }
- }
- });
- }
-
- @Override
- public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
- mHandler.post(() -> {
- Slog.d(TAG, "Removed, fingerId: " + fingerId + ", remaining: " + remaining);
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof RemovalConsumer)) {
- Slog.e(TAG, "onRemoved for non-removal consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
- final RemovalConsumer removalConsumer = (RemovalConsumer) client;
- removalConsumer.onRemoved(fp, remaining);
- });
- }
-
- @Override
- public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof EnumerateConsumer)) {
- Slog.e(TAG, "onEnumerate for non-enumerate consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
- final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;
- enumerateConsumer.onEnumerationResult(fp, remaining);
- });
- }
- }
-
- @VisibleForTesting
- Fingerprint21(@NonNull Context context,
- @NonNull BiometricStateCallback biometricStateCallback,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- @NonNull FingerprintSensorPropertiesInternal sensorProps,
- @NonNull BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler,
- @NonNull Handler handler,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull HalResultController controller,
- @NonNull BiometricContext biometricContext) {
- mContext = context;
- mBiometricStateCallback = biometricStateCallback;
- mAuthenticationStateListeners = authenticationStateListeners;
- mBiometricContext = biometricContext;
-
- mSensorProperties = sensorProps;
- mSensorId = sensorProps.sensorId;
- mIsUdfps = sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
- || sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC;
- mIsPowerbuttonFps = sensorProps.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON;
-
- mScheduler = scheduler;
- mHandler = handler;
- mActivityTaskManager = ActivityTaskManager.getInstance();
- mTaskStackListener = new BiometricTaskStackListener();
- mAuthenticatorIds = Collections.synchronizedMap(new HashMap<>());
- mLazyDaemon = Fingerprint21.this::getDaemon;
- mLockoutResetDispatcher = lockoutResetDispatcher;
- mLockoutTracker = new LockoutFrameworkImpl(context, mLockoutResetCallback);
- mHalResultController = controller;
- mHalResultController.setCallback(() -> {
- mDaemon = null;
- mCurrentUserId = UserHandle.USER_NULL;
- });
-
- AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
- new AuthenticationStatsBroadcastReceiver(
- mContext,
- BiometricsProtoEnums.MODALITY_FINGERPRINT,
- (AuthenticationStatsCollector collector) -> {
- Slog.d(TAG, "Initializing AuthenticationStatsCollector");
- mAuthenticationStatsCollector = collector;
- });
-
- try {
- ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to register user switch observer");
- }
- }
-
- public static Fingerprint21 newInstance(@NonNull Context context,
- @NonNull BiometricStateCallback biometricStateCallback,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- @NonNull FingerprintSensorPropertiesInternal sensorProps,
- @NonNull Handler handler,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
- final BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler =
- new BiometricScheduler<>(
- BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps),
- gestureAvailabilityDispatcher);
- final HalResultController controller = new HalResultController(sensorProps.sensorId,
- context, handler, scheduler);
- return new Fingerprint21(context, biometricStateCallback, authenticationStateListeners,
- sensorProps, scheduler, handler, lockoutResetDispatcher, controller,
- BiometricContext.getInstance(context));
- }
-
- @Override
- public void serviceDied(long cookie) {
- Slog.e(TAG, "HAL died");
- mHandler.post(() -> {
- PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId)
- .incrementHALDeathCount();
- mDaemon = null;
- mCurrentUserId = UserHandle.USER_NULL;
-
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (client instanceof ErrorConsumer) {
- Slog.e(TAG, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
- final ErrorConsumer errorConsumer = (ErrorConsumer) client;
- errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
-
- FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
- BiometricsProtoEnums.MODALITY_FINGERPRINT,
- BiometricsProtoEnums.ISSUE_HAL_DEATH,
- -1 /* sensorId */);
- }
-
- mScheduler.recordCrashState();
- mScheduler.reset();
- });
- }
-
- 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) {
- final TestHal testHal = new TestHal(mContext, mSensorId);
- testHal.setNotify(mHalResultController);
- return testHal;
- }
-
- if (mDaemon != null) {
- return mDaemon;
- }
-
- Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
- + mScheduler.getCurrentClient());
- try {
- mDaemon = IBiometricsFingerprint.getService();
- } catch (java.util.NoSuchElementException e) {
- // Service doesn't exist or cannot be opened.
- Slog.w(TAG, "NoSuchElementException", e);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to get fingerprint HAL", e);
- }
-
- if (mDaemon == null) {
- Slog.w(TAG, "Fingerprint HAL not available");
- return null;
- }
-
- mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
-
- // HAL ID for these HIDL versions are only used to determine if callbacks have been
- // successfully set.
- long halId = 0;
- try {
- halId = mDaemon.setNotify(mHalResultController);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to set callback for fingerprint HAL", e);
- mDaemon = null;
- }
-
- Slog.d(TAG, "Fingerprint HAL ready, HAL ID: " + halId);
- if (halId != 0) {
- scheduleLoadAuthenticatorIds();
- scheduleInternalCleanup(ActivityManager.getCurrentUser(), null /* callback */);
- } else {
- Slog.e(TAG, "Unable to set callback");
- mDaemon = null;
- }
-
- return mDaemon;
- }
-
- @Nullable IUdfpsOverlayController getUdfpsOverlayController() {
- return mUdfpsOverlayController;
- }
-
- private void scheduleLoadAuthenticatorIds() {
- // Note that this can be performed on the scheduler (as opposed to being done immediately
- // when the HAL is (re)loaded, since
- // 1) If this is truly the first time it's being performed (e.g. system has just started),
- // this will be run very early and way before any applications need to generate keys.
- // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has
- // just been reloaded), the framework already has a cache of the authenticatorIds. This
- // is safe because authenticatorIds only change when A) new template has been enrolled,
- // or B) all templates are removed.
- mHandler.post(() -> {
- for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
- final int targetUserId = user.id;
- if (!mAuthenticatorIds.containsKey(targetUserId)) {
- scheduleUpdateActiveUserWithoutHandler(targetUserId, true /* force */);
- }
- }
- });
- }
-
- private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
- scheduleUpdateActiveUserWithoutHandler(targetUserId, false /* force */);
- }
-
- /**
- * Schedules the {@link FingerprintUpdateActiveUserClient} without posting the work onto the
- * handler. Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
- * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
- * this operation on the same lambda/runnable as those operations so that the ordering is
- * correct.
- *
- * @param targetUserId Switch to this user, and update their authenticatorId
- * @param force Always retrieve the authenticatorId, even if we are already the targetUserId
- */
- private void scheduleUpdateActiveUserWithoutHandler(int targetUserId, boolean force) {
- final boolean hasEnrolled =
- !getEnrolledFingerprints(mSensorProperties.sensorId, targetUserId).isEmpty();
- final FingerprintUpdateActiveUserClientLegacy client =
- new FingerprintUpdateActiveUserClientLegacy(mContext, mLazyDaemon, targetUserId,
- mContext.getOpPackageName(), mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext,
- this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- 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);
- }
- }
- });
- }
-
- private int getCurrentUser() {
- return mCurrentUserId;
- }
-
- @Override
- public boolean containsSensor(int sensorId) {
- return mSensorProperties.sensorId == sensorId;
- }
-
- @Override
- @NonNull
- public List<FingerprintSensorPropertiesInternal> getSensorProperties() {
- final List<FingerprintSensorPropertiesInternal> properties = new ArrayList<>();
- properties.add(mSensorProperties);
- return properties;
- }
-
- @Nullable
- @Override
- public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId) {
- return mSensorProperties;
- }
-
- @Override
- public void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
- // Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler
- // thread.
- mHandler.post(() -> {
- 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(() -> {
- 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(() -> {
- 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,
- @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
- @FingerprintManager.EnrollReason int enrollReason,
- @NonNull FingerprintEnrollOptions options) {
- final long id = mRequestCounter.incrementAndGet();
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- if (Flags.deHidl()) {
- scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver,
- opPackageName, enrollReason, id, options);
- } else {
- scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver,
- opPackageName, enrollReason, id, options);
- }
- });
- 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,
- @NonNull FingerprintEnrollOptions options) {
- 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,
- // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
- mSidefpsController,
- mAuthenticationStateListeners, enrollReason, options);
- 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,
- @NonNull FingerprintEnrollOptions options) {
- 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,
- // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
- mSidefpsController,
- mAuthenticationStateListeners,
- mContext.getResources().getInteger(
- com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser),
- enrollReason, options);
- 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));
- }
-
- @Override
- public long scheduleFingerDetect(@NonNull IBinder token,
- @NonNull ClientMonitorCallbackConverter listener,
- @NonNull FingerprintAuthenticateOptions options,
- int statsClient) {
- final long id = mRequestCounter.incrementAndGet();
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(options.getUserId());
-
- final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
-
- 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,
- @NonNull FingerprintAuthenticateOptions options,
- long requestId, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(options.getUserId());
-
- final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
-
- 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,
- mAuthenticationStateListeners,
- 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,
- mAuthenticationStateListeners,
- allowBackgroundAuthentication, mSensorProperties,
- Utils.getCurrentStrength(mSensorId));
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
- }
-
- @Override
- public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
- int cookie, @NonNull ClientMonitorCallbackConverter listener,
- @NonNull FingerprintAuthenticateOptions options, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication) {
- final long id = mRequestCounter.incrementAndGet();
-
- scheduleAuthenticate(token, operationId, cookie, listener,
- options, id, restricted, statsClient, allowBackgroundAuthentication);
-
- return id;
- }
-
- @Override
- public void startPreparedClient(int sensorId, int cookie) {
- mHandler.post(() -> mScheduler.startPreparedClient(cookie));
- }
-
- @Override
- public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
- Slog.d(TAG, "cancelAuthentication, sensorId: " + sensorId);
- mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token, requestId));
- }
-
- @Override
- public void scheduleRemove(int sensorId, @NonNull IBinder token,
- @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
- @NonNull String opPackageName) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- 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,
- @NonNull String opPackageName) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- // For [email protected], remove(0) means remove all enrollments
- if (Flags.deHidl()) {
- scheduleRemoveAidl(token, receiver, 0 /* fingerId */, userId, opPackageName);
- } else {
- scheduleRemoveHidl(token, receiver, 0 /* fingerId */, userId, opPackageName);
- }
- });
- }
-
- private void scheduleInternalCleanup(int userId,
- @Nullable ClientMonitorCallback callback) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- 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) {
- scheduleInternalCleanup(userId, new ClientMonitorCompositeCallback(callback,
- mBiometricStateCallback));
- }
-
- @Override
- public void scheduleInternalCleanup(int sensorId, int userId,
- @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
- scheduleInternalCleanup(userId, new ClientMonitorCompositeCallback(callback,
- mBiometricStateCallback));
- }
-
- private BiometricLogger createLogger(int statsAction, int statsClient,
- AuthenticationStatsCollector authenticationStatsCollector) {
- return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
- statsAction, statsClient, authenticationStatsCollector);
- }
-
- @Override
- public boolean isHardwareDetected(int sensorId) {
- return getDaemon() != null;
- }
-
- @Override
- public void rename(int sensorId, int fingerId, int userId, @NonNull String name) {
- mHandler.post(() -> {
- FingerprintUtils.getLegacyInstance(mSensorId)
- .renameBiometricForUser(mContext, userId, fingerId, name);
- });
- }
-
- @Override
- @NonNull
- public List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId) {
- return FingerprintUtils.getLegacyInstance(mSensorId).getBiometricsForUser(mContext, userId);
- }
-
- @Override
- public boolean hasEnrollments(int sensorId, int userId) {
- return !getEnrolledFingerprints(sensorId, userId).isEmpty();
- }
-
- @Override
- @LockoutTracker.LockoutMode public int getLockoutModeForUser(int sensorId, int userId) {
- return mLockoutTracker.getLockoutModeForUser(userId);
- }
-
- @Override
- public long getAuthenticatorId(int sensorId, int userId) {
- return mAuthenticatorIds.getOrDefault(userId, 0L);
- }
-
- @Override
- public void onPointerDown(long requestId, int sensorId, PointerContext pc) {
- mScheduler.getCurrentClientIfMatches(requestId, (client) -> {
- if (!(client instanceof Udfps)) {
- Slog.w(TAG, "onFingerDown received during client: " + client);
- return;
- }
- ((Udfps) client).onPointerDown(pc);
- });
- }
-
- @Override
- public void onPointerUp(long requestId, int sensorId, PointerContext pc) {
- mScheduler.getCurrentClientIfMatches(requestId, (client) -> {
- if (!(client instanceof Udfps)) {
- Slog.w(TAG, "onFingerDown received during client: " + client);
- return;
- }
- ((Udfps) client).onPointerUp(pc);
- });
- }
-
- @Override
- public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId,
- int sensorId) {
- mScheduler.getCurrentClientIfMatches(requestId, (client) -> {
- if (!(client instanceof Udfps)) {
- Slog.w(TAG, "onUdfpsUiEvent received during client: " + client);
- return;
- }
- ((Udfps) client).onUdfpsUiEvent(event);
- });
- }
-
- @Override
- public void onPowerPressed() {
- Slog.e(TAG, "onPowerPressed not supported for HIDL clients");
- }
-
- @Override
- public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
- mUdfpsOverlayController = controller;
- }
-
- // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
- @Override
- public void setSidefpsController(@NonNull ISidefpsController controller) {
- mSidefpsController = controller;
- }
-
- @Override
- public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
- boolean clearSchedulerBuffer) {
- final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
-
- proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
- proto.write(SensorStateProto.MODALITY, SensorStateProto.FINGERPRINT);
- if (mSensorProperties.isAnyUdfpsType()) {
- proto.write(SensorStateProto.MODALITY_FLAGS, SensorStateProto.FINGERPRINT_UDFPS);
- }
- proto.write(SensorStateProto.CURRENT_STRENGTH,
- Utils.getCurrentStrength(mSensorProperties.sensorId));
- proto.write(SensorStateProto.SCHEDULER, mScheduler.dumpProtoState(clearSchedulerBuffer));
-
- for (UserInfo user : UserManager.get(mContext).getUsers()) {
- final int userId = user.getUserHandle().getIdentifier();
-
- final long userToken = proto.start(SensorStateProto.USER_STATES);
- proto.write(UserStateProto.USER_ID, userId);
- proto.write(UserStateProto.NUM_ENROLLED, FingerprintUtils.getLegacyInstance(mSensorId)
- .getBiometricsForUser(mContext, userId).size());
- proto.end(userToken);
- }
-
- proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_HARDWARE_AUTH_TOKEN,
- mSensorProperties.resetLockoutRequiresHardwareAuthToken);
- proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_CHALLENGE,
- mSensorProperties.resetLockoutRequiresChallenge);
-
- proto.end(sensorToken);
- }
-
- @Override
- public void dumpProtoMetrics(int sensorId, FileDescriptor fd) {
- PerformanceTracker tracker =
- PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
-
- final ProtoOutputStream proto = new ProtoOutputStream(fd);
- for (UserInfo user : UserManager.get(mContext).getUsers()) {
- final int userId = user.getUserHandle().getIdentifier();
-
- final long userToken = proto.start(FingerprintServiceDumpProto.USERS);
-
- proto.write(FingerprintUserStatsProto.USER_ID, userId);
- proto.write(FingerprintUserStatsProto.NUM_FINGERPRINTS,
- FingerprintUtils.getLegacyInstance(mSensorId)
- .getBiometricsForUser(mContext, userId).size());
-
- // Normal fingerprint authentications (e.g. lockscreen)
- long countsToken = proto.start(FingerprintUserStatsProto.NORMAL);
- proto.write(PerformanceStatsProto.ACCEPT, tracker.getAcceptForUser(userId));
- proto.write(PerformanceStatsProto.REJECT, tracker.getRejectForUser(userId));
- proto.write(PerformanceStatsProto.ACQUIRE, tracker.getAcquireForUser(userId));
- proto.write(PerformanceStatsProto.LOCKOUT, tracker.getTimedLockoutForUser(userId));
- proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT,
- tracker.getPermanentLockoutForUser(userId));
- proto.end(countsToken);
-
- // Statistics about secure fingerprint transactions (e.g. to unlock password
- // storage, make secure purchases, etc.)
- countsToken = proto.start(FingerprintUserStatsProto.CRYPTO);
- proto.write(PerformanceStatsProto.ACCEPT, tracker.getAcceptCryptoForUser(userId));
- proto.write(PerformanceStatsProto.REJECT, tracker.getRejectCryptoForUser(userId));
- proto.write(PerformanceStatsProto.ACQUIRE, tracker.getAcquireCryptoForUser(userId));
- proto.write(PerformanceStatsProto.LOCKOUT, 0); // meaningless for crypto
- proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, 0); // meaningless for crypto
- proto.end(countsToken);
-
- proto.end(userToken);
- }
- proto.flush();
- tracker.clear();
- }
-
- @Override
- public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
- @NonNull IInvalidationCallback callback) {
- // TODO (b/179101888): Remove this temporary workaround.
- try {
- callback.onCompleted();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to complete InvalidateAuthenticatorId");
- }
- }
-
- @Override
- public void dumpInternal(int sensorId, @NonNull PrintWriter pw) {
- PerformanceTracker performanceTracker =
- PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
-
- JSONObject dump = new JSONObject();
- try {
- dump.put("service", TAG);
- dump.put("isUdfps", mIsUdfps);
- dump.put("isPowerbuttonFps", mIsPowerbuttonFps);
-
- JSONArray sets = new JSONArray();
- for (UserInfo user : UserManager.get(mContext).getUsers()) {
- final int userId = user.getUserHandle().getIdentifier();
- final int N = FingerprintUtils.getLegacyInstance(mSensorId)
- .getBiometricsForUser(mContext, userId).size();
- JSONObject set = new JSONObject();
- set.put("id", userId);
- set.put("count", N);
- set.put("accept", performanceTracker.getAcceptForUser(userId));
- set.put("reject", performanceTracker.getRejectForUser(userId));
- set.put("acquire", performanceTracker.getAcquireForUser(userId));
- set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
- set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
- // cryptoStats measures statistics about secure fingerprint transactions
- // (e.g. to unlock password storage, make secure purchases, etc.)
- set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
- set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
- set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
- sets.put(set);
- }
-
- dump.put("prints", sets);
- } catch (JSONException e) {
- Slog.e(TAG, "dump formatting failure", e);
- }
- pw.println(dump);
- pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
- mScheduler.dump(pw);
- }
-
- void setTestHalEnabled(boolean enabled) {
- mTestHalEnabled = enabled;
- }
-
- @NonNull
- @Override
- public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
- @NonNull String opPackageName) {
- return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback,
- mBiometricStateCallback, this, mHalResultController);
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
deleted file mode 100644
index f857946..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ /dev/null
@@ -1,573 +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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.trust.TrustManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.PointerContext;
-import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
-import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.Slog;
-import android.util.SparseBooleanArray;
-
-import com.android.internal.R;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.sensors.AuthenticationConsumer;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-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.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-/**
- * A mockable/testable provider of the {@link android.hardware.biometrics.fingerprint.V2_3} HIDL
- * interface. This class is intended simulate UDFPS logic for devices that do not have an actual
- * [email protected] HAL (where UDFPS starts to become supported)
- *
- * UDFPS "accept" can only happen within a set amount of time after a sensor authentication. This is
- * specified by {@link MockHalResultController#AUTH_VALIDITY_MS}. Touches after this duration will
- * be treated as "reject".
- *
- * This class provides framework logic to emulate, for testing only, the UDFPS functionalies below:
- *
- * 1) IF either A) the caller is keyguard, and the device is not in a trusted state (authenticated
- * via biometric sensor or unlocked with a trust agent {@see android.app.trust.TrustManager}, OR
- * B) the caller is not keyguard, and regardless of trusted state, AND (following applies to both
- * (A) and (B) above) {@link FingerprintManager#onFingerDown(int, int, float, float)} is
- * received, this class will respond with {@link AuthenticationCallback#onAuthenticationFailed()}
- * after a tunable flat_time + variance_time.
- *
- * In the case above (1), callers must not receive a successful authentication event here because
- * the sensor has not actually been authenticated.
- *
- * 2) IF A) the caller is keyguard and the device is not in a trusted state, OR B) the caller is not
- * keyguard and regardless of trusted state, AND (following applies to both (A) and (B)) the
- * sensor is touched and the fingerprint is accepted by the HAL, and then
- * {@link FingerprintManager#onFingerDown(int, int, float, float)} is received, this class will
- * respond with {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)}
- * after a tunable flat_time + variance_time. Note that the authentication callback from the
- * sensor is held until {@link FingerprintManager#onFingerDown(int, int, float, float)} is
- * invoked.
- *
- * In the case above (2), callers can receive a successful authentication callback because the
- * real sensor was authenticated. Note that even though the real sensor was touched, keyguard
- * fingerprint authentication does not put keyguard into a trusted state because the
- * authentication callback is held until onFingerDown was invoked. This allows callers such as
- * keyguard to simulate a realistic path.
- *
- * 3) IF the caller is keyguard AND the device in a trusted state and then
- * {@link FingerprintManager#onFingerDown(int, int, float, float)} is received, this class will
- * respond with {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)}
- * after a tunable flat_time + variance time.
- *
- * In the case above (3), since the device is already unlockable via trust agent, it's fine to
- * simulate the successful auth path. Non-keyguard clients will fall into either (1) or (2)
- * above.
- *
- * This class currently does not simulate false rejection. Conversely, this class relies on the
- * real hardware sensor so does not affect false acceptance.
- */
-@SuppressWarnings("deprecation")
-public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManager.TrustListener {
-
- private static final String TAG = "Fingerprint21UdfpsMock";
-
- // Secure setting integer. If true, the system will load this class to enable udfps testing.
- public static final String CONFIG_ENABLE_TEST_UDFPS =
- "com.android.server.biometrics.sensors.fingerprint.test_udfps.enable";
- // Secure setting integer. A fixed duration intended to simulate something like the duration
- // required for image capture.
- private static final String CONFIG_AUTH_DELAY_PT1 =
- "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_pt1";
- // Secure setting integer. A fixed duration intended to simulate something like the duration
- // required for template matching to complete.
- private static final String CONFIG_AUTH_DELAY_PT2 =
- "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_pt2";
- // Secure setting integer. A random value between [-randomness, randomness] will be added to the
- // capture delay above for each accept/reject.
- private static final String CONFIG_AUTH_DELAY_RANDOMNESS =
- "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_randomness";
-
- private static final int DEFAULT_AUTH_DELAY_PT1_MS = 300;
- private static final int DEFAULT_AUTH_DELAY_PT2_MS = 400;
- private static final int DEFAULT_AUTH_DELAY_RANDOMNESS_MS = 100;
-
- @NonNull private final TestableBiometricScheduler mScheduler;
- @NonNull private final Handler mHandler;
- @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
- @NonNull private final MockHalResultController mMockHalResultController;
- @NonNull private final TrustManager mTrustManager;
- @NonNull private final SparseBooleanArray mUserHasTrust;
- @NonNull private final Random mRandom;
- @NonNull private final FakeRejectRunnable mFakeRejectRunnable;
- @NonNull private final FakeAcceptRunnable mFakeAcceptRunnable;
- @NonNull private final RestartAuthRunnable mRestartAuthRunnable;
-
- private static class TestableBiometricScheduler extends BiometricScheduler {
- @NonNull private Fingerprint21UdfpsMock mFingerprint21;
-
- TestableBiometricScheduler(
- @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
- super(BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher);
- }
-
- void init(@NonNull Fingerprint21UdfpsMock fingerprint21) {
- mFingerprint21 = fingerprint21;
- }
- }
-
- /**
- * All of the mocking/testing should happen in here. This way we don't need to modify the
- * {@link BaseClientMonitor} implementations and can run the
- * real path there.
- */
- private static class MockHalResultController extends HalResultController {
-
- // Duration for which a sensor authentication can be treated as UDFPS success.
- private static final int AUTH_VALIDITY_MS = 10 * 1000; // 10 seconds
-
- static class LastAuthArgs {
- @NonNull final AuthenticationConsumer lastAuthenticatedClient;
- final long deviceId;
- final int fingerId;
- final int groupId;
- @Nullable final ArrayList<Byte> token;
-
- LastAuthArgs(@NonNull AuthenticationConsumer authenticationConsumer, long deviceId,
- int fingerId, int groupId, @Nullable ArrayList<Byte> token) {
- lastAuthenticatedClient = authenticationConsumer;
- this.deviceId = deviceId;
- this.fingerId = fingerId;
- this.groupId = groupId;
- if (token == null) {
- this.token = null;
- } else {
- // Store a copy so the owner can be GC'd
- this.token = new ArrayList<>(token);
- }
- }
- }
-
- // Initialized after the constructor, but before it's ever used.
- @NonNull private RestartAuthRunnable mRestartAuthRunnable;
- @NonNull private Fingerprint21UdfpsMock mFingerprint21;
- @Nullable private LastAuthArgs mLastAuthArgs;
-
- MockHalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
- @NonNull BiometricScheduler scheduler) {
- super(sensorId, context, handler, scheduler);
- }
-
- void init(@NonNull RestartAuthRunnable restartAuthRunnable,
- @NonNull Fingerprint21UdfpsMock fingerprint21) {
- mRestartAuthRunnable = restartAuthRunnable;
- mFingerprint21 = fingerprint21;
- }
-
- @Nullable AuthenticationConsumer getLastAuthenticatedClient() {
- return mLastAuthArgs != null ? mLastAuthArgs.lastAuthenticatedClient : null;
- }
-
- /**
- * Intercepts the HAL authentication and holds it until the UDFPS simulation decides
- * that authentication finished.
- */
- @Override
- public void onAuthenticated(long deviceId, int fingerId, int groupId,
- ArrayList<Byte> token) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(TAG, "Non authentication consumer: " + client);
- return;
- }
-
- final boolean accepted = fingerId != 0;
- if (accepted) {
- mFingerprint21.setDebugMessage("Finger accepted");
- } else {
- mFingerprint21.setDebugMessage("Finger rejected");
- }
-
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- mLastAuthArgs = new LastAuthArgs(authenticationConsumer, deviceId, fingerId,
- groupId, token);
-
- // Remove any existing restart runnbables since auth just started, and put a new
- // one on the queue.
- mHandler.removeCallbacks(mRestartAuthRunnable);
- mRestartAuthRunnable.setLastAuthReference(authenticationConsumer);
- mHandler.postDelayed(mRestartAuthRunnable, AUTH_VALIDITY_MS);
- });
- }
-
- /**
- * Calls through to the real interface and notifies clients of accept/reject.
- */
- void sendAuthenticated(long deviceId, int fingerId, int groupId,
- ArrayList<Byte> token) {
- Slog.d(TAG, "sendAuthenticated: " + (fingerId != 0));
- mFingerprint21.setDebugMessage("Udfps match: " + (fingerId != 0));
- super.onAuthenticated(deviceId, fingerId, groupId, token);
- }
- }
-
- public static Fingerprint21UdfpsMock newInstance(@NonNull Context context,
- @NonNull BiometricStateCallback biometricStateCallback,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- @NonNull FingerprintSensorPropertiesInternal sensorProps,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
- @NonNull BiometricContext biometricContext) {
- Slog.d(TAG, "Creating Fingerprint23Mock!");
-
- final Handler handler = new Handler(Looper.getMainLooper());
- final TestableBiometricScheduler scheduler =
- new TestableBiometricScheduler(gestureAvailabilityDispatcher);
- final MockHalResultController controller =
- new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
- return new Fingerprint21UdfpsMock(context, biometricStateCallback,
- authenticationStateListeners, sensorProps, scheduler, handler,
- lockoutResetDispatcher, controller, biometricContext);
- }
-
- private static abstract class FakeFingerRunnable implements Runnable {
- private long mFingerDownTime;
- private int mCaptureDuration;
-
- /**
- * @param fingerDownTime System time when onFingerDown occurred
- * @param captureDuration Duration that the finger needs to be down for
- */
- void setSimulationTime(long fingerDownTime, int captureDuration) {
- mFingerDownTime = fingerDownTime;
- mCaptureDuration = captureDuration;
- }
-
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- boolean isImageCaptureComplete() {
- return System.currentTimeMillis() - mFingerDownTime > mCaptureDuration;
- }
- }
-
- private final class FakeRejectRunnable extends FakeFingerRunnable {
- @Override
- public void run() {
- mMockHalResultController.sendAuthenticated(0, 0, 0, null);
- }
- }
-
- private final class FakeAcceptRunnable extends FakeFingerRunnable {
- @Override
- public void run() {
- if (mMockHalResultController.mLastAuthArgs == null) {
- // This can happen if the user has trust agents enabled, which make lockscreen
- // dismissable. Send a fake non-zero (accept) finger.
- Slog.d(TAG, "Sending fake finger");
- mMockHalResultController.sendAuthenticated(1 /* deviceId */,
- 1 /* fingerId */, 1 /* groupId */, null /* token */);
- } else {
- mMockHalResultController.sendAuthenticated(
- mMockHalResultController.mLastAuthArgs.deviceId,
- mMockHalResultController.mLastAuthArgs.fingerId,
- mMockHalResultController.mLastAuthArgs.groupId,
- mMockHalResultController.mLastAuthArgs.token);
- }
- }
- }
-
- /**
- * The fingerprint HAL allows multiple (5) fingerprint attempts per HIDL invocation of the
- * authenticate method. However, valid fingerprint authentications are invalidated after
- * {@link MockHalResultController#AUTH_VALIDITY_MS}, meaning UDFPS touches will be reported as
- * rejects if touched after that duration. However, since a valid fingerprint was detected, the
- * HAL and FingerprintService will not look for subsequent fingerprints.
- *
- * In order to keep the FingerprintManager API consistent (that multiple fingerprint attempts
- * are allowed per auth lifecycle), we internally cancel and restart authentication so that the
- * sensor is responsive again.
- */
- private static final class RestartAuthRunnable implements Runnable {
- @NonNull private final Fingerprint21UdfpsMock mFingerprint21;
- @NonNull private final TestableBiometricScheduler mScheduler;
-
- // Store a reference to the auth consumer that should be invalidated.
- private AuthenticationConsumer mLastAuthConsumer;
-
- RestartAuthRunnable(@NonNull Fingerprint21UdfpsMock fingerprint21,
- @NonNull TestableBiometricScheduler scheduler) {
- mFingerprint21 = fingerprint21;
- mScheduler = scheduler;
- }
-
- void setLastAuthReference(AuthenticationConsumer lastAuthConsumer) {
- mLastAuthConsumer = lastAuthConsumer;
- }
-
- @Override
- public void run() {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
-
- // We don't care about FingerprintDetectClient, since accept/rejects are both OK. UDFPS
- // rejects will just simulate the path where non-enrolled fingers are presented.
- if (!(client instanceof FingerprintAuthenticationClient)) {
- Slog.e(TAG, "Non-FingerprintAuthenticationClient client: " + client);
- return;
- }
-
- // Perhaps the runnable is stale, or the user stopped/started auth manually. Do not
- // restart auth in this case.
- if (client != mLastAuthConsumer) {
- Slog.e(TAG, "Current client: " + client
- + " does not match mLastAuthConsumer: " + mLastAuthConsumer);
- return;
- }
-
- Slog.d(TAG, "Restarting auth, current: " + client);
- mFingerprint21.setDebugMessage("Auth timed out");
-
- final FingerprintAuthenticationClient authClient =
- (FingerprintAuthenticationClient) client;
- // Store the authClient parameters so it can be rescheduled
- final IBinder token = client.getToken();
- final long operationId = authClient.getOperationId();
- final int cookie = client.getCookie();
- final ClientMonitorCallbackConverter listener = new ClientMonitorCallbackConverter(
- new IFingerprintServiceReceiver.Default());
- final boolean restricted = authClient.isRestricted();
- final int statsClient = client.getLogger().getStatsClient();
- final boolean isKeyguard = authClient.isKeyguard();
- final FingerprintAuthenticateOptions options =
- new FingerprintAuthenticateOptions.Builder()
- .setUserId(client.getTargetUserId())
- .setOpPackageName(client.getOwnerString())
- .build();
-
- // Don't actually send cancel() to the HAL, since successful auth already finishes
- // HAL authenticate() lifecycle. Just
- mScheduler.getInternalCallback().onClientFinished(client, true /* success */);
-
- // Schedule this only after we invoke onClientFinished for the previous client, so that
- // internal preemption logic is not run.
- mFingerprint21.scheduleAuthenticate(token,
- operationId, cookie, listener, options, restricted, statsClient,
- isKeyguard);
- }
- }
-
- private Fingerprint21UdfpsMock(@NonNull Context context,
- @NonNull BiometricStateCallback biometricStateCallback,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- @NonNull FingerprintSensorPropertiesInternal sensorProps,
- @NonNull TestableBiometricScheduler scheduler,
- @NonNull Handler handler,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull MockHalResultController controller,
- @NonNull BiometricContext biometricContext) {
- super(context, biometricStateCallback, authenticationStateListeners, sensorProps, scheduler,
- handler, lockoutResetDispatcher, controller, biometricContext);
- mScheduler = scheduler;
- mScheduler.init(this);
- mHandler = handler;
- // resetLockout is controlled by the framework, so hardwareAuthToken is not required
- final boolean resetLockoutRequiresHardwareAuthToken = false;
- final int maxTemplatesAllowed = mContext.getResources()
- .getInteger(R.integer.config_fingerprintMaxTemplatesPerUser);
- mSensorProperties = new FingerprintSensorPropertiesInternal(sensorProps.sensorId,
- sensorProps.sensorStrength, maxTemplatesAllowed, sensorProps.componentInfo,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, false /* halControlsIllumination */,
- resetLockoutRequiresHardwareAuthToken, sensorProps.getAllLocations());
- mMockHalResultController = controller;
- mUserHasTrust = new SparseBooleanArray();
- mTrustManager = context.getSystemService(TrustManager.class);
- mTrustManager.registerTrustListener(this);
- mRandom = new Random();
- mFakeRejectRunnable = new FakeRejectRunnable();
- mFakeAcceptRunnable = new FakeAcceptRunnable();
- mRestartAuthRunnable = new RestartAuthRunnable(this, mScheduler);
-
- // We can't initialize this during MockHalresultController's constructor due to a circular
- // dependency.
- mMockHalResultController.init(mRestartAuthRunnable, this);
- }
-
- @Override
- public void onTrustChanged(boolean enabled, boolean newlyUnlocked, int userId, int flags,
- List<String> trustGrantedMessages) {
- mUserHasTrust.put(userId, enabled);
- }
-
- @Override
- public void onTrustManagedChanged(boolean enabled, int userId) {
-
- }
-
- @Override
- public void onTrustError(CharSequence message) {
-
- }
-
- @Override
- public void onEnabledTrustAgentsChanged(int userId) {
-
- }
-
- @Override
- public void onIsActiveUnlockRunningChanged(boolean isRunning, int userId) {
-
- }
-
- @Override
- @NonNull
- public List<FingerprintSensorPropertiesInternal> getSensorProperties() {
- final List<FingerprintSensorPropertiesInternal> properties = new ArrayList<>();
- properties.add(mSensorProperties);
- return properties;
- }
-
- @Override
- public void onPointerDown(long requestId, int sensorId, PointerContext pc) {
- mHandler.post(() -> {
- Slog.d(TAG, "onFingerDown");
- final AuthenticationConsumer lastAuthenticatedConsumer =
- mMockHalResultController.getLastAuthenticatedClient();
- final BaseClientMonitor currentScheduledClient = mScheduler.getCurrentClient();
-
- if (currentScheduledClient == null) {
- Slog.d(TAG, "Not authenticating");
- return;
- }
-
- mHandler.removeCallbacks(mFakeRejectRunnable);
- mHandler.removeCallbacks(mFakeAcceptRunnable);
-
- // The sensor was authenticated, is still the currently scheduled client, and the
- // user touched the UDFPS affordance. Pretend that auth succeeded.
- final boolean authenticatedClientIsCurrent = lastAuthenticatedConsumer != null
- && lastAuthenticatedConsumer == currentScheduledClient;
- // User is unlocked on keyguard via Trust Agent
- final boolean keyguardAndTrusted;
- if (currentScheduledClient instanceof FingerprintAuthenticationClient) {
- keyguardAndTrusted = ((FingerprintAuthenticationClient) currentScheduledClient)
- .isKeyguard()
- && mUserHasTrust.get(currentScheduledClient.getTargetUserId(), false);
- } else {
- keyguardAndTrusted = false;
- }
-
- final int captureDuration = getNewCaptureDuration();
- final int matchingDuration = getMatchingDuration();
- final int totalDuration = captureDuration + matchingDuration;
- setDebugMessage("Duration: " + totalDuration
- + " (" + captureDuration + " + " + matchingDuration + ")");
- if (authenticatedClientIsCurrent || keyguardAndTrusted) {
- mFakeAcceptRunnable.setSimulationTime(System.currentTimeMillis(), captureDuration);
- mHandler.postDelayed(mFakeAcceptRunnable, totalDuration);
- } else if (currentScheduledClient instanceof AuthenticationConsumer) {
- // Something is authenticating but authentication has not succeeded yet. Pretend
- // that auth rejected.
- mFakeRejectRunnable.setSimulationTime(System.currentTimeMillis(), captureDuration);
- mHandler.postDelayed(mFakeRejectRunnable, totalDuration);
- }
- });
- }
-
- @Override
- public void onPointerUp(long requestId, int sensorId, PointerContext pc) {
- mHandler.post(() -> {
- Slog.d(TAG, "onFingerUp");
-
- // Only one of these can be on the handler at any given time (see onFingerDown). If
- // image capture is not complete, send ACQUIRED_TOO_FAST and remove the runnable from
- // the handler. Image capture (onFingerDown) needs to happen again.
- if (mHandler.hasCallbacks(mFakeRejectRunnable)
- && !mFakeRejectRunnable.isImageCaptureComplete()) {
- mHandler.removeCallbacks(mFakeRejectRunnable);
- mMockHalResultController.onAcquired(0 /* deviceId */,
- FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST,
- 0 /* vendorCode */);
- } else if (mHandler.hasCallbacks(mFakeAcceptRunnable)
- && !mFakeAcceptRunnable.isImageCaptureComplete()) {
- mHandler.removeCallbacks(mFakeAcceptRunnable);
- mMockHalResultController.onAcquired(0 /* deviceId */,
- FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST,
- 0 /* vendorCode */);
- }
- });
- }
-
- private int getNewCaptureDuration() {
- final ContentResolver contentResolver = mContext.getContentResolver();
- final int captureTime = Settings.Secure.getIntForUser(contentResolver,
- CONFIG_AUTH_DELAY_PT1,
- DEFAULT_AUTH_DELAY_PT1_MS,
- UserHandle.USER_CURRENT);
- final int randomDelayRange = Settings.Secure.getIntForUser(contentResolver,
- CONFIG_AUTH_DELAY_RANDOMNESS,
- DEFAULT_AUTH_DELAY_RANDOMNESS_MS,
- UserHandle.USER_CURRENT);
- final int randomDelay = mRandom.nextInt(randomDelayRange * 2) - randomDelayRange;
-
- // Must be at least 0
- return Math.max(captureTime + randomDelay, 0);
- }
-
- private int getMatchingDuration() {
- final int matchingTime = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- CONFIG_AUTH_DELAY_PT2,
- DEFAULT_AUTH_DELAY_PT2_MS,
- UserHandle.USER_CURRENT);
-
- // Must be at least 0
- return Math.max(matchingTime, 0);
- }
-
- private void setDebugMessage(String message) {
- try {
- final IUdfpsOverlayController controller = getUdfpsOverlayController();
- // Things can happen before SysUI loads and sets the controller.
- if (controller != null) {
- Slog.d(TAG, "setDebugMessage: " + message);
- controller.setDebugMessage(mSensorProperties.sensorId, message);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when sending message: " + message, e);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
deleted file mode 100644
index b6311af..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ /dev/null
@@ -1,315 +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.biometrics.sensors.fingerprint.hidl;
-
-import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
-
-import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-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.BiometricManager.Authenticators;
-import android.hardware.biometrics.BiometricSourceType;
-import android.hardware.biometrics.fingerprint.PointerContext;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.log.CallbackWithProbe;
-import com.android.server.biometrics.log.Probe;
-import com.android.server.biometrics.sensors.AuthenticationClient;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricNotificationUtils;
-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.LockoutTracker;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.SensorOverlays;
-import com.android.server.biometrics.sensors.fingerprint.Udfps;
-import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific authentication client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-class FingerprintAuthenticationClient
- extends AuthenticationClient<IBiometricsFingerprint, FingerprintAuthenticateOptions>
- implements Udfps {
-
- private static final String TAG = "Biometrics/FingerprintAuthClient";
-
- private final LockoutFrameworkImpl mLockoutFrameworkImpl;
- @NonNull private final SensorOverlays mSensorOverlays;
- @NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
- @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
- @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
-
- private boolean mIsPointerDown;
-
- FingerprintAuthenticationClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
- @NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener, long operationId,
- boolean restricted, @NonNull FingerprintAuthenticateOptions options,
- int cookie, boolean requireConfirmation, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext, boolean isStrongBiometric,
- @NonNull TaskStackListener taskStackListener,
- @NonNull LockoutFrameworkImpl lockoutTracker,
- @Nullable IUdfpsOverlayController udfpsOverlayController,
- // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
- @Nullable ISidefpsController sidefpsController,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintSensorPropertiesInternal sensorProps,
- @Authenticators.Types int sensorStrength) {
- super(context, lazyDaemon, token, listener, operationId, restricted,
- options, cookie, requireConfirmation, logger, biometricContext,
- isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication,
- false /* shouldVibrate */, sensorStrength);
- setRequestId(requestId);
- mLockoutFrameworkImpl = lockoutTracker;
- if (sidefpsControllerRefactor()) {
- mSensorOverlays = new SensorOverlays(udfpsOverlayController);
- } else {
- mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
- }
- mAuthenticationStateListeners = authenticationStateListeners;
- mSensorProps = sensorProps;
- mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
-
- if (mSensorProps.isAnyUdfpsType()) {
- // UDFPS requires user to touch before becoming "active"
- mState = STATE_STARTED_PAUSED;
- } else {
- mState = STATE_STARTED;
- }
- }
-
- @NonNull
- @Override
- protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
- return new ClientMonitorCompositeCallback(mALSProbeCallback, callback);
- }
-
- @Override
- public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
- boolean authenticated, ArrayList<Byte> token) {
- super.onAuthenticated(identifier, authenticated, token);
-
- // Authentication lifecycle ends either when
- // 1) Authenticated == true
- // 2) Error occurred (lockout or some other error)
- // Note that authentication doesn't end when Authenticated == false
-
- if (authenticated) {
- mState = STATE_STOPPED;
- resetFailedAttempts(getTargetUserId());
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
- if (reportBiometricAuthAttempts()) {
- mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
- getTargetUserId());
- }
- } else {
- mState = STATE_STARTED_PAUSED_ATTEMPTED;
- final @LockoutTracker.LockoutMode int lockoutMode =
- mLockoutFrameworkImpl.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());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
- onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
- cancel();
- }
- if (reportBiometricAuthAttempts()) {
- mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
- getTargetUserId());
- }
- }
- }
-
- @Override
- public void onError(int errorCode, int vendorCode) {
- super.onError(errorCode, vendorCode);
-
- if (errorCode == BiometricFingerprintConstants.FINGERPRINT_ERROR_BAD_CALIBRATION) {
- BiometricNotificationUtils.showBadCalibrationNotification(getContext());
- }
-
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
- }
-
- private void resetFailedAttempts(int userId) {
- mLockoutFrameworkImpl.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId);
- }
-
- @Override
- protected void handleLifecycleAfterAuth(boolean authenticated) {
- if (authenticated) {
- mCallback.onClientFinished(this, true /* success */);
- }
- }
-
- @Override
- public void onAcquired(int acquiredInfo, int vendorCode) {
- mAuthenticationStateListeners.onAuthenticationAcquired(
- BiometricSourceType.FINGERPRINT, getRequestReason(), acquiredInfo);
- super.onAcquired(acquiredInfo, vendorCode);
-
- @LockoutTracker.LockoutMode final int lockoutMode =
- getLockoutTracker().getLockoutModeForUser(getTargetUserId());
- if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
- PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
- pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
- }
- }
-
- @Override
- public boolean wasUserDetected() {
- // TODO: Update if it needs to be used for fingerprint, i.e. success/reject, error_timeout
- return false;
- }
-
- @Override
- public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
- mLockoutFrameworkImpl.addFailedAttemptForUser(userId);
- @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;
- }
-
- @Override
- protected void startHalOperation() {
- mSensorOverlays.show(getSensorId(), getRequestReason(), this);
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStarted(getRequestReason());
- }
-
- try {
- // GroupId was never used. In fact, groupId is always the same as userId.
- getFreshDaemon().authenticate(mOperationId, getTargetUserId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting auth", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- protected void stopHalOperation() {
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
-
- try {
- getFreshDaemon().cancel();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting cancel", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- public void onPointerDown(PointerContext pc) {
- mIsPointerDown = true;
- mState = STATE_STARTED;
- mALSProbeCallback.getProbe().enable();
- UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
-
- try {
- getListener().onUdfpsPointerDown(getSensorId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }
-
- @Override
- public void onPointerUp(PointerContext pc) {
- mIsPointerDown = false;
- mState = STATE_STARTED_PAUSED_ATTEMPTED;
- mALSProbeCallback.getProbe().disable();
- UdfpsHelper.onFingerUp(getFreshDaemon());
-
- try {
- getListener().onUdfpsPointerUp(getSensorId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }
-
- @Override
- public boolean isPointerDown() {
- return mIsPointerDown;
- }
-
- @Override
- public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
- // Unsupported in HIDL.
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
deleted file mode 100644
index 50e48fe..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ /dev/null
@@ -1,172 +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.biometrics.sensors.fingerprint.hidl;
-
-import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricRequestConstants;
-import android.hardware.biometrics.fingerprint.PointerContext;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.BiometricsProto;
-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.AuthenticationConsumer;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.SensorOverlays;
-import com.android.server.biometrics.sensors.fingerprint.Udfps;
-import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * 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<IBiometricsFingerprint>
- implements AuthenticationConsumer, Udfps {
-
- private static final String TAG = "FingerprintDetectClient";
-
- private final boolean mIsStrongBiometric;
- @NonNull private final SensorOverlays mSensorOverlays;
- private boolean mIsPointerDown;
-
- public FingerprintDetectClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
- @NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener,
- @NonNull FingerprintAuthenticateOptions options,
- @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
- @Nullable IUdfpsOverlayController udfpsOverlayController,
- boolean isStrongBiometric) {
- super(context, lazyDaemon, token, listener, options.getUserId(),
- options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
- true /* shouldVibrate */, biometricLogger, biometricContext);
- setRequestId(requestId);
- if (sidefpsControllerRefactor()) {
- mSensorOverlays = new SensorOverlays(udfpsOverlayController);
- } else {
- mSensorOverlays = new SensorOverlays(
- udfpsOverlayController, null /* sideFpsController */);
- }
- mIsStrongBiometric = isStrongBiometric;
- }
-
- @Override
- protected void stopHalOperation() {
- mSensorOverlays.hide(getSensorId());
-
- try {
- getFreshDaemon().cancel();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting cancel", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
- startHalOperation();
- }
-
- @Override
- protected void startHalOperation() {
- mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD,
- this);
-
- try {
- getFreshDaemon().authenticate(0 /* operationId */, getTargetUserId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting auth", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
- mSensorOverlays.hide(getSensorId());
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- public void onPointerDown(PointerContext pc) {
- mIsPointerDown = true;
- UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
- }
-
- @Override
- public void onPointerUp(PointerContext pc) {
- mIsPointerDown = false;
- UdfpsHelper.onFingerUp(getFreshDaemon());
- }
-
- @Override
- public boolean isPointerDown() {
- return mIsPointerDown;
- }
-
- @Override
- public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
- // Unsupported in HIDL.
- }
-
- @Override
- public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
- boolean authenticated, ArrayList<Byte> hardwareAuthToken) {
- getLogger().logOnAuthenticated(getContext(), getOperationContext(),
- authenticated, false /* requireConfirmation */,
- getTargetUserId(), false /* isBiometricPrompt */);
-
- // Do not distinguish between success/failures.
- vibrateSuccess();
-
- final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId());
- pm.incrementAuthForUser(getTargetUserId(), authenticated);
-
- try {
- getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when sending onDetected", e);
- }
- }
-
- @Override
- public int getProtoEnum() {
- return BiometricsProto.CM_DETECT_INTERACTION;
- }
-
- @Override
- public boolean interruptsPrecedingClients() {
- return true;
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
deleted file mode 100644
index 8f937fc..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ /dev/null
@@ -1,229 +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.biometrics.sensors.fingerprint.hidl;
-
-import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricStateListener;
-import android.hardware.biometrics.fingerprint.PointerContext;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.FingerprintEnrollOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricNotificationUtils;
-import com.android.server.biometrics.sensors.BiometricUtils;
-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.EnrollClient;
-import com.android.server.biometrics.sensors.SensorOverlays;
-import com.android.server.biometrics.sensors.fingerprint.Udfps;
-import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
-
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific enroll client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint>
- implements Udfps {
-
- private static final String TAG = "FingerprintEnrollClient";
-
- @NonNull private final SensorOverlays mSensorOverlays;
- private final @FingerprintManager.EnrollReason int mEnrollReason;
- @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
- private boolean mIsPointerDown;
-
- FingerprintEnrollClient(
- @NonNull Context context, @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
- @NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull byte[] hardwareAuthToken, @NonNull String owner,
- @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
- @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
- @Nullable IUdfpsOverlayController udfpsOverlayController,
- // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
- @Nullable ISidefpsController sidefpsController,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- @FingerprintManager.EnrollReason int enrollReason,
- @NonNull FingerprintEnrollOptions options) {
- super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
- timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger,
- biometricContext,
- BiometricFingerprintConstants.reasonToMetric(options.getEnrollReason()));
- setRequestId(requestId);
- if (sidefpsControllerRefactor()) {
- mSensorOverlays = new SensorOverlays(udfpsOverlayController);
- } else {
- mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
- }
- mAuthenticationStateListeners = authenticationStateListeners;
-
- mEnrollReason = enrollReason;
- if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
- getLogger().disableMetrics();
- }
- Slog.w(TAG, "EnrollOptions "
- + FingerprintEnrollOptions.enrollReasonToString(options.getEnrollReason()));
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
-
- BiometricNotificationUtils.cancelFingerprintEnrollNotification(getContext());
- }
-
- @NonNull
- @Override
- protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
- return new ClientMonitorCompositeCallback(
- getLogger().getAmbientLightProbe(true /* startWithClient */), callback);
- }
-
- @Override
- protected boolean hasReachedEnrollmentLimit() {
- final int limit = getContext().getResources().getInteger(
- com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
- final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId())
- .size();
- if (enrolled >= limit) {
- Slog.w(TAG, "Too many fingerprints registered, user: " + getTargetUserId());
- return true;
- }
- return false;
- }
-
- @Override
- protected void startHalOperation() {
- mSensorOverlays.show(getSensorId(), getRequestReasonFromEnrollReason(mEnrollReason),
- this);
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStarted(
- getRequestReasonFromEnrollReason(mEnrollReason));
- }
-
- BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
- try {
- // GroupId was never used. In fact, groupId is always the same as userId.
- getFreshDaemon().enroll(mHardwareAuthToken, getTargetUserId(), mTimeoutSec);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting enroll", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- protected void stopHalOperation() {
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
-
- try {
- getFreshDaemon().cancel();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting cancel", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) {
- super.onEnrollResult(identifier, remaining);
-
- mSensorOverlays.ifUdfps(
- controller -> controller.onEnrollmentProgress(getSensorId(), remaining));
-
- if (remaining == 0) {
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
- }
- }
-
- @Override
- public void onAcquired(int acquiredInfo, int vendorCode) {
- super.onAcquired(acquiredInfo, vendorCode);
-
- mSensorOverlays.ifUdfps(controller -> {
- if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) {
- controller.onEnrollmentHelp(getSensorId());
- }
- });
-
- mCallback.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
- }
-
- @Override
- public void onError(int errorCode, int vendorCode) {
- super.onError(errorCode, vendorCode);
-
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
- }
-
- @Override
- public void onPointerDown(PointerContext pc) {
- mIsPointerDown = true;
- UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
- }
-
- @Override
- public void onPointerUp(PointerContext pc) {
- mIsPointerDown = false;
- UdfpsHelper.onFingerUp(getFreshDaemon());
- }
-
- @Override
- public boolean isPointerDown() {
- return mIsPointerDown;
- }
-
- @Override
- public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
- // Unsupported in HIDL.
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
deleted file mode 100644
index 3bb7135..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
+++ /dev/null
@@ -1,68 +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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.GenerateChallengeClient;
-
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific generateChallenge/preEnroll client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-public class FingerprintGenerateChallengeClient
- extends GenerateChallengeClient<IBiometricsFingerprint> {
-
- private static final String TAG = "FingerprintGenerateChallengeClient";
-
- FingerprintGenerateChallengeClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
- @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
- int sensorId, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext) {
- super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
- biometricContext);
- }
-
- @Override
- protected void startHalOperation() {
- try {
- final long challenge = getFreshDaemon().preEnroll();
- try {
- getListener().onChallengeGenerated(getSensorId(), getTargetUserId(), challenge);
- mCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "preEnroll failed", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
deleted file mode 100644
index 8b61f59..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
+++ /dev/null
@@ -1,76 +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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.Fingerprint;
-import android.os.IBinder;
-
-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.InternalCleanupClient;
-import com.android.server.biometrics.sensors.InternalEnumerateClient;
-import com.android.server.biometrics.sensors.RemovalClient;
-
-import java.util.List;
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific internal cleanup client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-class FingerprintInternalCleanupClient
- extends InternalCleanupClient<Fingerprint, IBiometricsFingerprint> {
-
- FingerprintInternalCleanupClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
- @NonNull String owner, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull BiometricUtils<Fingerprint> utils,
- @NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
- utils, authenticatorIds);
- }
-
- @Override
- protected InternalEnumerateClient<IBiometricsFingerprint> getEnumerateClient(
- Context context, Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token,
- int userId, String owner, List<Fingerprint> enrolledList,
- BiometricUtils<Fingerprint> utils, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
- return new FingerprintInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
- enrolledList, utils, sensorId, logger, biometricContext);
- }
-
- @Override
- protected RemovalClient<Fingerprint, IBiometricsFingerprint> getRemovalClient(Context context,
- Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token,
- int biometricId, int userId, String owner, BiometricUtils<Fingerprint> utils,
- int sensorId, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext, Map<Integer, Long> authenticatorIds) {
- // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
- // is all done internally.
- return new FingerprintRemovalClient(context, lazyDaemon, token,
- null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
- sensorId, logger, biometricContext, authenticatorIds);
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
deleted file mode 100644
index 0840f1b..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
+++ /dev/null
@@ -1,61 +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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.Fingerprint;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-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.InternalEnumerateClient;
-
-import java.util.List;
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific internal enumerate client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-class FingerprintInternalEnumerateClient extends InternalEnumerateClient<IBiometricsFingerprint> {
- private static final String TAG = "FingerprintInternalEnumerateClient";
-
- FingerprintInternalEnumerateClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
- int userId, @NonNull String owner, @NonNull List<Fingerprint> enrolledList,
- @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
- super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
- logger, biometricContext);
- }
-
- @Override
- protected void startHalOperation() {
- try {
- getFreshDaemon().enumerate();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting enumerate", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
deleted file mode 100644
index 9ec56c2..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
+++ /dev/null
@@ -1,67 +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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.Fingerprint;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-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.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.RemovalClient;
-
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific removal client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-class FingerprintRemovalClient extends RemovalClient<Fingerprint, IBiometricsFingerprint> {
- private static final String TAG = "FingerprintRemovalClient";
-
- private final int mBiometricId;
-
- FingerprintRemovalClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
- @NonNull ClientMonitorCallbackConverter listener, int biometricId, int userId,
- @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
- logger, biometricContext, authenticatorIds);
- mBiometricId = biometricId;
- }
-
- @Override
- protected void startHalOperation() {
- try {
- // GroupId was never used. In fact, groupId is always the same as userId.
- getFreshDaemon().remove(getTargetUserId(), mBiometricId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting remove", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
deleted file mode 100644
index 843fcc8..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
+++ /dev/null
@@ -1,61 +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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-
-import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-
-/**
- * Clears lockout, which is handled in the framework (and not the HAL) for the
- * [email protected] interface.
- */
-public class FingerprintResetLockoutClient extends BaseClientMonitor {
-
- @NonNull final LockoutFrameworkImpl mLockoutTracker;
-
- public FingerprintResetLockoutClient(@NonNull Context context, int userId,
- @NonNull String owner, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull LockoutFrameworkImpl lockoutTracker) {
- super(context, null /* token */, null /* listener */, userId, owner, 0 /* cookie */,
- sensorId, logger, biometricContext);
- mLockoutTracker = lockoutTracker;
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
- mLockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */,
- getTargetUserId());
- callback.onClientFinished(this, true /* success */);
- }
-
- public boolean interruptsPrecedingClients() {
- return true;
- }
-
- @Override
- public int getProtoEnum() {
- return BiometricsProto.CM_RESET_LOCKOUT;
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
deleted file mode 100644
index 6273417..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
+++ /dev/null
@@ -1,59 +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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.RevokeChallengeClient;
-
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific revokeChallenge client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-public class FingerprintRevokeChallengeClient
- extends RevokeChallengeClient<IBiometricsFingerprint> {
-
- private static final String TAG = "FingerprintRevokeChallengeClient";
-
- FingerprintRevokeChallengeClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
- int userId, @NonNull String owner, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
- super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
- }
-
- @Override
- protected void startHalOperation() {
- try {
- getFreshDaemon().postEnroll();
- mCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "revokeChallenge/postEnroll failed", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java
deleted file mode 100644
index fc85402..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.os.Build;
-import android.os.Environment;
-import android.os.RemoteException;
-import android.os.SELinux;
-import android.util.Slog;
-
-import com.android.server.biometrics.BiometricsProto;
-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.HalClientMonitor;
-
-import java.io.File;
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * TODO(b/304604965): Delete this class once Flags.DE_HIDL is ready for release.
- */
-public class FingerprintUpdateActiveUserClientLegacy extends
- HalClientMonitor<IBiometricsFingerprint> {
- private static final String TAG = "FingerprintUpdateActiveUserClient";
- private static final String FP_DATA_DIR = "fpdata";
-
- private final Supplier<Integer> mCurrentUserId;
- private final boolean mForceUpdateAuthenticatorId;
- private final boolean mHasEnrolledBiometrics;
- private final Map<Integer, Long> mAuthenticatorIds;
- private File mDirectory;
-
- FingerprintUpdateActiveUserClientLegacy(@NonNull Context context,
- @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
- @NonNull String owner, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull Supplier<Integer> currentUserId,
- boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
- boolean forceUpdateAuthenticatorId) {
- super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, logger, biometricContext);
- mCurrentUserId = currentUserId;
- mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId;
- mHasEnrolledBiometrics = hasEnrolledBiometrics;
- mAuthenticatorIds = authenticatorIds;
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
-
- if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) {
- Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning");
- callback.onClientFinished(this, true /* success */);
- return;
- }
-
- int firstSdkInt = Build.VERSION.DEVICE_INITIAL_SDK_INT;
- if (firstSdkInt < Build.VERSION_CODES.BASE) {
- Slog.e(TAG, "First SDK version " + firstSdkInt + " is invalid; must be "
- + "at least VERSION_CODES.BASE");
- }
- File baseDir;
- if (firstSdkInt <= Build.VERSION_CODES.O_MR1) {
- baseDir = Environment.getUserSystemDirectory(getTargetUserId());
- } else {
- baseDir = Environment.getDataVendorDeDirectory(getTargetUserId());
- }
-
- mDirectory = new File(baseDir, FP_DATA_DIR);
- if (!mDirectory.exists()) {
- if (!mDirectory.mkdir()) {
- Slog.e(TAG, "Cannot make directory: " + mDirectory.getAbsolutePath());
- callback.onClientFinished(this, false /* success */);
- return;
- }
- // Calling mkdir() from this process will create a directory with our
- // permissions (inherited from the containing dir). This command fixes
- // the label.
- if (!SELinux.restorecon(mDirectory)) {
- Slog.e(TAG, "Restorecons failed. Directory will have wrong label.");
- callback.onClientFinished(this, false /* success */);
- return;
- }
- }
-
- startHalOperation();
- }
-
- @Override
- public void unableToStart() {
- // Nothing to do here
- }
-
- @Override
- protected void startHalOperation() {
- try {
- final int targetId = getTargetUserId();
- Slog.d(TAG, "Setting active user: " + targetId);
- getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath());
- mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics
- ? getFreshDaemon().getAuthenticatorId() : 0L);
- mCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to setActiveGroup: " + e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- public int getProtoEnum() {
- return BiometricsProto.CM_UPDATE_ACTIVE_USER;
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
index 47fdcdb9..3214b6d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
@@ -186,7 +186,7 @@
mLockoutTracker,
mLockoutResetDispatcher,
mAuthSessionCoordinator,
- () -> {}, mAidlResponseHandlerCallback);
+ mAidlResponseHandlerCallback);
}
@VisibleForTesting IBiometricsFingerprint getIBiometricsFingerprint() {
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 05e681e..645a366 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -61,6 +61,9 @@
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -69,6 +72,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Range;
import android.util.Slog;
import android.view.Display;
import android.view.IDisplayWindowListener;
@@ -85,6 +89,8 @@
import com.android.server.SystemService;
import com.android.server.wm.WindowManagerInternal;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -243,6 +249,7 @@
public int mVideoStabilizationMode;
public boolean mUsedUltraWide;
public boolean mUsedZoomOverride;
+ public Range<Integer> mMostRequestedFpsRange;
public final long mLogId;
public final int mSessionIndex;
@@ -265,13 +272,15 @@
mDeviceError = deviceError;
mLogId = logId;
mSessionIndex = sessionIdx;
+ mMostRequestedFpsRange = new Range<Integer>(0, 0);
}
public void markCompleted(int internalReconfigure, long requestCount,
long resultErrorCount, boolean deviceError,
List<CameraStreamStats> streamStats, String userTag,
int videoStabilizationMode, boolean usedUltraWide,
- boolean usedZoomOverride, CameraExtensionSessionStats extStats) {
+ boolean usedZoomOverride, Range<Integer> mostRequestedFpsRange,
+ CameraExtensionSessionStats extStats) {
if (mCompleted) {
return;
}
@@ -287,6 +296,7 @@
mUsedUltraWide = usedUltraWide;
mUsedZoomOverride = usedZoomOverride;
mExtSessionStats = extStats;
+ mMostRequestedFpsRange = mostRequestedFpsRange;
if (CameraServiceProxy.DEBUG) {
Slog.v(TAG, "A camera facing " + cameraFacingToString(mCameraFacing) +
" was in use by " + mClientName + " for " +
@@ -637,6 +647,60 @@
Binder.restoreCallingIdentity(ident);
}
}
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver)
+ throws RemoteException {
+ new CSPShellCmd(CameraServiceProxy.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ private static class CSPShellCmd extends ShellCommand {
+ private static final String TAG = "CSPShellCmd";
+ private static final String USAGE = """
+ usage: cmd media.camera.proxy SUBCMD [args]
+
+ SUBCMDs:
+ dump_events: Write out all collected camera usage events to statsd.
+ Does not print to terminal.
+ help: You're reading it.
+ """;
+
+ private final CameraServiceProxy mCameraServiceProxy;
+
+ CSPShellCmd(CameraServiceProxy proxy) {
+ mCameraServiceProxy = proxy;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ switch (cmd.replace('-', '_')) {
+ case "dump_events":
+ int eventCount = mCameraServiceProxy.getUsageEventCount();
+ mCameraServiceProxy.dumpUsageEvents();
+ pw.println("Camera usage events dumped: " + eventCount);
+ break;
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (Exception e) {
+ Slog.e(mCameraServiceProxy.TAG, "Error running shell command", e);
+ return 1;
+ }
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ getOutPrintWriter().println(USAGE);
+ }
+ }
};
private final FoldStateListener mFoldStateListener;
@@ -882,6 +946,9 @@
? ", zoomOverrideUsage " + e.mUsedZoomOverride
: "";
+ String mostRequestedFpsRangeDebug = Flags.analytics24q3()
+ ? ", mostRequestedFpsRange " + e.mMostRequestedFpsRange
+ : "";
Slog.v(TAG, "CAMERA_ACTION_EVENT: action " + e.mAction
+ " clientName " + e.mClientName
+ ", duration " + e.getDuration()
@@ -900,6 +967,7 @@
+ ", videoStabilizationMode " + e.mVideoStabilizationMode
+ ultrawideDebug
+ zoomOverrideDebug
+ + mostRequestedFpsRangeDebug
+ ", logId " + e.mLogId
+ ", sessionIndex " + e.mSessionIndex
+ ", mExtSessionStats {type " + extensionType
@@ -966,7 +1034,17 @@
e.mUserTag, e.mVideoStabilizationMode,
e.mLogId, e.mSessionIndex,
extensionType, extensionIsAdvanced, e.mUsedUltraWide,
- e.mUsedZoomOverride);
+ e.mUsedZoomOverride,
+ e.mMostRequestedFpsRange.getLower(), e.mMostRequestedFpsRange.getUpper());
+ }
+ }
+
+ /**
+ * Get camera usage event count
+ */
+ int getUsageEventCount() {
+ synchronized (mLock) {
+ return mCameraUsageHistory.size();
}
}
@@ -1173,6 +1251,10 @@
long logId = cameraState.getLogId();
int sessionIdx = cameraState.getSessionIndex();
CameraExtensionSessionStats extSessionStats = cameraState.getExtensionSessionStats();
+ Range<Integer> mostRequestedFpsRange = Flags.analytics24q3()
+ ? cameraState.getMostRequestedFpsRange()
+ : new Range<Integer>(0, 0);
+
synchronized(mLock) {
// Update active camera list and notify NFC if necessary
boolean wasEmpty = mActiveCameraUsage.isEmpty();
@@ -1228,7 +1310,8 @@
oldEvent.markCompleted(/*internalReconfigure*/0, /*requestCount*/0,
/*resultErrorCount*/0, /*deviceError*/false, streamStats,
/*userTag*/"", /*videoStabilizationMode*/-1, /*usedUltraWide*/false,
- /*usedZoomOverride*/false, new CameraExtensionSessionStats());
+ /*usedZoomOverride*/false, new Range<Integer>(0, 0),
+ new CameraExtensionSessionStats());
mCameraUsageHistory.add(oldEvent);
}
break;
@@ -1240,7 +1323,7 @@
doneEvent.markCompleted(internalReconfigureCount, requestCount,
resultErrorCount, deviceError, streamStats, userTag,
videoStabilizationMode, usedUltraWide, usedZoomOverride,
- extSessionStats);
+ mostRequestedFpsRange, extSessionStats);
mCameraUsageHistory.add(doneEvent);
// Do not double count device error
deviceError = false;
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 9f4b3d2..c393e92 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -1651,7 +1651,7 @@
return AppBackgroundRestrictionsInfo.LEVEL_RESTRICTED_BUCKET;
case ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
return AppBackgroundRestrictionsInfo.LEVEL_BACKGROUND_RESTRICTED;
- case ActivityManager.RESTRICTION_LEVEL_HIBERNATION:
+ case ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED:
return AppBackgroundRestrictionsInfo.LEVEL_HIBERNATION;
default:
return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 1c169a0..5c93181 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -1766,7 +1766,8 @@
loadDensityMapping(config);
loadBrightnessDefaultFromDdcXml(config);
loadBrightnessConstraintsFromConfigXml();
- if (mFlags.isEvenDimmerEnabled()) {
+ if (mFlags.isEvenDimmerEnabled() && mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled)) {
mEvenDimmerBrightnessData = EvenDimmerBrightnessData.loadConfig(config);
}
loadBrightnessMap(config);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 8f1277b..68e2bd6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -548,6 +548,17 @@
}
};
+ private final DisplayModeDirector.DisplayDeviceConfigProvider mDisplayDeviceConfigProvider =
+ displayId -> {
+ synchronized (mSyncRoot) {
+ final DisplayDevice device = getDeviceForDisplayLocked(displayId);
+ if (device == null) {
+ return null;
+ }
+ return device.getDisplayDeviceConfig();
+ }
+ };
+
private final BrightnessSynchronizer mBrightnessSynchronizer;
private final DeviceConfigParameterProvider mConfigParameterProvider;
@@ -599,7 +610,8 @@
mLogicalDisplayMapper = new LogicalDisplayMapper(mContext,
foldSettingProvider, new FoldGracePeriodProvider(),
mDisplayDeviceRepo, new LogicalDisplayListener(), mSyncRoot, mHandler, mFlags);
- mDisplayModeDirector = new DisplayModeDirector(context, mHandler, mFlags);
+ mDisplayModeDirector = new DisplayModeDirector(
+ context, mHandler, mFlags, mDisplayDeviceConfigProvider);
mBrightnessSynchronizer = new BrightnessSynchronizer(mContext,
mFlags.isBrightnessIntRangeUserPerceptionEnabled());
Resources resources = mContext.getResources();
@@ -4940,18 +4952,6 @@
}
@Override
- public boolean isVrrSupportEnabled(int displayId) {
- DisplayDevice device;
- synchronized (mSyncRoot) {
- device = getDeviceForDisplayLocked(displayId);
- }
- if (device == null) {
- return false;
- }
- return device.getDisplayDeviceConfig().isVrrSupportEnabled();
- }
-
- @Override
public void setWindowManagerMirroring(int displayId, boolean isMirroring) {
synchronized (mSyncRoot) {
final DisplayDevice device = getDeviceForDisplayLocked(displayId);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index cfdb75f..896670e4 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1406,42 +1406,47 @@
}
setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
}
- // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
- if (!mFlags.isRefactorDisplayPowerControllerEnabled() && (Float.isNaN(brightnessState)
- || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD)) {
- if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
- brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
- mTempBrightnessEvent);
- if (BrightnessUtils.isValidBrightnessValue(brightnessState)
- || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
- rawBrightnessState = mAutomaticBrightnessController
- .getRawAutomaticScreenBrightness();
- brightnessState = clampScreenBrightness(brightnessState);
- // slowly adapt to auto-brightness
- // TODO(b/253226419): slowChange should be decided by strategy.updateBrightness
- slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
- && !mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged();
- brightnessAdjustmentFlags =
- mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags();
- updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
- mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+
+ if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
+ // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
+ if (Float.isNaN(brightnessState)
+ || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD) {
+ if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
+ brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
+ mTempBrightnessEvent);
+ if (BrightnessUtils.isValidBrightnessValue(brightnessState)
+ || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
+ rawBrightnessState = mAutomaticBrightnessController
+ .getRawAutomaticScreenBrightness();
+ // slowly adapt to auto-brightness
+ // TODO(b/253226419): slowChange should be decided by
+ // strategy.updateBrightness
+ slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
+ && !mAutomaticBrightnessStrategy
+ .getAutoBrightnessAdjustmentChanged();
+ brightnessAdjustmentFlags =
+ mAutomaticBrightnessStrategy
+ .getAutoBrightnessAdjustmentReasonsFlags();
+ updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+ mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+ }
+ setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ } else {
+ mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
+ // Restore the lower-priority brightness strategy
+ brightnessState = displayBrightnessState.getBrightness();
}
- setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
- } else {
- mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
- // Restore the lower-priority brightness strategy
- brightnessState = displayBrightnessState.getBrightness();
}
+ } else {
+ mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
}
- } else {
- // Any non-auto-brightness values such as override or temporary should still be subject
- // to clamping so that they don't go beyond the current max as specified by Brightness
- // Range Controller.
+ }
+
+ if (!Float.isNaN(brightnessState)) {
brightnessState = clampScreenBrightness(brightnessState);
- mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
}
// If there's an offload session, we need to set the initial doze brightness before
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 a862b6e..fa42316 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -120,8 +120,6 @@
private static final int MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED = 7;
private static final int MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED = 8;
- private static final float FLOAT_TOLERANCE = RefreshRateRange.FLOAT_TOLERANCE;
-
private final Object mLock = new Object();
private final Context mContext;
@@ -149,9 +147,8 @@
private SparseArray<Display.Mode[]> mSupportedModesByDisplay;
// A map from the display ID to the default mode of that display.
private SparseArray<Display.Mode> mDefaultModeByDisplay;
-
- // a map from display id to vrr support
- private SparseBooleanArray mVrrSupportedByDisplay;
+ // a map from display id to display device config
+ private SparseArray<DisplayDeviceConfig> mDisplayDeviceConfigByDisplay = new SparseArray<>();
private BrightnessObserver mBrightnessObserver;
@@ -193,15 +190,19 @@
private final DisplayManagerFlags mDisplayManagerFlags;
+ private final DisplayDeviceConfigProvider mDisplayDeviceConfigProvider;
public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler,
- @NonNull DisplayManagerFlags displayManagerFlags) {
- this(context, handler, new RealInjector(context), displayManagerFlags);
+ @NonNull DisplayManagerFlags displayManagerFlags,
+ @NonNull DisplayDeviceConfigProvider displayDeviceConfigProvider) {
+ this(context, handler, new RealInjector(context),
+ displayManagerFlags, displayDeviceConfigProvider);
}
public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler,
@NonNull Injector injector,
- @NonNull DisplayManagerFlags displayManagerFlags) {
+ @NonNull DisplayManagerFlags displayManagerFlags,
+ @NonNull DisplayDeviceConfigProvider displayDeviceConfigProvider) {
mIsDisplayResolutionRangeVotingEnabled = displayManagerFlags
.isDisplayResolutionRangeVotingEnabled();
mIsUserPreferredModeVoteEnabled = displayManagerFlags.isUserPreferredModeVoteEnabled();
@@ -212,6 +213,7 @@
mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled = displayManagerFlags
.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled();
mDisplayManagerFlags = displayManagerFlags;
+ mDisplayDeviceConfigProvider = displayDeviceConfigProvider;
mContext = context;
mHandler = new DisplayModeDirectorHandler(handler.getLooper());
mInjector = injector;
@@ -219,7 +221,6 @@
displayManagerFlags.isRefreshRateVotingTelemetryEnabled());
mSupportedModesByDisplay = new SparseArray<>();
mDefaultModeByDisplay = new SparseArray<>();
- mVrrSupportedByDisplay = new SparseBooleanArray();
mAppRequestObserver = new AppRequestObserver();
mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig());
mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
@@ -315,7 +316,7 @@
List<Display.Mode> availableModes = new ArrayList<>();
availableModes.add(defaultMode);
VoteSummary primarySummary = new VoteSummary(mIsDisplayResolutionRangeVotingEnabled,
- mVrrSupportedByDisplay.get(displayId),
+ isVrrSupportedLocked(displayId),
mLoggingEnabled, mSupportsFrameRateOverride);
int lowestConsideredPriority = Vote.MIN_PRIORITY;
int highestConsideredPriority = Vote.MAX_PRIORITY;
@@ -356,7 +357,7 @@
}
VoteSummary appRequestSummary = new VoteSummary(mIsDisplayResolutionRangeVotingEnabled,
- mVrrSupportedByDisplay.get(displayId),
+ isVrrSupportedLocked(displayId),
mLoggingEnabled, mSupportsFrameRateOverride);
appRequestSummary.applyVotes(votes,
@@ -444,9 +445,14 @@
return mAppRequestObserver;
}
+ private boolean isVrrSupportedLocked(int displayId) {
+ DisplayDeviceConfig config = mDisplayDeviceConfigByDisplay.get(displayId);
+ return config != null && config.isVrrSupportEnabled();
+ }
+
private boolean isVrrSupportedByAnyDisplayLocked() {
- for (int i = 0; i < mVrrSupportedByDisplay.size(); i++) {
- if (mVrrSupportedByDisplay.valueAt(i)) {
+ for (int i = 0; i < mDisplayDeviceConfigByDisplay.size(); i++) {
+ if (mDisplayDeviceConfigByDisplay.valueAt(i).isVrrSupportEnabled()) {
return true;
}
}
@@ -552,7 +558,7 @@
if (mSystemRequestObserver != null) {
boolean vrrSupported;
synchronized (mLock) {
- vrrSupported = mVrrSupportedByDisplay.get(displayId);
+ vrrSupported = isVrrSupportedLocked(displayId);
}
if (vrrSupported) {
mSystemRequestObserver.requestDisplayModes(token, displayId, modeIds);
@@ -644,8 +650,8 @@
}
@VisibleForTesting
- void injectVrrByDisplay(SparseBooleanArray vrrByDisplay) {
- mVrrSupportedByDisplay = vrrByDisplay;
+ void injectDisplayDeviceConfigByDisplay(SparseArray<DisplayDeviceConfig> ddcByDisplay) {
+ mDisplayDeviceConfigByDisplay = ddcByDisplay;
}
@VisibleForTesting
@@ -694,6 +700,16 @@
}
/**
+ * Provides access to DisplayDeviceConfig for specific display
+ */
+ public interface DisplayDeviceConfigProvider {
+ /**
+ * Returns DisplayDeviceConfig for specific display
+ */
+ @Nullable DisplayDeviceConfig getDisplayDeviceConfig(int displayId);
+ }
+
+ /**
* Listens for changes refresh rate coordination.
*/
public interface DesiredDisplayModeSpecsListener {
@@ -1087,20 +1103,6 @@
if (Float.isInfinite(minRefreshRate)) {
// Infinity means that we want the highest possible refresh rate
minRefreshRate = highestRefreshRate;
-
- if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
- && displayId == Display.DEFAULT_DISPLAY) {
- // The flag has been turned off, we need to restore the original value. We'll
- // use the peak refresh rate of the default display.
- Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
- highestRefreshRate, cr.getUserId());
- }
- } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
- && displayId == Display.DEFAULT_DISPLAY
- && Math.round(minRefreshRate) == Math.round(highestRefreshRate)) {
- // The flag has been turned on, we need to upgrade the setting
- Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
- Float.POSITIVE_INFINITY, cr.getUserId());
}
float peakRefreshRate = Settings.System.getFloatForUser(cr,
@@ -1108,20 +1110,6 @@
if (Float.isInfinite(peakRefreshRate)) {
// Infinity means that we want the highest possible refresh rate
peakRefreshRate = highestRefreshRate;
-
- if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
- && displayId == Display.DEFAULT_DISPLAY) {
- // The flag has been turned off, we need to restore the original value. We'll
- // use the peak refresh rate of the default display.
- Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
- highestRefreshRate, cr.getUserId());
- }
- } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
- && displayId == Display.DEFAULT_DISPLAY
- && Math.round(peakRefreshRate) == Math.round(highestRefreshRate)) {
- // The flag has been turned on, we need to upgrade the setting
- Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
- Float.POSITIVE_INFINITY, cr.getUserId());
}
updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate,
@@ -1317,7 +1305,6 @@
private final Handler mHandler;
private final VotesStorage mVotesStorage;
- private DisplayManagerInternal mDisplayManagerInternal;
private int mExternalDisplayPeakWidth;
private int mExternalDisplayPeakHeight;
private int mExternalDisplayPeakRefreshRate;
@@ -1354,7 +1341,6 @@
}
public void observe() {
- mDisplayManagerInternal = mInjector.getDisplayManagerInternal();
mInjector.registerDisplayListener(this, mHandler);
// Populate existing displays
@@ -1367,21 +1353,21 @@
modes.put(displayId, info.supportedModes);
defaultModes.put(displayId, info.getDefaultMode());
}
- boolean vrrSupportedByDefaultDisplay = mDisplayManagerInternal
- .isVrrSupportEnabled(Display.DEFAULT_DISPLAY);
+ DisplayDeviceConfig defaultDisplayConfig = mDisplayDeviceConfigProvider
+ .getDisplayDeviceConfig(Display.DEFAULT_DISPLAY);
synchronized (mLock) {
final int size = modes.size();
for (int i = 0; i < size; i++) {
mSupportedModesByDisplay.put(modes.keyAt(i), modes.valueAt(i));
mDefaultModeByDisplay.put(defaultModes.keyAt(i), defaultModes.valueAt(i));
}
- mVrrSupportedByDisplay.put(Display.DEFAULT_DISPLAY, vrrSupportedByDefaultDisplay);
+ mDisplayDeviceConfigByDisplay.put(Display.DEFAULT_DISPLAY, defaultDisplayConfig);
}
}
@Override
public void onDisplayAdded(int displayId) {
- updateVrrStatus(displayId);
+ updateDisplayDeviceConfig(displayId);
DisplayInfo displayInfo = getDisplayInfo(displayId);
updateDisplayModes(displayId, displayInfo);
updateLayoutLimitedFrameRate(displayId, displayInfo);
@@ -1395,7 +1381,7 @@
synchronized (mLock) {
mSupportedModesByDisplay.remove(displayId);
mDefaultModeByDisplay.remove(displayId);
- mVrrSupportedByDisplay.delete(displayId);
+ mDisplayDeviceConfigByDisplay.remove(displayId);
mSettingsObserver.removeRefreshRateSetting(displayId);
}
updateLayoutLimitedFrameRate(displayId, null);
@@ -1406,7 +1392,7 @@
@Override
public void onDisplayChanged(int displayId) {
- updateVrrStatus(displayId);
+ updateDisplayDeviceConfig(displayId);
DisplayInfo displayInfo = getDisplayInfo(displayId);
updateDisplayModes(displayId, displayInfo);
updateLayoutLimitedFrameRate(displayId, displayInfo);
@@ -1536,10 +1522,11 @@
mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE, null);
}
- private void updateVrrStatus(int displayId) {
- boolean isVrrSupported = mDisplayManagerInternal.isVrrSupportEnabled(displayId);
+ private void updateDisplayDeviceConfig(int displayId) {
+ DisplayDeviceConfig config = mDisplayDeviceConfigProvider
+ .getDisplayDeviceConfig(displayId);
synchronized (mLock) {
- mVrrSupportedByDisplay.put(displayId, isVrrSupported);
+ mDisplayDeviceConfigByDisplay.put(displayId, config);
}
}
@@ -2264,7 +2251,7 @@
}
if (mVsyncLowLightBlockingVoteEnabled
- && mVrrSupportedByDisplay.get(Display.DEFAULT_DISPLAY)) {
+ && isVrrSupportedLocked(Display.DEFAULT_DISPLAY)) {
refreshRateSwitchingVote = Vote.forSupportedRefreshRatesAndDisableSwitching(
List.of(
new SupportedRefreshRatesVote.RefreshRates(
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f32c11d..73df594 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -250,8 +250,19 @@
private final Object mAssociationsLock = new Object();
@GuardedBy("mAssociationsLock")
private final Map<String, Integer> mRuntimeAssociations = new ArrayMap<>();
+
+ // The associations of input devices to displays by port. Maps from {InputDevice#mName} (String)
+ // to {DisplayInfo#uniqueId} (String) so that events from the Input Device go to a
+ // specific display.
@GuardedBy("mAssociationsLock")
- private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>();
+ private final Map<String, String> mUniqueIdAssociationsByPort = new ArrayMap<>();
+
+ // The associations of input devices to displays by descriptor. Maps from
+ // {InputDevice#mDescriptor} to {DisplayInfo#uniqueId} (String) so that events from the
+ // input device go to a specific display.
+ @GuardedBy("mAssociationsLock")
+ private final Map<String, String> mUniqueIdAssociationsByDescriptor = new ArrayMap<>();
+
// The map from input port (String) to the keyboard layout identifiers (comma separated string
// containing language tag and layout type) associated with the corresponding keyboard device.
// Currently only accessed by InputReader.
@@ -1741,8 +1752,8 @@
/**
* Add a runtime association between the input port and the display port. This overrides any
* static associations.
- * @param inputPort The port of the input device.
- * @param displayPort The physical port of the associated display.
+ * @param inputPort the port of the input device
+ * @param displayPort the physical port of the associated display
*/
@Override // Binder call
public void addPortAssociation(@NonNull String inputPort, int displayPort) {
@@ -1763,7 +1774,7 @@
/**
* Remove the runtime association between the input port and the display port. Any existing
* static association for the cleared input port will be restored.
- * @param inputPort The port of the input device to be cleared.
+ * @param inputPort the port of the input device to be cleared
*/
@Override // Binder call
public void removePortAssociation(@NonNull String inputPort) {
@@ -1782,10 +1793,11 @@
}
@Override // Binder call
- public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
+ public void addUniqueIdAssociationByPort(@NonNull String inputPort,
+ @NonNull String displayUniqueId) {
if (!checkCallingPermission(
android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
- "addUniqueIdAssociation()")) {
+ "addUniqueIdAssociationByPort()")) {
throw new SecurityException(
"Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
}
@@ -1793,22 +1805,65 @@
Objects.requireNonNull(inputPort);
Objects.requireNonNull(displayUniqueId);
synchronized (mAssociationsLock) {
- mUniqueIdAssociations.put(inputPort, displayUniqueId);
+ mUniqueIdAssociationsByPort.put(inputPort, displayUniqueId);
}
mNative.changeUniqueIdAssociation();
}
@Override // Binder call
- public void removeUniqueIdAssociation(@NonNull String inputPort) {
+ public void removeUniqueIdAssociationByPort(@NonNull String inputPort) {
if (!checkCallingPermission(
android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
- "removeUniqueIdAssociation()")) {
+ "removeUniqueIdAssociationByPort()")) {
throw new SecurityException("Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
}
Objects.requireNonNull(inputPort);
synchronized (mAssociationsLock) {
- mUniqueIdAssociations.remove(inputPort);
+ mUniqueIdAssociationsByPort.remove(inputPort);
+ }
+ mNative.changeUniqueIdAssociation();
+ }
+
+ /**
+ * Adds a runtime association between the input device descriptor and the display unique id.
+ * @param inputDeviceDescriptor the descriptor of the input device
+ * @param displayUniqueId the unique ID of the display
+ */
+ @Override // Binder call
+ public void addUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor,
+ @NonNull String displayUniqueId) {
+ if (!checkCallingPermission(
+ android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
+ "addUniqueIdAssociationByDescriptor()")) {
+ throw new SecurityException(
+ "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
+ }
+
+ Objects.requireNonNull(inputDeviceDescriptor);
+ Objects.requireNonNull(displayUniqueId);
+ synchronized (mAssociationsLock) {
+ mUniqueIdAssociationsByDescriptor.put(inputDeviceDescriptor, displayUniqueId);
+ }
+ mNative.changeUniqueIdAssociation();
+ }
+
+ /**
+ * Removes the runtime association between the input device and the display.
+ * @param inputDeviceDescriptor the descriptor of the input device
+ */
+ @Override // Binder call
+ public void removeUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor) {
+ if (!checkCallingPermission(
+ android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
+ "removeUniqueIdAssociationByDescriptor()")) {
+ throw new SecurityException(
+ "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
+ }
+
+ Objects.requireNonNull(inputDeviceDescriptor);
+ synchronized (mAssociationsLock) {
+ mUniqueIdAssociationsByDescriptor.remove(inputDeviceDescriptor);
}
mNative.changeUniqueIdAssociation();
}
@@ -2183,13 +2238,20 @@
pw.println(" display: " + v);
});
}
- if (!mUniqueIdAssociations.isEmpty()) {
+ if (!mUniqueIdAssociationsByPort.isEmpty()) {
pw.println("Unique Id Associations:");
- mUniqueIdAssociations.forEach((k, v) -> {
+ mUniqueIdAssociationsByPort.forEach((k, v) -> {
pw.print(" port: " + k);
pw.println(" uniqueId: " + v);
});
}
+ if (!mUniqueIdAssociationsByDescriptor.isEmpty()) {
+ pw.println("Unique Id Associations:");
+ mUniqueIdAssociationsByDescriptor.forEach((k, v) -> {
+ pw.print(" descriptor: " + k);
+ pw.println(" uniqueId: " + v);
+ });
+ }
if (!mDeviceTypeAssociations.isEmpty()) {
pw.println("Type Associations:");
mDeviceTypeAssociations.forEach((k, v) -> {
@@ -2622,10 +2684,21 @@
// Native callback
@SuppressWarnings("unused")
- private String[] getInputUniqueIdAssociations() {
+ private String[] getInputUniqueIdAssociationsByPort() {
final Map<String, String> associations;
synchronized (mAssociationsLock) {
- associations = new HashMap<>(mUniqueIdAssociations);
+ associations = new HashMap<>(mUniqueIdAssociationsByPort);
+ }
+
+ return flatten(associations);
+ }
+
+ // Native callback
+ @SuppressWarnings("unused")
+ private String[] getInputUniqueIdAssociationsByDescriptor() {
+ final Map<String, String> associations;
+ synchronized (mAssociationsLock) {
+ associations = new HashMap<>(mUniqueIdAssociationsByDescriptor);
}
return flatten(associations);
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 9ba647f..97c32b9 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -664,6 +664,46 @@
}
}
+ /**
+ * <ol>
+ * <li> Layout selection Algorithm:
+ * <ul>
+ * <li> Choose product specific layout(KCM file with matching vendor ID and product
+ * ID) </li>
+ * <li> If none, then find layout based on PK layout info (based on country code
+ * provided by the HID descriptor of the keyboard) </li>
+ * <li> If none, then find layout based on IME layout info associated with the IME
+ * subtype </li>
+ * <li> If none, return null (Generic.kcm is the default) </li>
+ * </ul>
+ * </li>
+ * <li> Finding correct layout corresponding to provided layout info:
+ * <ul>
+ * <li> Filter all available layouts based on the IME subtype script code </li>
+ * <li> Derive locale from the provided layout info </li>
+ * <li> If layoutType i.e. qwerty, azerty, etc. is provided, filter layouts by
+ * layoutType and try to find matching layout to the derived locale. </li>
+ * <li> If none found or layoutType not provided, then ignore the layoutType and try
+ * to find matching layout to the derived locale. </li>
+ * </ul>
+ * </li>
+ * <li> Finding matching layout for the derived locale:
+ * <ul>
+ * <li> If language code doesn't match, ignore the layout (We can never match a
+ * layout if language code isn't matching) </li>
+ * <li> If country code matches, layout score +1 </li>
+ * <li> Else if country code of layout is empty, layout score +0.5 (empty country
+ * code is a semi match with derived locale with country code, this is to prioritize
+ * empty country code layouts over fully mismatching layouts) </li>
+ * <li> If variant matches, layout score +1 </li>
+ * <li> Else if variant of layout is empty, layout score +0.5 (empty variant is a
+ * semi match with derive locale with country code, this is to prioritize empty
+ * variant layouts over fully mismatching layouts) </li>
+ * <li> Choose the layout with the best score. </li>
+ * </ul>
+ * </li>
+ * </ol>
+ */
@NonNull
private static KeyboardLayoutSelectionResult getDefaultKeyboardLayoutBasedOnImeInfo(
KeyboardIdentifier keyboardIdentifier, @Nullable ImeInfo imeInfo,
@@ -753,8 +793,8 @@
private static String getMatchingLayoutForProvidedLanguageTag(List<KeyboardLayout> layoutList,
@NonNull String languageTag) {
Locale locale = Locale.forLanguageTag(languageTag);
- String layoutMatchingLanguage = null;
- String layoutMatchingLanguageAndCountry = null;
+ String bestMatchingLayout = null;
+ float bestMatchingLayoutScore = 0;
for (KeyboardLayout layout : layoutList) {
final LocaleList locales = layout.getLocales();
@@ -763,23 +803,28 @@
if (l == null) {
continue;
}
- if (l.getLanguage().equals(locale.getLanguage())) {
- if (layoutMatchingLanguage == null) {
- layoutMatchingLanguage = layout.getDescriptor();
- }
- if (l.getCountry().equals(locale.getCountry())) {
- if (layoutMatchingLanguageAndCountry == null) {
- layoutMatchingLanguageAndCountry = layout.getDescriptor();
- }
- if (l.getVariant().equals(locale.getVariant())) {
- return layout.getDescriptor();
- }
- }
+ if (!l.getLanguage().equals(locale.getLanguage())) {
+ // If language mismatches: NEVER choose that layout
+ continue;
+ }
+ float layoutScore = 1; // If language matches then score +1
+ if (l.getCountry().equals(locale.getCountry())) {
+ layoutScore += 1; // If country matches then score +1
+ } else if (TextUtils.isEmpty(l.getCountry())) {
+ layoutScore += 0.5; // Consider empty country as semi-match
+ }
+ if (l.getVariant().equals(locale.getVariant())) {
+ layoutScore += 1; // If variant matches then score +1
+ } else if (TextUtils.isEmpty(l.getVariant())) {
+ layoutScore += 0.5; // Consider empty variant as semi-match
+ }
+ if (layoutScore > bestMatchingLayoutScore) {
+ bestMatchingLayoutScore = layoutScore;
+ bestMatchingLayout = layout.getDescriptor();
}
}
}
- return layoutMatchingLanguageAndCountry != null
- ? layoutMatchingLanguageAndCountry : layoutMatchingLanguage;
+ return bestMatchingLayout;
}
private void reloadKeyboardLayouts() {
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index c7b60da..dd6433d 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -19,11 +19,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Handler;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
@@ -67,7 +69,7 @@
AdditionalSubtypeUtils.save(map, inputMethodMap, userId);
}
- static void initialize(@NonNull Handler handler) {
+ static void initialize(@NonNull Handler handler, @NonNull Context context) {
final UserManagerInternal userManagerInternal =
LocalServices.getService(UserManagerInternal.class);
handler.post(() -> {
@@ -79,8 +81,16 @@
handler.post(() -> {
synchronized (ImfLock.class) {
if (!sPerUserMap.contains(userId)) {
- sPerUserMap.put(userId,
- AdditionalSubtypeUtils.load(userId));
+ final AdditionalSubtypeMap additionalSubtypeMap =
+ AdditionalSubtypeUtils.load(userId);
+ sPerUserMap.put(userId, additionalSubtypeMap);
+ final InputMethodSettings settings =
+ InputMethodManagerService
+ .queryInputMethodServicesInternal(context,
+ userId,
+ additionalSubtypeMap,
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
}
}
});
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 03a85c4..808e296 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -283,9 +283,16 @@
final Context mContext;
final Resources mRes;
private final Handler mHandler;
- @NonNull
+
@MultiUserUnawareField
- private InputMethodSettings mSettings;
+ @UserIdInt
+ @GuardedBy("ImfLock.class")
+ private int mCurrentUserId;
+
+ /** Holds all user related data */
+ @GuardedBy("ImfLock.class")
+ private UserDataRepository mUserDataRepository;
+
@MultiUserUnawareField
final SettingsObserver mSettingsObserver;
final WindowManagerInternal mWindowManagerInternal;
@@ -490,7 +497,7 @@
@GuardedBy("ImfLock.class")
@Nullable
InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) {
- return mSettings.getMethodMap().get(imeId);
+ return InputMethodSettingsRepository.get(mCurrentUserId).getMethodMap().get(imeId);
}
/**
@@ -811,7 +818,8 @@
InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
} else {
boolean enabledChanged = false;
- String newEnabled = mSettings.getEnabledInputMethodsStr();
+ String newEnabled = InputMethodSettingsRepository.get(mCurrentUserId)
+ .getEnabledInputMethodsStr();
if (!mLastEnabled.equals(newEnabled)) {
mLastEnabled = newEnabled;
enabledChanged = true;
@@ -843,9 +851,11 @@
// sender userId can be a real user ID or USER_ALL.
final int senderUserId = pendingResult.getSendingUserId();
if (senderUserId != UserHandle.USER_ALL) {
- if (senderUserId != mSettings.getUserId()) {
- // A background user is trying to hide the dialog. Ignore.
- return;
+ synchronized (ImfLock.class) {
+ if (senderUserId != mCurrentUserId) {
+ // A background user is trying to hide the dialog. Ignore.
+ return;
+ }
}
}
mMenuController.hideInputMethodMenu();
@@ -869,9 +879,14 @@
if (!mSystemReady) {
return;
}
- mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
- AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
- DirectBootAwareness.AUTO);
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ final InputMethodSettings settings = queryInputMethodServicesInternal(
+ mContext,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
+ }
postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
// If the locale is changed, needs to reset the default ime
resetDefaultImeLocked(mContext);
@@ -932,7 +947,7 @@
@GuardedBy("ImfLock.class")
private boolean isChangingPackagesOfCurrentUserLocked() {
final int userId = getChangingUserId();
- final boolean retval = userId == mSettings.getUserId();
+ final boolean retval = userId == mCurrentUserId;
if (DEBUG) {
if (!retval) {
Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
@@ -947,8 +962,10 @@
if (!isChangingPackagesOfCurrentUserLocked()) {
return false;
}
- String curInputMethodId = mSettings.getSelectedInputMethod();
- final List<InputMethodInfo> methodList = mSettings.getMethodList();
+ final InputMethodSettings settings =
+ InputMethodSettingsRepository.get(mCurrentUserId);
+ String curInputMethodId = settings.getSelectedInputMethod();
+ final List<InputMethodInfo> methodList = settings.getMethodList();
final int numImes = methodList.size();
if (curInputMethodId != null) {
for (int i = 0; i < numImes; i++) {
@@ -1065,16 +1082,10 @@
private void onFinishPackageChangesInternal() {
synchronized (ImfLock.class) {
final int userId = getChangingUserId();
- final boolean isCurrentUser = (userId == mSettings.getUserId());
+ final boolean isCurrentUser = (userId == mCurrentUserId);
final AdditionalSubtypeMap additionalSubtypeMap =
AdditionalSubtypeMapRepository.get(userId);
- final InputMethodSettings settings;
- if (isCurrentUser) {
- settings = mSettings;
- } else {
- settings = queryInputMethodServicesInternal(mContext, userId,
- additionalSubtypeMap, DirectBootAwareness.AUTO);
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
InputMethodInfo curIm = null;
String curInputMethodId = settings.getSelectedInputMethod();
@@ -1092,7 +1103,7 @@
imesToClearAdditionalSubtypes.add(imiId);
}
int change = isPackageDisappearing(imi.getPackageName());
- if (change == PACKAGE_TEMPORARY_CHANGE || change == PACKAGE_PERMANENT_CHANGE) {
+ if (change == PACKAGE_PERMANENT_CHANGE) {
Slog.i(TAG, "Input method uninstalled, disabling: " + imi.getComponent());
if (isCurrentUser) {
setInputMethodEnabledLocked(imi.getId(), false);
@@ -1118,16 +1129,17 @@
AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
settings.getMethodMap());
}
+ if (isCurrentUser
+ && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
+ return;
+ }
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
if (!isCurrentUser) {
return;
}
-
- if (!(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
- return;
- }
- mSettings = queryInputMethodServicesInternal(mContext, userId,
- newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
boolean changed = false;
@@ -1276,26 +1288,29 @@
public void onUserStarting(TargetUser user) {
// Called on ActivityManager thread.
SecureSettingsWrapper.onUserStarting(user.getUserIdentifier());
+ synchronized (ImfLock.class) {
+ mService.mUserDataRepository.getOrCreate(user.getUserIdentifier());
+ }
}
+
}
void onUnlockUser(@UserIdInt int userId) {
synchronized (ImfLock.class) {
- final int currentUserId = mSettings.getUserId();
if (DEBUG) {
- Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
- }
- if (userId != currentUserId) {
- return;
+ Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + mCurrentUserId);
}
if (!mSystemReady) {
return;
}
- mSettings = queryInputMethodServicesInternal(mContext, userId,
- AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
- // We need to rebuild IMEs.
- postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
- updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
+ if (mCurrentUserId == userId) {
+ // We need to rebuild IMEs.
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
+ updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
+ }
}
}
@@ -1361,19 +1376,24 @@
mShowOngoingImeSwitcherForPhones = false;
- AdditionalSubtypeMapRepository.initialize(mHandler);
+ // InputMethodSettingsRepository should be initialized before buildInputMethodListLocked
+ InputMethodSettingsRepository.initialize(mHandler, mContext);
+ AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
- final int userId = mActivityManagerInternal.getCurrentUserId();
+ mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
+ mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal);
+ for (int id : mUserManagerInternal.getUserIds()) {
+ mUserDataRepository.getOrCreate(id);
+ }
- // mSettings should be created before buildInputMethodListLocked
- mSettings = InputMethodSettings.createEmptyMap(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
mSwitchingController =
InputMethodSubtypeSwitchingController.createInstanceLocked(context,
- mSettings.getMethodMap(), userId);
+ settings.getMethodMap(), settings.getUserId());
mHardwareKeyboardShortcutController =
- new HardwareKeyboardShortcutController(mSettings.getMethodMap(),
- mSettings.getUserId());
+ new HardwareKeyboardShortcutController(settings.getMethodMap(),
+ settings.getUserId());
mMenuController = new InputMethodMenuController(this);
mBindingController =
bindingControllerForTesting != null
@@ -1405,7 +1425,7 @@
@GuardedBy("ImfLock.class")
@UserIdInt
int getCurrentImeUserIdLocked() {
- return mSettings.getUserId();
+ return mCurrentUserId;
}
private final class InkWindowInitializer implements Runnable {
@@ -1441,12 +1461,13 @@
private void resetDefaultImeLocked(Context context) {
// Do not reset the default (current) IME when it is a 3rd-party IME
String selectedMethodId = getSelectedMethodIdLocked();
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
if (selectedMethodId != null
- && !mSettings.getMethodMap().get(selectedMethodId).isSystem()) {
+ && !settings.getMethodMap().get(selectedMethodId).isSystem()) {
return;
}
final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes(
- context, mSettings.getEnabledInputMethodList());
+ context, settings.getEnabledInputMethodList());
if (suitableImes.isEmpty()) {
Slog.i(TAG, "No default found");
return;
@@ -1502,7 +1523,7 @@
IInputMethodClientInvoker clientToBeReset) {
if (DEBUG) {
Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
- + " currentUserId=" + mSettings.getUserId());
+ + " currentUserId=" + mCurrentUserId);
}
maybeInitImeNavbarConfigLocked(newUserId);
@@ -1510,8 +1531,9 @@
// ContentObserver should be registered again when the user is changed
mSettingsObserver.registerContentObserverLocked(newUserId);
- mSettings = InputMethodSettings.createEmptyMap(newUserId);
- final String defaultImiId = mSettings.getSelectedInputMethod();
+ mCurrentUserId = newUserId;
+ final String defaultImiId = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, newUserId);
if (DEBUG) {
Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId
@@ -1529,10 +1551,9 @@
// and user switch would not happen at that time.
resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
- mSettings = queryInputMethodServicesInternal(mContext, newUserId,
- AdditionalSubtypeMapRepository.get(newUserId), DirectBootAwareness.AUTO);
+ final InputMethodSettings newSettings = InputMethodSettingsRepository.get(newUserId);
postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
- if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
+ if (TextUtils.isEmpty(newSettings.getSelectedInputMethod())) {
// This is the first time of the user switch and
// set the current ime to the proper one.
resetDefaultImeLocked(mContext);
@@ -1542,12 +1563,12 @@
if (initialUserSwitch) {
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, newUserId),
- mSettings.getEnabledInputMethodList());
+ newSettings.getEnabledInputMethodList());
}
if (DEBUG) {
Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId
- + " selectedIme=" + mSettings.getSelectedInputMethod());
+ + " selectedIme=" + newSettings.getSelectedInputMethod());
}
if (mIsInteractive && clientToBeReset != null) {
@@ -1570,7 +1591,7 @@
}
if (!mSystemReady) {
mSystemReady = true;
- final int currentUserId = mSettings.getUserId();
+ final int currentUserId = mCurrentUserId;
mStatusBarManagerInternal =
LocalServices.getService(StatusBarManagerInternal.class);
hideStatusBarIconLocked();
@@ -1591,7 +1612,7 @@
// the "mImeDrawsImeNavBarResLazyInitFuture" field.
synchronized (ImfLock.class) {
mImeDrawsImeNavBarResLazyInitFuture = null;
- if (currentUserId != mSettings.getUserId()) {
+ if (currentUserId != mCurrentUserId) {
// This means that the current user is already switched to other user
// before the background task is executed. In this scenario the relevant
// field should already be initialized.
@@ -1610,17 +1631,19 @@
UserHandle.ALL, broadcastFilterForAllUsers, null, null,
Context.RECEIVER_EXPORTED);
- final String defaultImiId = mSettings.getSelectedInputMethod();
+ final String defaultImiId = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, currentUserId);
final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
- mSettings = queryInputMethodServicesInternal(mContext, currentUserId,
- AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ currentUserId, AdditionalSubtypeMapRepository.get(currentUserId),
DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(currentUserId, newSettings);
postInputMethodSettingUpdatedLocked(
!imeSelectedOnBoot /* resetDefaultEnabledIme */);
updateFromSettingsLocked(true);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, currentUserId),
- mSettings.getEnabledInputMethodList());
+ newSettings.getEnabledInputMethodList());
}
}
}
@@ -1667,7 +1690,7 @@
}
synchronized (ImfLock.class) {
final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
- mSettings.getUserId(), null);
+ mCurrentUserId, null);
if (resolvedUserIds.length != 1) {
return Collections.emptyList();
}
@@ -1690,7 +1713,7 @@
}
synchronized (ImfLock.class) {
final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
- mSettings.getUserId(), null);
+ mCurrentUserId, null);
if (resolvedUserIds.length != 1) {
return Collections.emptyList();
}
@@ -1718,14 +1741,12 @@
}
// Check if selected IME of current user supports handwriting.
- if (userId == mSettings.getUserId()) {
+ if (userId == mCurrentUserId) {
return mBindingController.supportsStylusHandwriting()
&& (!connectionless
|| mBindingController.supportsConnectionlessStylusHandwriting());
}
- //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
- //TODO(b/210039666): use cache.
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(
settings.getSelectedInputMethod());
return imi != null && imi.supportsStylusHandwriting()
@@ -1749,9 +1770,8 @@
private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
@DirectBootAwareness int directBootAwareness, int callingUid) {
final InputMethodSettings settings;
- if (userId == mSettings.getUserId()
- && directBootAwareness == DirectBootAwareness.AUTO) {
- settings = mSettings;
+ if (directBootAwareness == DirectBootAwareness.AUTO) {
+ settings = InputMethodSettingsRepository.get(userId);
} else {
final AdditionalSubtypeMap additionalSubtypeMap =
AdditionalSubtypeMapRepository.get(userId);
@@ -1769,15 +1789,8 @@
@GuardedBy("ImfLock.class")
private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId,
int callingUid) {
- final ArrayList<InputMethodInfo> methodList;
- final InputMethodSettings settings;
- if (userId == mSettings.getUserId()) {
- methodList = mSettings.getEnabledInputMethodList();
- settings = mSettings;
- } else {
- settings = queryMethodMapForUserLocked(userId);
- methodList = settings.getEnabledInputMethodList();
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ final ArrayList<InputMethodInfo> methodList = settings.getEnabledInputMethodList();
// filter caller's access to input methods
methodList.removeIf(imi ->
!canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings));
@@ -1835,22 +1848,7 @@
@GuardedBy("ImfLock.class")
private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
- if (userId == mSettings.getUserId()) {
- final InputMethodInfo imi;
- String selectedMethodId = getSelectedMethodIdLocked();
- if (imiId == null && selectedMethodId != null) {
- imi = mSettings.getMethodMap().get(selectedMethodId);
- } else {
- imi = mSettings.getMethodMap().get(imiId);
- }
- if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, mSettings)) {
- return Collections.emptyList();
- }
- return mSettings.getEnabledInputMethodSubtypeList(
- imi, allowsImplicitlyEnabledSubtypes);
- }
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(imiId);
if (imi == null) {
return Collections.emptyList();
@@ -2031,7 +2029,7 @@
final boolean restarting = !initial;
final Binder startInputToken = new Binder();
- final StartInputInfo info = new StartInputInfo(mSettings.getUserId(),
+ final StartInputInfo info = new StartInputInfo(mCurrentUserId,
getCurTokenLocked(),
mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
UserHandle.getUserId(mCurClient.mUid),
@@ -2045,9 +2043,9 @@
// same-user scenarios.
// That said ignoring cross-user scenario will never affect IMEs that do not have
// INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
- if (mSettings.getUserId() == UserHandle.getUserId(
+ if (mCurrentUserId == UserHandle.getUserId(
mCurClient.mUid)) {
- mPackageManagerInternal.grantImplicitAccess(mSettings.getUserId(),
+ mPackageManagerInternal.grantImplicitAccess(mCurrentUserId,
null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()),
mCurClient.mUid, true /* direct */);
}
@@ -2070,7 +2068,8 @@
}
String curId = getCurIdLocked();
- final InputMethodInfo curInputMethodInfo = mSettings.getMethodMap().get(curId);
+ final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId)
+ .getMethodMap().get(curId);
final boolean suppressesSpellChecker =
curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
@@ -2258,17 +2257,18 @@
return currentMethodId;
}
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
final int oldDeviceId = mDeviceIdToShowIme;
mDeviceIdToShowIme = mVdmInternal.getDeviceIdForDisplayId(mDisplayIdToShowIme);
if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
if (oldDeviceId == DEVICE_ID_DEFAULT) {
return currentMethodId;
}
- final String defaultDeviceMethodId = mSettings.getSelectedDefaultDeviceInputMethod();
+ final String defaultDeviceMethodId = settings.getSelectedDefaultDeviceInputMethod();
if (DEBUG) {
Slog.v(TAG, "Restoring default device input method: " + defaultDeviceMethodId);
}
- mSettings.putSelectedDefaultDeviceInputMethod(null);
+ settings.putSelectedDefaultDeviceInputMethod(null);
return defaultDeviceMethodId;
}
@@ -2276,7 +2276,7 @@
mVirtualDeviceMethodMap.get(mDeviceIdToShowIme, currentMethodId);
if (Objects.equals(deviceMethodId, currentMethodId)) {
return currentMethodId;
- } else if (!mSettings.getMethodMap().containsKey(deviceMethodId)) {
+ } else if (!settings.getMethodMap().containsKey(deviceMethodId)) {
if (DEBUG) {
Slog.v(TAG, "Disabling IME on virtual device with id " + mDeviceIdToShowIme
+ " because its custom input method is not available: " + deviceMethodId);
@@ -2288,7 +2288,7 @@
if (DEBUG) {
Slog.v(TAG, "Storing default device input method " + currentMethodId);
}
- mSettings.putSelectedDefaultDeviceInputMethod(currentMethodId);
+ settings.putSelectedDefaultDeviceInputMethod(currentMethodId);
}
if (DEBUG) {
Slog.v(TAG, "Switching current input method from " + currentMethodId
@@ -2318,7 +2318,8 @@
if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) {
return false;
}
- final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId);
+ final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId)
+ .getMethodMap().get(selectedMethodId);
if (imi == null) {
return false;
}
@@ -2662,7 +2663,7 @@
} else if (packageName != null) {
if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
final PackageManager userAwarePackageManager =
- getPackageManagerForUser(mContext, mSettings.getUserId());
+ getPackageManagerForUser(mContext, mCurrentUserId);
ApplicationInfo applicationInfo = null;
try {
applicationInfo = userAwarePackageManager.getApplicationInfo(packageName,
@@ -2724,7 +2725,7 @@
return false;
}
if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
- && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId())) {
+ && mWindowManagerInternal.isKeyguardSecure(mCurrentUserId)) {
return false;
}
if ((visibility & InputMethodService.IME_ACTIVE) == 0
@@ -2741,7 +2742,8 @@
return false;
}
- List<InputMethodInfo> imes = mSettings.getEnabledInputMethodListWithFilter(
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ List<InputMethodInfo> imes = settings.getEnabledInputMethodListWithFilter(
InputMethodInfo::shouldShowInInputMethodPicker);
final int numImes = imes.size();
if (numImes > 2) return true;
@@ -2753,7 +2755,7 @@
for (int i = 0; i < numImes; ++i) {
final InputMethodInfo imi = imes.get(i);
final List<InputMethodSubtype> subtypes =
- mSettings.getEnabledInputMethodSubtypeList(imi, true);
+ settings.getEnabledInputMethodSubtypeList(imi, true);
final int subtypeCount = subtypes.size();
if (subtypeCount == 0) {
++nonAuxCount;
@@ -2905,11 +2907,12 @@
@GuardedBy("ImfLock.class")
void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
if (enabledMayChange) {
final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext,
- mSettings.getUserId());
+ settings.getUserId());
- List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList();
+ List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
for (int i = 0; i < enabled.size(); i++) {
// We allow the user to select "disabled until used" apps, so if they
// are enabling one of those here we now need to make it enabled.
@@ -2936,20 +2939,20 @@
if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
String ime = SecureSettingsWrapper.getString(
- Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId());
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, settings.getUserId());
String defaultDeviceIme = SecureSettingsWrapper.getString(
- Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+ Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, settings.getUserId());
if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
if (DEBUG) {
Slog.v(TAG, "Current input method " + ime + " differs from the stored default"
- + " device input method for user " + mSettings.getUserId()
+ + " device input method for user " + settings.getUserId()
+ " - restoring " + defaultDeviceIme);
}
SecureSettingsWrapper.putString(
Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme,
- mSettings.getUserId());
+ settings.getUserId());
SecureSettingsWrapper.putString(
- Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+ Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, settings.getUserId());
}
}
@@ -2957,14 +2960,14 @@
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
// enabled.
- String id = mSettings.getSelectedInputMethod();
+ String id = settings.getSelectedInputMethod();
// There is no input method selected, try to choose new applicable input method.
if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
- id = mSettings.getSelectedInputMethod();
+ id = settings.getSelectedInputMethod();
}
if (!TextUtils.isEmpty(id)) {
try {
- setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
+ setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeId(id));
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Unknown input method from prefs: " + id, e);
resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED);
@@ -2975,18 +2978,18 @@
}
// TODO: Instantiate mSwitchingController for each user.
- if (mSettings.getUserId() == mSwitchingController.getUserId()) {
- mSwitchingController.resetCircularListLocked(mSettings.getMethodMap());
+ if (settings.getUserId() == mSwitchingController.getUserId()) {
+ mSwitchingController.resetCircularListLocked(settings.getMethodMap());
} else {
mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
- mContext, mSettings.getMethodMap(), mSettings.getUserId());
+ mContext, settings.getMethodMap(), settings.getUserId());
}
// TODO: Instantiate mHardwareKeyboardShortcutController for each user.
- if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
- mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap());
+ if (settings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+ mHardwareKeyboardShortcutController.reset(settings.getMethodMap());
} else {
mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
- mSettings.getMethodMap(), mSettings.getUserId());
+ settings.getMethodMap(), settings.getUserId());
}
sendOnNavButtonFlagsChangedLocked();
}
@@ -3010,14 +3013,15 @@
@GuardedBy("ImfLock.class")
void setInputMethodLocked(String id, int subtypeId, int deviceId) {
- InputMethodInfo info = mSettings.getMethodMap().get(id);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ InputMethodInfo info = settings.getMethodMap().get(id);
if (info == null) {
throw getExceptionForUnknownImeId(id);
}
// See if we need to notify a subtype change within the same IME.
if (id.equals(getSelectedMethodIdLocked())) {
- final int userId = mSettings.getUserId();
+ final int userId = settings.getUserId();
final int subtypeCount = info.getSubtypeCount();
if (subtypeCount <= 0) {
notifyInputMethodSubtypeChangedLocked(userId, info, null);
@@ -3058,7 +3062,7 @@
// method is a custom one specific to a virtual device. So only update the settings
// entry used to restore the default device input method once we want to show the IME
// back on the default device.
- mSettings.putSelectedDefaultDeviceInputMethod(id);
+ settings.putSelectedDefaultDeviceInputMethod(id);
return;
}
IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -3586,7 +3590,7 @@
return InputBindResult.USER_SWITCHING;
}
final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
- mSettings.getUserId(), false /* enabledOnly */);
+ mCurrentUserId, false /* enabledOnly */);
for (int profileId : profileIdsWithDisabled) {
if (profileId == userId) {
scheduleSwitchUserTaskLocked(userId, cs.mClient);
@@ -3632,10 +3636,10 @@
}
// Verify if caller is a background user.
- final int currentUserId = mSettings.getUserId();
- if (userId != currentUserId) {
+ if (userId != mCurrentUserId) {
if (ArrayUtils.contains(
- mUserManagerInternal.getProfileIds(currentUserId, false), userId)) {
+ mUserManagerInternal.getProfileIds(mCurrentUserId, false),
+ userId)) {
// cross-profile access is always allowed here to allow
// profile-switching.
scheduleSwitchUserTaskLocked(userId, cs.mClient);
@@ -3824,7 +3828,7 @@
&& mImeBindingState.mFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
return true;
}
- if (mSettings.getUserId() != UserHandle.getUserId(uid)) {
+ if (mCurrentUserId != UserHandle.getUserId(uid)) {
return false;
}
if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid(
@@ -3888,9 +3892,10 @@
if (!calledWithValidTokenLocked(token)) {
return;
}
- final InputMethodInfo imi = mSettings.getMethodMap().get(id);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ final InputMethodInfo imi = settings.getMethodMap().get(id);
if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, mSettings)) {
+ imi.getPackageName(), callingUid, userId, settings)) {
throw getExceptionForUnknownImeId(id);
}
setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID);
@@ -3906,9 +3911,10 @@
if (!calledWithValidTokenLocked(token)) {
return;
}
- final InputMethodInfo imi = mSettings.getMethodMap().get(id);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ final InputMethodInfo imi = settings.getMethodMap().get(id);
if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, mSettings)) {
+ imi.getPackageName(), callingUid, userId, settings)) {
throw getExceptionForUnknownImeId(id);
}
if (subtype != null) {
@@ -3926,10 +3932,11 @@
if (!calledWithValidTokenLocked(token)) {
return false;
}
- final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtype();
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype();
final InputMethodInfo lastImi;
if (lastIme != null) {
- lastImi = mSettings.getMethodMap().get(lastIme.first);
+ lastImi = settings.getMethodMap().get(lastIme.first);
} else {
lastImi = null;
}
@@ -3953,7 +3960,7 @@
// This is a safety net. If the currentSubtype can't be added to the history
// and the framework couldn't find the last ime, we will make the last ime be
// the most applicable enabled keyboard subtype of the system imes.
- final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList();
+ final List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
if (enabled != null) {
final int enabledCount = enabled.size();
final String locale;
@@ -3961,7 +3968,7 @@
&& !TextUtils.isEmpty(mCurrentSubtype.getLocale())) {
locale = mCurrentSubtype.getLocale();
} else {
- locale = SystemLocaleWrapper.get(mSettings.getUserId()).get(0).toString();
+ locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString();
}
for (int i = 0; i < enabledCount; ++i) {
final InputMethodInfo imi = enabled.get(i);
@@ -4008,8 +4015,9 @@
@GuardedBy("ImfLock.class")
private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
- onlyCurrentIme, mSettings.getMethodMap().get(getSelectedMethodIdLocked()),
+ onlyCurrentIme, settings.getMethodMap().get(getSelectedMethodIdLocked()),
mCurrentSubtype);
if (nextSubtype == null) {
return false;
@@ -4025,9 +4033,10 @@
if (!calledWithValidTokenLocked(token)) {
return false;
}
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
false /* onlyCurrentIme */,
- mSettings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype);
+ settings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype);
return nextSubtype != null;
}
}
@@ -4039,12 +4048,7 @@
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
- if (mSettings.getUserId() == userId) {
- return mSettings.getLastInputMethodSubtype();
- }
-
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
- return settings.getLastInputMethodSubtype();
+ return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype();
}
}
@@ -4075,23 +4079,20 @@
}
final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
- final boolean isCurrentUser = (mSettings.getUserId() == userId);
- final InputMethodSettings settings = isCurrentUser
- ? mSettings
- : queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO);
+ final boolean isCurrentUser = (mCurrentUserId == userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
if (additionalSubtypeMap != newAdditionalSubtypeMap) {
AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
settings.getMethodMap());
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
if (isCurrentUser) {
final long ident = Binder.clearCallingIdentity();
try {
- mSettings = queryInputMethodServicesInternal(mContext,
- mSettings.getUserId(),
- AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
- DirectBootAwareness.AUTO);
postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -4121,9 +4122,8 @@
final long ident = Binder.clearCallingIdentity();
try {
synchronized (ImfLock.class) {
- final boolean currentUser = (mSettings.getUserId() == userId);
- final InputMethodSettings settings = currentUser
- ? mSettings : queryMethodMapForUserLocked(userId);
+ final boolean currentUser = (mCurrentUserId == userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
return;
}
@@ -4465,11 +4465,11 @@
}
return;
}
- if (mSettings.getUserId() != mSwitchingController.getUserId()) {
+ if (mCurrentUserId != mSwitchingController.getUserId()) {
return;
}
- final InputMethodInfo imi =
- mSettings.getMethodMap().get(getSelectedMethodIdLocked());
+ final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId)
+ .getMethodMap().get(getSelectedMethodIdLocked());
if (imi != null) {
mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
}
@@ -4529,8 +4529,9 @@
return;
} else {
// Called with current IME's token.
- if (mSettings.getMethodMap().get(id) != null
- && mSettings.getEnabledInputMethodListWithFilter(
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ if (settings.getMethodMap().get(id) != null
+ && settings.getEnabledInputMethodListWithFilter(
(info) -> info.getId().equals(id)).isEmpty()) {
throw new IllegalStateException("Requested IME is not enabled: " + id);
}
@@ -4709,21 +4710,23 @@
return false;
}
synchronized (ImfLock.class) {
+ final InputMethodSettings settings =
+ InputMethodSettingsRepository.get(mCurrentUserId);
final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked()
- && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId());
- final String lastInputMethodId = mSettings.getSelectedInputMethod();
+ && mWindowManagerInternal.isKeyguardSecure(settings.getUserId());
+ final String lastInputMethodId = settings.getSelectedInputMethod();
int lastInputMethodSubtypeId =
- mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
+ settings.getSelectedInputMethodSubtypeId(lastInputMethodId);
final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
.getSortedInputMethodAndSubtypeList(
showAuxSubtypes, isScreenLocked, true /* forImeMenu */,
- mContext, mSettings.getMethodMap(), mSettings.getUserId());
+ mContext, settings.getMethodMap(), settings.getUserId());
if (imList.isEmpty()) {
Slog.w(TAG, "Show switching menu failed, imList is empty,"
+ " showAuxSubtypes: " + showAuxSubtypes
+ " isScreenLocked: " + isScreenLocked
- + " userId: " + mSettings.getUserId());
+ + " userId: " + settings.getUserId());
return false;
}
@@ -4909,8 +4912,9 @@
@GuardedBy("ImfLock.class")
private boolean chooseNewDefaultIMELocked() {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME(
- mSettings.getEnabledInputMethodList());
+ settings.getEnabledInputMethodList());
if (imi != null) {
if (DEBUG) {
Slog.d(TAG, "New default IME was selected: " + imi.getId());
@@ -5024,6 +5028,8 @@
mMethodMapUpdateCount++;
mMyPackageMonitor.clearKnownImePackageNamesLocked();
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+
// Construct the set of possible IME packages for onPackageChanged() to avoid false
// negatives when the package state remains to be the same but only the component state is
// changed.
@@ -5034,7 +5040,7 @@
final List<ResolveInfo> allInputMethodServices =
mContext.getPackageManager().queryIntentServicesAsUser(
new Intent(InputMethod.SERVICE_INTERFACE),
- PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getUserId());
+ PackageManager.MATCH_DISABLED_COMPONENTS, settings.getUserId());
final int numImes = allInputMethodServices.size();
for (int i = 0; i < numImes; ++i) {
final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
@@ -5049,11 +5055,11 @@
if (!resetDefaultEnabledIme) {
boolean enabledImeFound = false;
boolean enabledNonAuxImeFound = false;
- final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodList();
+ final List<InputMethodInfo> enabledImes = settings.getEnabledInputMethodList();
final int numImes = enabledImes.size();
for (int i = 0; i < numImes; ++i) {
final InputMethodInfo imi = enabledImes.get(i);
- if (mSettings.getMethodMap().containsKey(imi.getId())) {
+ if (settings.getMethodMap().containsKey(imi.getId())) {
enabledImeFound = true;
if (!imi.isAuxiliaryIme()) {
enabledNonAuxImeFound = true;
@@ -5077,7 +5083,7 @@
if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) {
final ArrayList<InputMethodInfo> defaultEnabledIme =
- InputMethodInfoUtils.getDefaultEnabledImes(mContext, mSettings.getMethodList(),
+ InputMethodInfoUtils.getDefaultEnabledImes(mContext, settings.getMethodList(),
reenableMinimumNonAuxSystemImes);
final int numImes = defaultEnabledIme.size();
for (int i = 0; i < numImes; ++i) {
@@ -5089,9 +5095,9 @@
}
}
- final String defaultImiId = mSettings.getSelectedInputMethod();
+ final String defaultImiId = settings.getSelectedInputMethod();
if (!TextUtils.isEmpty(defaultImiId)) {
- if (!mSettings.getMethodMap().containsKey(defaultImiId)) {
+ if (!settings.getMethodMap().containsKey(defaultImiId)) {
Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
if (chooseNewDefaultIMELocked()) {
updateInputMethodsFromSettingsLocked(true);
@@ -5105,26 +5111,26 @@
updateDefaultVoiceImeIfNeededLocked();
// TODO: Instantiate mSwitchingController for each user.
- if (mSettings.getUserId() == mSwitchingController.getUserId()) {
- mSwitchingController.resetCircularListLocked(mSettings.getMethodMap());
+ if (settings.getUserId() == mSwitchingController.getUserId()) {
+ mSwitchingController.resetCircularListLocked(settings.getMethodMap());
} else {
mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
- mContext, mSettings.getMethodMap(), mSettings.getUserId());
+ mContext, settings.getMethodMap(), mCurrentUserId);
}
// TODO: Instantiate mHardwareKeyboardShortcutController for each user.
- if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
- mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap());
+ if (settings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+ mHardwareKeyboardShortcutController.reset(settings.getMethodMap());
} else {
mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
- mSettings.getMethodMap(), mSettings.getUserId());
+ settings.getMethodMap(), settings.getUserId());
}
sendOnNavButtonFlagsChangedLocked();
// Notify InputMethodListListeners of the new installed InputMethods.
- final List<InputMethodInfo> inputMethodList = mSettings.getMethodList();
+ final List<InputMethodInfo> inputMethodList = settings.getMethodList();
mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED,
- mSettings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget();
+ settings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget();
}
@GuardedBy("ImfLock.class")
@@ -5139,11 +5145,12 @@
@GuardedBy("ImfLock.class")
private void updateDefaultVoiceImeIfNeededLocked() {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
final String systemSpeechRecognizer =
mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer);
- final String currentDefaultVoiceImeId = mSettings.getDefaultVoiceInputMethod();
+ final String currentDefaultVoiceImeId = settings.getDefaultVoiceInputMethod();
final InputMethodInfo newSystemVoiceIme = InputMethodInfoUtils.chooseSystemVoiceIme(
- mSettings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId);
+ settings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId);
if (newSystemVoiceIme == null) {
if (DEBUG) {
Slog.i(TAG, "Found no valid default Voice IME. If the user is still locked,"
@@ -5152,7 +5159,7 @@
// Clear DEFAULT_VOICE_INPUT_METHOD when necessary. Note that InputMethodSettings
// does not update the actual Secure Settings until the user is unlocked.
if (!TextUtils.isEmpty(currentDefaultVoiceImeId)) {
- mSettings.putDefaultVoiceInputMethod("");
+ settings.putDefaultVoiceInputMethod("");
// We don't support disabling the voice ime when a package is removed from the
// config.
}
@@ -5165,7 +5172,7 @@
Slog.i(TAG, "Enabling the default Voice IME:" + newSystemVoiceIme);
}
setInputMethodEnabledLocked(newSystemVoiceIme.getId(), true);
- mSettings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
+ settings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
}
// ----------------------------------------------------------------------
@@ -5180,8 +5187,9 @@
*/
@GuardedBy("ImfLock.class")
private boolean setInputMethodEnabledLocked(String id, boolean enabled) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
if (enabled) {
- final String enabledImeIdsStr = mSettings.getEnabledInputMethodsStr();
+ final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
enabledImeIdsStr, id);
if (TextUtils.equals(enabledImeIdsStr, newEnabledImeIdsStr)) {
@@ -5189,29 +5197,29 @@
// Nothing to do. The previous state was enabled.
return true;
}
- mSettings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
+ settings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
// Previous state was disabled.
return false;
} else {
- final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
+ final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = settings
.getEnabledInputMethodsAndSubtypeList();
StringBuilder builder = new StringBuilder();
- if (mSettings.buildAndPutEnabledInputMethodsStrRemovingId(
+ if (settings.buildAndPutEnabledInputMethodsStrRemovingId(
builder, enabledInputMethodsList, id)) {
if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
// Disabled input method is currently selected, switch to another one.
- final String selId = mSettings.getSelectedInputMethod();
+ final String selId = settings.getSelectedInputMethod();
if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
resetSelectedInputMethodAndSubtypeLocked("");
}
- } else if (id.equals(mSettings.getSelectedDefaultDeviceInputMethod())) {
+ } else if (id.equals(settings.getSelectedDefaultDeviceInputMethod())) {
// Disabled default device IME while using a virtual device one, choose a
// new default one but only update the settings.
InputMethodInfo newDefaultIme =
InputMethodInfoUtils.getMostApplicableDefaultIME(
- mSettings.getEnabledInputMethodList());
- mSettings.putSelectedDefaultDeviceInputMethod(
+ settings.getEnabledInputMethodList());
+ settings.putSelectedDefaultDeviceInputMethod(
newDefaultIme == null ? null : newDefaultIme.getId());
}
// Previous state was enabled.
@@ -5227,29 +5235,30 @@
@GuardedBy("ImfLock.class")
private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
boolean setSubtypeOnly) {
- mSettings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(),
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ settings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(),
mCurrentSubtype);
// Set Subtype here
if (imi == null || subtypeId < 0) {
- mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+ settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
mCurrentSubtype = null;
} else {
if (subtypeId < imi.getSubtypeCount()) {
InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
- mSettings.putSelectedSubtype(subtype.hashCode());
+ settings.putSelectedSubtype(subtype.hashCode());
mCurrentSubtype = subtype;
} else {
- mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+ settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
// If the subtype is not specified, choose the most applicable one
mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
}
}
- notifyInputMethodSubtypeChangedLocked(mSettings.getUserId(), imi, mCurrentSubtype);
+ notifyInputMethodSubtypeChangedLocked(settings.getUserId(), imi, mCurrentSubtype);
if (!setSubtypeOnly) {
// Set InputMethod here
- mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
+ settings.putSelectedInputMethod(imi != null ? imi.getId() : "");
}
}
@@ -5257,13 +5266,15 @@
private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
mDisplayIdToShowIme = INVALID_DISPLAY;
- mSettings.putSelectedDefaultDeviceInputMethod(null);
- InputMethodInfo imi = mSettings.getMethodMap().get(newDefaultIme);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ settings.putSelectedDefaultDeviceInputMethod(null);
+
+ InputMethodInfo imi = settings.getMethodMap().get(newDefaultIme);
int lastSubtypeId = NOT_A_SUBTYPE_ID;
// newDefaultIme is empty when there is no candidate for the selected IME.
if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
- String subtypeHashCode = mSettings.getLastSubtypeForInputMethod(newDefaultIme);
+ String subtypeHashCode = settings.getLastSubtypeForInputMethod(newDefaultIme);
if (subtypeHashCode != null) {
try {
lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
@@ -5290,12 +5301,12 @@
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
- if (mSettings.getUserId() == userId) {
+ if (mCurrentUserId == userId) {
return getCurrentInputMethodSubtypeLocked();
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
- return settings.getCurrentInputMethodSubtypeForNonCurrentUsers();
+ return InputMethodSettingsRepository.get(userId)
+ .getCurrentInputMethodSubtypeForNonCurrentUsers();
}
}
@@ -5315,26 +5326,27 @@
if (selectedMethodId == null) {
return null;
}
- final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
- final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ final boolean subtypeIsSelected = settings.isSubtypeSelected();
+ final InputMethodInfo imi = settings.getMethodMap().get(selectedMethodId);
if (imi == null || imi.getSubtypeCount() == 0) {
return null;
}
if (!subtypeIsSelected || mCurrentSubtype == null
|| !SubtypeUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
- int subtypeId = mSettings.getSelectedInputMethodSubtypeId(selectedMethodId);
+ int subtypeId = settings.getSelectedInputMethodSubtypeId(selectedMethodId);
if (subtypeId == NOT_A_SUBTYPE_ID) {
// If there are no selected subtypes, the framework will try to find
// the most applicable subtype from explicitly or implicitly enabled
// subtypes.
List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
- mSettings.getEnabledInputMethodSubtypeList(imi, true);
+ settings.getEnabledInputMethodSubtypeList(imi, true);
// If there is only one explicitly or implicitly enabled subtype,
// just returns it.
if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
} else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
- final String locale = SystemLocaleWrapper.get(mSettings.getUserId())
+ final String locale = SystemLocaleWrapper.get(settings.getUserId())
.get(0).toString();
mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtype(
explicitlyOrImplicitlyEnabledSubtypes,
@@ -5357,38 +5369,22 @@
*/
@GuardedBy("ImfLock.class")
private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
- final InputMethodSettings settings;
- if (userId == mSettings.getUserId()) {
- settings = mSettings;
- } else {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- settings = queryInputMethodServicesInternal(mContext, userId,
- additionalSubtypeMap, DirectBootAwareness.AUTO);
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
return settings.getMethodMap().get(settings.getSelectedInputMethod());
}
@GuardedBy("ImfLock.class")
- private InputMethodSettings queryMethodMapForUserLocked(@UserIdInt int userId) {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO);
- }
-
- @GuardedBy("ImfLock.class")
private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) {
- if (userId == mSettings.getUserId()) {
- if (!mSettings.getMethodMap().containsKey(imeId)
- || !mSettings.getEnabledInputMethodList()
- .contains(mSettings.getMethodMap().get(imeId))) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ if (userId == mCurrentUserId) {
+ if (!settings.getMethodMap().containsKey(imeId)
+ || !settings.getEnabledInputMethodList()
+ .contains(settings.getMethodMap().get(imeId))) {
return false; // IME is not found or not enabled.
}
setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
return true;
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
if (!settings.getMethodMap().containsKey(imeId)
|| !settings.getEnabledInputMethodList().contains(
settings.getMethodMap().get(imeId))) {
@@ -5429,8 +5425,9 @@
@GuardedBy("ImfLock.class")
private void switchKeyboardLayoutLocked(int direction) {
- final InputMethodInfo currentImi = mSettings.getMethodMap().get(
- getSelectedMethodIdLocked());
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+
+ final InputMethodInfo currentImi = settings.getMethodMap().get(getSelectedMethodIdLocked());
if (currentImi == null) {
return;
}
@@ -5442,7 +5439,7 @@
if (nextSubtypeHandle == null) {
return;
}
- final InputMethodInfo nextImi = mSettings.getMethodMap().get(nextSubtypeHandle.getImeId());
+ final InputMethodInfo nextImi = settings.getMethodMap().get(nextSubtypeHandle.getImeId());
if (nextImi == null) {
return;
}
@@ -5521,17 +5518,14 @@
@Override
public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) {
synchronized (ImfLock.class) {
- if (userId == mSettings.getUserId()) {
- if (!mSettings.getMethodMap().containsKey(imeId)) {
- return false; // IME is not found.
- }
- setInputMethodEnabledLocked(imeId, enabled);
- return true;
- }
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.getMethodMap().containsKey(imeId)) {
return false; // IME is not found.
}
+ if (userId == mCurrentUserId) {
+ setInputMethodEnabledLocked(imeId, enabled);
+ return true;
+ }
if (enabled) {
final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
@@ -5858,8 +5852,9 @@
final Printer p = new PrintWriterPrinter(pw);
synchronized (ImfLock.class) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
p.println("Current Input Method Manager state:");
- final List<InputMethodInfo> methodList = mSettings.getMethodList();
+ final List<InputMethodInfo> methodList = settings.getMethodList();
int numImes = methodList.size();
p.println(" Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount);
for (int i = 0; i < numImes; i++) {
@@ -5873,6 +5868,7 @@
@SuppressWarnings("GuardedBy") Consumer<ClientState> clientControllerDump = c -> {
p.println(" " + c + ":");
p.println(" client=" + c.mClient);
+
p.println(" fallbackInputConnection="
+ c.mFallbackInputConnection);
p.println(" sessionRequested="
@@ -5881,8 +5877,12 @@
" sessionRequestedForAccessibility="
+ c.mSessionRequestedForAccessibility);
p.println(" curSession=" + c.mCurSession);
+ p.println(" selfReportedDisplayId=" + c.mSelfReportedDisplayId);
+ p.println(" uid=" + c.mUid);
+ p.println(" pid=" + c.mPid);
};
mClientController.forAllClients(clientControllerDump);
+ p.println(" mCurrentUserId=" + mCurrentUserId);
p.println(" mCurMethodId=" + getSelectedMethodIdLocked());
client = mCurClient;
p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
@@ -5908,8 +5908,6 @@
? Arrays.toString(mStylusIds.toArray()) : ""));
p.println(" mSwitchingController:");
mSwitchingController.dump(p);
- p.println(" mSettings:");
- mSettings.dump(p, " ");
p.println(" mStartInputHistory:");
mStartInputHistory.dump(pw, " ");
@@ -6164,7 +6162,7 @@
}
synchronized (ImfLock.class) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getUserId(), shellCommand.getErrPrintWriter());
+ mCurrentUserId, shellCommand.getErrPrintWriter());
try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
for (int userId : userIds) {
final List<InputMethodInfo> methods = all
@@ -6209,7 +6207,7 @@
PrintWriter error = shellCommand.getErrPrintWriter()) {
synchronized (ImfLock.class) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getUserId(), shellCommand.getErrPrintWriter());
+ mCurrentUserId, shellCommand.getErrPrintWriter());
for (int userId : userIds) {
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
@@ -6268,14 +6266,14 @@
PrintWriter error) {
boolean failedToEnableUnknownIme = false;
boolean previouslyEnabled = false;
- if (userId == mSettings.getUserId()) {
- if (enabled && !mSettings.getMethodMap().containsKey(imeId)) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ if (userId == mCurrentUserId) {
+ if (enabled && !settings.getMethodMap().containsKey(imeId)) {
failedToEnableUnknownIme = true;
} else {
previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled);
}
} else {
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
if (enabled) {
if (!settings.getMethodMap().containsKey(imeId)) {
failedToEnableUnknownIme = true;
@@ -6330,7 +6328,7 @@
PrintWriter error = shellCommand.getErrPrintWriter()) {
synchronized (ImfLock.class) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getUserId(), shellCommand.getErrPrintWriter());
+ mCurrentUserId, shellCommand.getErrPrintWriter());
for (int userId : userIds) {
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
@@ -6370,7 +6368,7 @@
synchronized (ImfLock.class) {
try (PrintWriter out = shellCommand.getOutPrintWriter()) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getUserId(), shellCommand.getErrPrintWriter());
+ mCurrentUserId, shellCommand.getErrPrintWriter());
for (int userId : userIds) {
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
@@ -6382,15 +6380,16 @@
}
final String nextIme;
final List<InputMethodInfo> nextEnabledImes;
- if (userId == mSettings.getUserId()) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ if (userId == mCurrentUserId) {
hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
mBindingController.unbindCurrentMethod();
// Enable default IMEs, disable others
- var toDisable = mSettings.getEnabledInputMethodList();
+ var toDisable = settings.getEnabledInputMethodList();
var defaultEnabled = InputMethodInfoUtils.getDefaultEnabledImes(
- mContext, mSettings.getMethodList());
+ mContext, settings.getMethodList());
toDisable.removeAll(defaultEnabled);
for (InputMethodInfo info : toDisable) {
setInputMethodEnabledLocked(info.getId(), false);
@@ -6404,16 +6403,11 @@
}
updateInputMethodsFromSettingsLocked(true /* enabledMayChange */);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
- getPackageManagerForUser(mContext, mSettings.getUserId()),
- mSettings.getEnabledInputMethodList());
- nextIme = mSettings.getSelectedInputMethod();
- nextEnabledImes = mSettings.getEnabledInputMethodList();
+ getPackageManagerForUser(mContext, settings.getUserId()),
+ settings.getEnabledInputMethodList());
+ nextIme = settings.getSelectedInputMethod();
+ nextEnabledImes = settings.getEnabledInputMethodList();
} else {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- final InputMethodSettings settings = queryInputMethodServicesInternal(
- mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO);
-
nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
settings.getMethodList());
nextIme = InputMethodInfoUtils.getMostApplicableDefaultIME(
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
new file mode 100644
index 0000000..60b9a4c
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+final class InputMethodSettingsRepository {
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private InputMethodSettingsRepository() {
+ }
+
+ @NonNull
+ @GuardedBy("ImfLock.class")
+ static InputMethodSettings get(@UserIdInt int userId) {
+ final InputMethodSettings obj = sPerUserMap.get(userId);
+ if (obj != null) {
+ return obj;
+ }
+ return InputMethodSettings.createEmptyMap(userId);
+ }
+
+ @GuardedBy("ImfLock.class")
+ static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
+ sPerUserMap.put(userId, obj);
+ }
+
+ static void initialize(@NonNull Handler handler, @NonNull Context context) {
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ handler.post(() -> {
+ userManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ final int userId = user.id;
+ handler.post(() -> {
+ synchronized (ImfLock.class) {
+ sPerUserMap.remove(userId);
+ }
+ });
+ }
+ });
+ synchronized (ImfLock.class) {
+ for (int userId : userManagerInternal.getUserIds()) {
+ final InputMethodSettings settings =
+ InputMethodManagerService.queryInputMethodServicesInternal(
+ context,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ sPerUserMap.put(userId, settings);
+ }
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
new file mode 100644
index 0000000..7f00229
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.UserManagerInternal;
+
+import java.util.function.Consumer;
+
+final class UserDataRepository {
+
+ @GuardedBy("ImfLock.class")
+ private final SparseArray<UserData> mUserData = new SparseArray<>();
+
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ UserData getOrCreate(@UserIdInt int userId) {
+ UserData userData = mUserData.get(userId);
+ if (userData == null) {
+ userData = new UserData(userId);
+ mUserData.put(userId, userData);
+ }
+ return userData;
+ }
+
+ @GuardedBy("ImfLock.class")
+ void forAllUserData(Consumer<UserData> consumer) {
+ for (int i = 0; i < mUserData.size(); i++) {
+ consumer.accept(mUserData.valueAt(i));
+ }
+ }
+
+ UserDataRepository(@NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal) {
+ userManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ final int userId = user.id;
+ handler.post(() -> {
+ synchronized (ImfLock.class) {
+ mUserData.remove(userId);
+ }
+ });
+ }
+
+ @Override
+ public void onUserCreated(UserInfo user, Object unusedToken) {
+ final int userId = user.id;
+ handler.post(() -> {
+ synchronized (ImfLock.class) {
+ getOrCreate(userId);
+ }
+ });
+ }
+ });
+ }
+
+ /** Placeholder for all IMMS user specific fields */
+ static final class UserData {
+ @UserIdInt
+ final int mUserId;
+
+ /**
+ * Intended to be instantiated only from this file.
+ */
+ private UserData(@UserIdInt int userId) {
+ mUserId = userId;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 71a9f54..32a0ef4 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -38,6 +38,7 @@
import android.util.Log;
import com.android.internal.location.GpsNetInitiatedHandler;
+import com.android.server.FgThread;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -200,7 +201,12 @@
SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
if (subManager != null) {
- subManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
+ if (Flags.subscriptionsListenerThread()) {
+ subManager.addOnSubscriptionsChangedListener(FgThread.getExecutor(),
+ mOnSubscriptionsChangeListener);
+ } else {
+ subManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
+ }
}
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 194ab04..3d68555 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -1458,14 +1458,10 @@
@Override
public IBinder getBinderForSetQueue() throws RemoteException {
return new ParcelableListBinder<QueueItem>(
+ QueueItem.class,
(list) -> {
- // Checking list items are instanceof QueueItem to validate against
- // malicious apps calling it directly via reflection with non compilable
- // items. See b/317048338 for more details
- List<QueueItem> sanitizedQueue =
- list.stream().filter(it -> it instanceof QueueItem).toList();
synchronized (mLock) {
- mQueue = sanitizedQueue;
+ mQueue = list;
}
mHandler.post(MessageHandler.MSG_UPDATE_QUEUE);
});
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 8df38a8..c105b9c 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -473,6 +473,10 @@
.setProviderId(mUniqueId)
.setSystemSession(true)
.addSelectedRoute(MediaRoute2Info.ROUTE_ID_DEFAULT)
+ .setTransferReason(newSessionInfo.getTransferReason())
+ .setTransferInitiator(
+ newSessionInfo.getTransferInitiatorUserHandle(),
+ newSessionInfo.getTransferInitiatorPackageName())
.build();
return true;
}
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index bd73cb6..1938642 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -23,9 +23,15 @@
import android.app.Notification;
import android.app.NotificationChannel;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.LoggingOnly;
import android.content.Context;
import android.media.AudioAttributes;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Slog;
+import com.android.internal.compat.IPlatformCompat;
/**
* Stores the latest notification channel information for this notification
@@ -34,14 +40,26 @@
private static final String TAG = "ChannelExtractor";
private static final boolean DBG = false;
+ /**
+ * Corrects audio attributes for notifications based on characteristics of the notifications.
+ */
+ @ChangeId
+ @LoggingOnly
+ static final long RESTRICT_AUDIO_ATTRIBUTES = 331793339L;
+
private RankingConfig mConfig;
private Context mContext;
+ private IPlatformCompat mPlatformCompat;
public void initialize(Context ctx, NotificationUsageStats usageStats) {
mContext = ctx;
if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
}
+ public void setCompatChangeLogger(IPlatformCompat platformCompat) {
+ mPlatformCompat = platformCompat;
+ }
+
public RankingReconsideration process(NotificationRecord record) {
if (record == null || record.getNotification() == null) {
if (DBG) Slog.d(TAG, "skipping empty notification");
@@ -80,6 +98,7 @@
}
if (updateAttributes) {
+ reportAudioAttributesChanged(record.getUid());
NotificationChannel clone = record.getChannel().copy();
clone.setSound(clone.getSound(), new AudioAttributes.Builder(attributes)
.setUsage(USAGE_NOTIFICATION)
@@ -91,6 +110,17 @@
return null;
}
+ private void reportAudioAttributesChanged(int uid) {
+ final long id = Binder.clearCallingIdentity();
+ try {
+ mPlatformCompat.reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, uid);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unexpected exception while reporting to changecompat", e);
+ } finally {
+ Binder.restoreCallingIdentity(id);
+ }
+ }
+
@Override
public void setConfig(RankingConfig config) {
mConfig = config;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerPrivate.java b/services/core/java/com/android/server/notification/NotificationManagerPrivate.java
index 2cc63eb..a3a91e2 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerPrivate.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerPrivate.java
@@ -25,4 +25,6 @@
interface NotificationManagerPrivate {
@Nullable
NotificationRecord getNotificationByKey(String key);
+
+ void timeoutNotification(String key);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b14242e..ebea05d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -139,7 +139,6 @@
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.contentprotection.flags.Flags.rapidClearNotificationsByListenerAppOpEnabled;
-
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
@@ -221,6 +220,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
+import android.content.pm.ModuleInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
@@ -238,6 +238,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.DeadObjectException;
import android.os.DeviceIdleManager;
import android.os.Environment;
import android.os.Handler;
@@ -305,7 +306,6 @@
import android.view.accessibility.AccessibilityManager;
import android.widget.RemoteViews;
import android.widget.Toast;
-
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -357,9 +357,7 @@
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.BackgroundActivityStartCallback;
import com.android.server.wm.WindowManagerInternal;
-
import libcore.io.IoUtils;
-
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParserException;
@@ -521,7 +519,7 @@
/**
* Apps that post custom toasts in the background will have those blocked. Apps can
* still post toasts created with
- * {@link android.widget.Toast#makeText(Context, CharSequence, int)} and its variants while
+ * {@link Toast#makeText(Context, CharSequence, int)} and its variants while
* in the background.
*/
@ChangeId
@@ -556,7 +554,7 @@
/**
* Rate limit showing toasts, on a per package basis.
*
- * It limits the number of {@link android.widget.Toast#show()} calls to prevent overburdening
+ * It limits the number of {@link Toast#show()} calls to prevent overburdening
* the user with too many toasts in a limited time. Any attempt to show more toasts than allowed
* in a certain time frame will result in the toast being discarded.
*/
@@ -580,9 +578,9 @@
static final long ENFORCE_NO_CLEAR_FLAG_ON_MEDIA_NOTIFICATION = 264179692L;
/**
- * App calls to {@link android.app.NotificationManager#setInterruptionFilter} and
- * {@link android.app.NotificationManager#setNotificationPolicy} manage DND through the
- * creation and activation of an implicit {@link android.app.AutomaticZenRule}.
+ * App calls to {@link NotificationManager#setInterruptionFilter} and
+ * {@link NotificationManager#setNotificationPolicy} manage DND through the
+ * creation and activation of an implicit {@link AutomaticZenRule}.
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
@@ -624,6 +622,8 @@
private PowerManager mPowerManager;
private PostNotificationTrackerFactory mPostNotificationTrackerFactory;
+ private LockPatternUtils mLockUtils;
+
final IBinder mForegroundToken = new Binder();
@VisibleForTesting
WorkerHandler mHandler;
@@ -676,6 +676,10 @@
private static final int DB_VERSION = 1;
+
+ private static final String ADSERVICES_MODULE_PKG_NAME =
+ "com.android.adservices";
+
private static final String TAG_NOTIFICATION_POLICY = "notification-policy";
private static final String ATTR_VERSION = "version";
@@ -709,6 +713,7 @@
private NotificationHistoryManager mHistoryManager;
protected SnoozeHelper mSnoozeHelper;
+ private TimeToLiveHelper mTtlHelper;
private GroupHelper mGroupHelper;
private int mAutoGroupAtCount;
private boolean mIsTelevision;
@@ -734,6 +739,10 @@
// Broadcast intent receiver for notification permissions review-related intents
private ReviewNotificationPermissionsReceiver mReviewNotificationPermissionsReceiver;
+ private AppOpsManager.OnOpChangedListener mAppOpsListener;
+
+ private ModuleInfo mAdservicesModuleInfo;
+
static class Archive {
final SparseArray<Boolean> mEnabled;
final int mBufferSize;
@@ -779,7 +788,7 @@
public StatusBarNotification[] getArray(UserManager um, int count, boolean includeSnoozed) {
ArrayList<Integer> currentUsers = new ArrayList<>();
- currentUsers.add(UserHandle.USER_ALL);
+ currentUsers.add(USER_ALL);
Binder.withCleanCallingIdentity(() -> {
for (int user : um.getProfileIds(ActivityManager.getCurrentUser(), false)) {
currentUsers.add(user);
@@ -902,14 +911,14 @@
@VisibleForTesting
boolean isDNDMigrationDone(int userId) {
- return Settings.Secure.getIntForUser(getContext().getContentResolver(),
- Settings.Secure.DND_CONFIGS_MIGRATED, 0, userId) == 1;
+ return Secure.getIntForUser(getContext().getContentResolver(),
+ Secure.DND_CONFIGS_MIGRATED, 0, userId) == 1;
}
@VisibleForTesting
void setDNDMigrationDone(int userId) {
- Settings.Secure.putIntForUser(getContext().getContentResolver(),
- Settings.Secure.DND_CONFIGS_MIGRATED, 1, userId);
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.DND_CONFIGS_MIGRATED, 1, userId);
}
protected void migrateDefaultNAS() {
@@ -936,15 +945,15 @@
@VisibleForTesting
void setNASMigrationDone(int baseUserId) {
for (int profileId : mUm.getProfileIds(baseUserId, false)) {
- Settings.Secure.putIntForUser(getContext().getContentResolver(),
- Settings.Secure.NAS_SETTINGS_UPDATED, 1, profileId);
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.NAS_SETTINGS_UPDATED, 1, profileId);
}
}
@VisibleForTesting
boolean isNASMigrationDone(int userId) {
- return (Settings.Secure.getIntForUser(getContext().getContentResolver(),
- Settings.Secure.NAS_SETTINGS_UPDATED, 0, userId) == 1);
+ return (Secure.getIntForUser(getContext().getContentResolver(),
+ Secure.NAS_SETTINGS_UPDATED, 0, userId) == 1);
}
boolean isProfileUser(UserInfo userInfo) {
@@ -1097,7 +1106,7 @@
mSnoozeHelper.readXml(parser, System.currentTimeMillis());
}
if (LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG.equals(parser.getName())) {
- if (forRestore && userId != UserHandle.USER_SYSTEM) {
+ if (forRestore && userId != USER_SYSTEM) {
continue;
}
mLockScreenAllowSecureNotifications = parser.getAttributeBoolean(null,
@@ -1141,7 +1150,7 @@
InputStream infile = null;
try {
infile = mPolicyFile.openRead();
- readPolicyXml(infile, false /*forRestore*/, UserHandle.USER_ALL);
+ readPolicyXml(infile, false /*forRestore*/, USER_ALL);
// We re-load the default dnd packages to allow the newly added and denined.
final boolean isWatch = mPackageManagerClient.hasSystemFeature(
@@ -1191,7 +1200,7 @@
}
try {
- writePolicyXml(stream, false /*forBackup*/, UserHandle.USER_ALL);
+ writePolicyXml(stream, false /*forBackup*/, USER_ALL);
mPolicyFile.finishWrite(stream);
} catch (IOException e) {
Slog.w(TAG, "Failed to save policy file, restoring backup", e);
@@ -1220,7 +1229,7 @@
mAssistants.writeXml(out, forBackup, userId);
mSnoozeHelper.writeXml(out);
mConditionProviders.writeXml(out, forBackup, userId);
- if (!forBackup || userId == UserHandle.USER_SYSTEM) {
+ if (!forBackup || userId == USER_SYSTEM) {
writeSecureNotificationsPolicy(out);
}
out.endTag(null, TAG_NOTIFICATION_POLICY);
@@ -1273,7 +1282,7 @@
StatusBarNotification sbn = r.getSbn();
cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
- sbn.getId(), Notification.FLAG_AUTO_CANCEL,
+ sbn.getId(), FLAG_AUTO_CANCEL,
FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_BUBBLE,
false, r.getUserId(), REASON_CLICK, nv.rank, nv.count, null);
nv.recycle();
@@ -1761,9 +1770,50 @@
};
- NotificationManagerPrivate mNotificationManagerPrivate = key -> {
- synchronized (mNotificationLock) {
- return mNotificationsByKey.get(key);
+ NotificationManagerPrivate mNotificationManagerPrivate = new NotificationManagerPrivate() {
+ @Nullable
+ @Override
+ public NotificationRecord getNotificationByKey(String key) {
+ synchronized (mNotificationLock) {
+ return mNotificationsByKey.get(key);
+ }
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+ public void timeoutNotification(String key) {
+ boolean foundNotification = false;
+ int uid = 0;
+ int pid = 0;
+ String packageName = null;
+ String tag = null;
+ int id = 0;
+ int userId = 0;
+
+ synchronized (mNotificationLock) {
+ NotificationRecord record = findNotificationByKeyLocked(key);
+ if (record != null) {
+ foundNotification = true;
+ uid = record.getUid();
+ pid = record.getSbn().getInitialPid();
+ packageName = record.getSbn().getPackageName();
+ tag = record.getSbn().getTag();
+ id = record.getSbn().getId();
+ userId = record.getUserId();
+ }
+ }
+ if (foundNotification) {
+ if (lifetimeExtensionRefactor()) {
+ cancelNotification(uid, pid, packageName, tag, id, 0,
+ FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB
+ | FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY,
+ true, userId, REASON_TIMEOUT, null);
+ } else {
+ cancelNotification(uid, pid, packageName, tag, id, 0,
+ FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB,
+ true, userId, REASON_TIMEOUT, null);
+ }
+ }
}
};
@@ -1893,7 +1943,7 @@
|| action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)
|| action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) {
int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_ALL);
+ USER_ALL);
String pkgList[] = null;
int uidList[] = null;
boolean removingPackage = queryRemove &&
@@ -1942,7 +1992,7 @@
try {
final int enabled = mPackageManager.getApplicationEnabledSetting(
pkgName,
- changeUserId != UserHandle.USER_ALL ? changeUserId :
+ changeUserId != USER_ALL ? changeUserId :
USER_SYSTEM);
if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|| enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
@@ -2055,22 +2105,22 @@
private final class SettingsObserver extends ContentObserver {
private final Uri NOTIFICATION_BADGING_URI
- = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING);
+ = Secure.getUriFor(Secure.NOTIFICATION_BADGING);
private final Uri NOTIFICATION_BUBBLES_URI
- = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES);
+ = Secure.getUriFor(Secure.NOTIFICATION_BUBBLES);
private final Uri NOTIFICATION_RATE_LIMIT_URI
= Settings.Global.getUriFor(Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE);
private final Uri NOTIFICATION_HISTORY_ENABLED
- = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_HISTORY_ENABLED);
+ = Secure.getUriFor(Secure.NOTIFICATION_HISTORY_ENABLED);
private final Uri NOTIFICATION_SHOW_MEDIA_ON_QUICK_SETTINGS_URI
= Settings.Global.getUriFor(Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS);
private final Uri LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
- = Settings.Secure.getUriFor(
- Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ = Secure.getUriFor(
+ Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
private final Uri LOCK_SCREEN_SHOW_NOTIFICATIONS
- = Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS);
+ = Secure.getUriFor(Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS);
private final Uri SHOW_NOTIFICATION_SNOOZE
- = Settings.Secure.getUriFor(Settings.Secure.SHOW_NOTIFICATION_SNOOZE);
+ = Secure.getUriFor(Secure.SHOW_NOTIFICATION_SNOOZE);
SettingsObserver(Handler handler) {
super(handler);
@@ -2079,27 +2129,31 @@
void observe() {
ContentResolver resolver = getContext().getContentResolver();
resolver.registerContentObserver(NOTIFICATION_BADGING_URI,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(NOTIFICATION_RATE_LIMIT_URI,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(NOTIFICATION_HISTORY_ENABLED,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(NOTIFICATION_SHOW_MEDIA_ON_QUICK_SETTINGS_URI,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(LOCK_SCREEN_SHOW_NOTIFICATIONS,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(SHOW_NOTIFICATION_SNOOZE,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
update(null);
}
+ void destroy() {
+ getContext().getContentResolver().unregisterContentObserver(this);
+ }
+
@Override public void onChange(boolean selfChange, Uri uri, int userId) {
update(uri);
}
@@ -2131,7 +2185,7 @@
mPreferencesHelper.updateLockScreenShowNotifications();
}
if (SHOW_NOTIFICATION_SNOOZE.equals(uri)) {
- final boolean snoozeEnabled = Settings.Secure.getIntForUser(resolver,
+ final boolean snoozeEnabled = Secure.getIntForUser(resolver,
Secure.SHOW_NOTIFICATION_SNOOZE, 0, UserHandle.USER_CURRENT)
!= 0;
if (!snoozeEnabled) {
@@ -2144,8 +2198,8 @@
ContentResolver resolver = getContext().getContentResolver();
if (uri == null || NOTIFICATION_HISTORY_ENABLED.equals(uri)) {
mArchive.updateHistoryEnabled(userId,
- Settings.Secure.getIntForUser(resolver,
- Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0,
+ Secure.getIntForUser(resolver,
+ Secure.NOTIFICATION_HISTORY_ENABLED, 0,
userId) == 1);
// note: this setting is also handled in NotificationHistoryManager
}
@@ -2229,6 +2283,11 @@
}
@VisibleForTesting
+ void setLockPatternUtils(LockPatternUtils lockUtils) {
+ mLockUtils = lockUtils;
+ }
+
+ @VisibleForTesting
ShortcutHelper getShortcutHelper() {
return mShortcutHelper;
}
@@ -2400,7 +2459,7 @@
getContext().sendBroadcastAsUser(
new Intent(ACTION_INTERRUPTION_FILTER_CHANGED_INTERNAL)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT),
- UserHandle.ALL, permission.MANAGE_NOTIFICATIONS);
+ UserHandle.ALL, android.Manifest.permission.MANAGE_NOTIFICATIONS);
synchronized (mNotificationLock) {
updateInterruptionFilterLocked();
}
@@ -2455,16 +2514,16 @@
mNotificationChannelLogger,
mAppOps,
mUserProfiles,
- mShowReviewPermissionsNotification);
- mRankingHelper = new RankingHelper(getContext(),
- mRankingHandler,
- mPreferencesHelper,
- mZenModeHelper,
- mUsageStats,
- extractorNames);
+ mShowReviewPermissionsNotification,
+ Clock.systemUTC());
+ mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper,
+ mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat);
mSnoozeHelper = snoozeHelper;
mGroupHelper = groupHelper;
mHistoryManager = historyManager;
+ if (Flags.allNotifsNeedTtl()) {
+ mTtlHelper = new TimeToLiveHelper(mNotificationManagerPrivate, getContext());
+ }
// This is a ManagedServices object that keeps track of the listeners.
mListeners = notificationListeners;
@@ -2551,10 +2610,12 @@
getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, sdFilter, null,
null);
- IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT);
- timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
- getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter,
- Context.RECEIVER_EXPORTED_UNAUDITED);
+ if (!Flags.allNotifsNeedTtl()) {
+ IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT);
+ timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
+ getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
+ }
IntentFilter settingsRestoredFilter = new IntentFilter(Intent.ACTION_SETTING_RESTORED);
getContext().registerReceiver(mRestoreReceiver, settingsRestoredFilter);
@@ -2567,15 +2628,16 @@
ReviewNotificationPermissionsReceiver.getFilter(),
Context.RECEIVER_NOT_EXPORTED);
- mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null,
- new AppOpsManager.OnOpChangedInternalListener() {
- @Override
- public void onOpChanged(@NonNull String op, @NonNull String packageName,
- int userId) {
- mHandler.post(
- () -> handleNotificationPermissionChange(packageName, userId));
- }
- });
+ mAppOpsListener = new AppOpsManager.OnOpChangedInternalListener() {
+ @Override
+ public void onOpChanged(@NonNull String op, @NonNull String packageName,
+ int userId) {
+ mHandler.post(
+ () -> handleNotificationPermissionChange(packageName, userId));
+ }
+ };
+
+ mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null, mAppOpsListener);
}
/**
@@ -2584,10 +2646,26 @@
public void onDestroy() {
getContext().unregisterReceiver(mIntentReceiver);
getContext().unregisterReceiver(mPackageIntentReceiver);
- getContext().unregisterReceiver(mNotificationTimeoutReceiver);
+ if (Flags.allNotifsNeedTtl()) {
+ mTtlHelper.destroy();
+ } else {
+ getContext().unregisterReceiver(mNotificationTimeoutReceiver);
+ }
getContext().unregisterReceiver(mRestoreReceiver);
getContext().unregisterReceiver(mLocaleChangeReceiver);
+ mSettingsObserver.destroy();
+ mRoleObserver.destroy();
+ if (mShortcutHelper != null) {
+ mShortcutHelper.destroy();
+ }
+ mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES);
+ mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES);
+ mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES);
+ mStatsManager.clearPullAtomCallback(DND_MODE_RULE);
+ mAppOps.stopWatchingMode(mAppOpsListener);
+ mAlarmManager.cancelAll();
+
if (mDeviceConfigChangedListener != null) {
DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener);
}
@@ -2842,7 +2920,10 @@
bubbsExtractor.setShortcutHelper(mShortcutHelper);
}
registerNotificationPreferencesPullers();
- new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker);
+ if (mLockUtils == null) {
+ mLockUtils = new LockPatternUtils(getContext());
+ }
+ mLockUtils.registerStrongAuthTracker(mStrongAuthTracker);
mAttentionHelper.onSystemReady();
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
// This observer will force an update when observe is called, causing us to
@@ -2861,6 +2942,15 @@
mZenModeHelper.setDeviceEffectsApplier(
new DefaultDeviceEffectsApplier(getContext()));
}
+ List<ModuleInfo> moduleInfoList =
+ mPackageManagerClient.getInstalledModules(
+ PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
+ // Cache adservices module info
+ for (ModuleInfo mi : moduleInfoList) {
+ if (Objects.equals(mi.getApexModuleName(), ADSERVICES_MODULE_PKG_NAME)) {
+ mAdservicesModuleInfo = mi;
+ }
+ }
} else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
} else if (phase == SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY) {
@@ -2952,7 +3042,7 @@
void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel,
boolean fromListener) {
- if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
+ if (channel.getImportance() == IMPORTANCE_NONE) {
// cancel
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0,
UserHandle.getUserId(uid), REASON_CHANNEL_BANNED
@@ -3217,14 +3307,14 @@
| SUPPRESSED_EFFECT_SCREEN_OFF);
// set the deprecated effects according to the new more specific effects
- if ((newSuppressedVisualEffects & Policy.SUPPRESSED_EFFECT_PEEK) != 0) {
+ if ((newSuppressedVisualEffects & SUPPRESSED_EFFECT_PEEK) != 0) {
newSuppressedVisualEffects |= SUPPRESSED_EFFECT_SCREEN_ON;
}
- if ((newSuppressedVisualEffects & Policy.SUPPRESSED_EFFECT_LIGHTS) != 0
+ if ((newSuppressedVisualEffects & SUPPRESSED_EFFECT_LIGHTS) != 0
&& (newSuppressedVisualEffects
- & Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) != 0
+ & SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) != 0
&& (newSuppressedVisualEffects
- & Policy.SUPPRESSED_EFFECT_AMBIENT) != 0) {
+ & SUPPRESSED_EFFECT_AMBIENT) != 0) {
newSuppressedVisualEffects |= SUPPRESSED_EFFECT_SCREEN_OFF;
}
} else {
@@ -3292,7 +3382,7 @@
if (n.extras != null) {
title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
if (title == null) {
- title = n.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
+ title = n.extras.getCharSequence(EXTRA_TITLE_BIG);
}
}
return title == null ? getContext().getResources().getString(
@@ -3311,9 +3401,9 @@
if (nb.getStyle() instanceof Notification.BigTextStyle) {
text = ((Notification.BigTextStyle) nb.getStyle()).getBigText();
- } else if (nb.getStyle() instanceof Notification.MessagingStyle) {
- Notification.MessagingStyle ms = (Notification.MessagingStyle) nb.getStyle();
- final List<Notification.MessagingStyle.Message> messages = ms.getMessages();
+ } else if (nb.getStyle() instanceof MessagingStyle) {
+ MessagingStyle ms = (MessagingStyle) nb.getStyle();
+ final List<MessagingStyle.Message> messages = ms.getMessages();
if (messages != null && messages.size() > 0) {
text = messages.get(messages.size() - 1).getText();
}
@@ -3364,7 +3454,7 @@
}
private int getRealUserId(int userId) {
- return userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId;
+ return userId == USER_ALL ? USER_SYSTEM : userId;
}
private ToastRecord getToastRecord(int uid, int pid, String packageName, boolean isSystemToast,
@@ -4096,8 +4186,8 @@
public void createConversationNotificationChannelForPackage(String pkg, int uid,
NotificationChannel parentChannel, String conversationId) {
enforceSystemOrSystemUI("only system can call this");
- Preconditions.checkNotNull(parentChannel);
- Preconditions.checkNotNull(conversationId);
+ checkNotNull(parentChannel);
+ checkNotNull(conversationId);
String parentId = parentChannel.getId();
NotificationChannel conversationChannel = parentChannel;
conversationChannel.setId(String.format(
@@ -4542,7 +4632,7 @@
int uid = Binder.getCallingUid();
ArrayList<Integer> currentUsers = new ArrayList<>();
- currentUsers.add(UserHandle.USER_ALL);
+ currentUsers.add(USER_ALL);
Binder.withCleanCallingIdentity(() -> {
for (int user : mUm.getProfileIds(ActivityManager.getCurrentUser(), false)) {
currentUsers.add(user);
@@ -4798,7 +4888,7 @@
* Register a listener binder directly with the notification manager.
*
* Only works with system callers. Apps should extend
- * {@link android.service.notification.NotificationListenerService}.
+ * {@link NotificationListenerService}.
*/
@Override
public void registerListener(final INotificationListener listener,
@@ -4855,7 +4945,7 @@
NotificationRecord r = mNotificationsByKey.get(keys[i]);
if (r == null) continue;
final int userId = r.getSbn().getUserId();
- if (userId != info.userid && userId != UserHandle.USER_ALL &&
+ if (userId != info.userid && userId != USER_ALL &&
!mUserProfiles.isCurrentProfile(userId)) {
continue;
}
@@ -4972,7 +5062,7 @@
NotificationRecord r = mNotificationsByKey.get(keys[i]);
if (r == null) continue;
final int userId = r.getSbn().getUserId();
- if (userId != info.userid && userId != UserHandle.USER_ALL
+ if (userId != info.userid && userId != USER_ALL
&& !mUserProfiles.isCurrentProfile(userId)) {
continue;
}
@@ -5639,7 +5729,7 @@
}
private void enforcePolicyAccess(int uid, String method) {
- if (PackageManager.PERMISSION_GRANTED == getContext().checkCallingPermission(
+ if (PERMISSION_GRANTED == getContext().checkCallingPermission(
android.Manifest.permission.MANAGE_NOTIFICATIONS)) {
return;
}
@@ -5670,7 +5760,7 @@
}
private void enforcePolicyAccess(String pkg, String method) {
- if (PackageManager.PERMISSION_GRANTED == getContext().checkCallingPermission(
+ if (PERMISSION_GRANTED == getContext().checkCallingPermission(
android.Manifest.permission.MANAGE_NOTIFICATIONS)) {
return;
}
@@ -5691,7 +5781,7 @@
try {
uid = getContext().getPackageManager().getPackageUidAsUser(pkg,
UserHandle.getCallingUserId());
- if (PackageManager.PERMISSION_GRANTED == checkComponentPermission(
+ if (PERMISSION_GRANTED == checkComponentPermission(
android.Manifest.permission.MANAGE_NOTIFICATIONS, uid,
-1, true)) {
return true;
@@ -5886,7 +5976,7 @@
/**
* Sets the notification policy. Apps that target API levels below
- * {@link android.os.Build.VERSION_CODES#P} cannot change user-designated values to
+ * {@link Build.VERSION_CODES#P} cannot change user-designated values to
* allow or disallow {@link Policy#PRIORITY_CATEGORY_ALARMS},
* {@link Policy#PRIORITY_CATEGORY_SYSTEM} and
* {@link Policy#PRIORITY_CATEGORY_MEDIA} from bypassing dnd
@@ -6254,7 +6344,7 @@
@Override
public void setPrivateNotificationsAllowed(boolean allow) {
- if (PackageManager.PERMISSION_GRANTED
+ if (PERMISSION_GRANTED
!= getContext().checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
throw new SecurityException(
"Requires CONTROL_KEYGUARD_SECURE_NOTIFICATIONS permission");
@@ -6275,7 +6365,7 @@
@Override
public boolean getPrivateNotificationsAllowed() {
- if (PackageManager.PERMISSION_GRANTED
+ if (PERMISSION_GRANTED
!= getContext().checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
throw new SecurityException(
"Requires CONTROL_KEYGUARD_SECURE_NOTIFICATIONS permission");
@@ -6563,9 +6653,9 @@
// Add summary
final ApplicationInfo appInfo =
adjustedSbn.getNotification().extras.getParcelable(
- Notification.EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
+ EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
final Bundle extras = new Bundle();
- extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo);
+ extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, appInfo);
final String channelId = notificationRecord.getChannel().getId();
final Notification summaryNotification =
@@ -6859,6 +6949,11 @@
if (!zenOnly) {
pw.println("\n Usage Stats:");
mUsageStats.dump(pw, " ", filter);
+
+ if (Flags.allNotifsNeedTtl()) {
+ pw.println("\n TimeToLive alarms:");
+ mTtlHelper.dump(pw, " ");
+ }
}
}
}
@@ -7455,7 +7550,7 @@
throws NameNotFoundException, RemoteException {
final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
- (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
+ (userId == USER_ALL) ? USER_SYSTEM : userId);
Notification.addFieldsFromContext(ai, notification);
if (notification.isForegroundService() && fgsPolicy == NOT_FOREGROUND_SERVICE) {
@@ -7570,7 +7665,7 @@
// Enforce NO_CLEAR flag on MediaStyle notification for apps with targetSdk >= V.
if (CompatChanges.isChangeEnabled(ENFORCE_NO_CLEAR_FLAG_ON_MEDIA_NOTIFICATION,
notificationUid)) {
- notification.flags |= Notification.FLAG_NO_CLEAR;
+ notification.flags |= FLAG_NO_CLEAR;
}
}
@@ -7605,13 +7700,27 @@
private boolean canBeNonDismissible(ApplicationInfo ai, Notification notification) {
return notification.isMediaNotification() || isEnterpriseExempted(ai)
|| notification.isStyle(Notification.CallStyle.class)
- || isDefaultSearchSelectorPackage(ai.packageName);
+ || isDefaultSearchSelectorPackage(ai.packageName)
+ || isDefaultAdservicesPackage(ai.packageName);
}
private boolean isDefaultSearchSelectorPackage(String pkg) {
return Objects.equals(mDefaultSearchSelectorPkg, pkg);
}
+ private boolean isDefaultAdservicesPackage(String pkg) {
+ if (mAdservicesModuleInfo == null) {
+ return false;
+ }
+ // Handles the special package structure for mainline modules
+ for (String apkName : mAdservicesModuleInfo.getApkInApexPackageNames()) {
+ if (Objects.equals(apkName, pkg)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean isEnterpriseExempted(ApplicationInfo ai) {
// Check if the app is an organization admin app
// TODO(b/234609037): Replace with new DPM APIs to check if organization admin
@@ -7622,7 +7731,7 @@
// Check if an app has been given system exemption
return mSystemExemptFromDismissal && mAppOps.checkOpNoThrow(
AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid,
- ai.packageName) == AppOpsManager.MODE_ALLOWED;
+ ai.packageName) == MODE_ALLOWED;
}
private boolean checkUseFullScreenIntentPermission(@NonNull AttributionSource attributionSource,
@@ -7727,7 +7836,7 @@
// Enqueue will trigger resort & flag is updated that way.
r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
mHandler.post(
- new NotificationManagerService.EnqueueNotificationRunnable(
+ new EnqueueNotificationRunnable(
r.getUser().getIdentifier(), r, isAppForeground,
mPostNotificationTrackerFactory.newTracker(null)));
}
@@ -7749,7 +7858,7 @@
@VisibleForTesting
int resolveNotificationUid(String callingPkg, String targetPkg, int callingUid, int userId) {
- if (userId == UserHandle.USER_ALL) {
+ if (userId == USER_ALL) {
userId = USER_SYSTEM;
}
// posted from app A on behalf of app A
@@ -8008,7 +8117,7 @@
final String pkg = r.getSbn().getPackageName();
final int callingUid = r.getSbn().getUid();
return mPreferencesHelper.isGroupBlocked(pkg, callingUid, r.getChannel().getGroup())
- || r.getImportance() == NotificationManager.IMPORTANCE_NONE;
+ || r.getImportance() == IMPORTANCE_NONE;
}
protected class SnoozeNotificationRunnable implements Runnable {
@@ -8347,7 +8456,11 @@
}
mEnqueuedNotifications.add(r);
- scheduleTimeoutLocked(r);
+ if (Flags.allNotifsNeedTtl()) {
+ mTtlHelper.scheduleTimeoutLocked(r, SystemClock.elapsedRealtime());
+ } else {
+ scheduleTimeoutLocked(r);
+ }
final StatusBarNotification n = r.getSbn();
if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
@@ -8567,7 +8680,7 @@
Slog.e(TAG, "Not posting notification without small icon: " + notification);
if (old != null && !old.isCanceled) {
mListeners.notifyRemovedLocked(r,
- NotificationListenerService.REASON_ERROR, r.getStats());
+ REASON_ERROR, r.getStats());
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -8935,7 +9048,7 @@
try {
isExemptFromRateLimiting = mPackageManager.checkPermission(
android.Manifest.permission.UNLIMITED_TOASTS, pkg, userId)
- == PackageManager.PERMISSION_GRANTED;
+ == PERMISSION_GRANTED;
} catch (RemoteException e) {
Slog.e(TAG, "Failed to connect with package manager");
}
@@ -9460,7 +9573,11 @@
int rank, int count, boolean wasPosted, String listenerName,
@ElapsedRealtimeLong long cancellationElapsedTimeMs) {
final String canceledKey = r.getKey();
- cancelScheduledTimeoutLocked(r);
+ if (Flags.allNotifsNeedTtl()) {
+ mTtlHelper.cancelScheduledTimeoutLocked(r);
+ } else {
+ cancelScheduledTimeoutLocked(r);
+ }
// Record caller.
recordCallerLocked(r);
@@ -9637,7 +9754,7 @@
// Uri, not when removing an individual listener.
revokeUriPermission(permissionOwner, uri,
UserHandle.getUserId(oldRecord.getUid()),
- null, UserHandle.USER_ALL);
+ null, USER_ALL);
}
}
}
@@ -9735,9 +9852,9 @@
} else {
return
// looking for USER_ALL notifications? match everything
- userId == UserHandle.USER_ALL
+ userId == USER_ALL
// a notification sent to USER_ALL matches any query
- || r.getUserId() == UserHandle.USER_ALL
+ || r.getUserId() == USER_ALL
// an exact user match
|| r.getUserId() == userId;
}
@@ -9816,7 +9933,7 @@
continue;
}
// Don't remove notifications to all, if there's no package name specified
- if (nullPkgIndicatesUserSwitch && pkg == null && r.getUserId() == UserHandle.USER_ALL) {
+ if (nullPkgIndicatesUserSwitch && pkg == null && r.getUserId() == USER_ALL) {
continue;
}
if (!flagChecker.apply(r.getFlags())) {
@@ -10314,7 +10431,7 @@
return false;
}
- if (userId == UserHandle.USER_ALL) {
+ if (userId == USER_ALL) {
userId = USER_SYSTEM;
}
@@ -10537,7 +10654,7 @@
if (requiredPermission != null) {
try {
if (mPackageManager.checkPermission(requiredPermission, pkg, userId)
- != PackageManager.PERMISSION_GRANTED) {
+ != PERMISSION_GRANTED) {
canUseManagedServices = false;
}
} catch (RemoteException e) {
@@ -10663,7 +10780,7 @@
c.caption = "notification assistant";
c.serviceInterface = NotificationAssistantService.SERVICE_INTERFACE;
c.xmlTag = TAG_ENABLED_NOTIFICATION_ASSISTANTS;
- c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT;
+ c.secureSettingName = Secure.ENABLED_NOTIFICATION_ASSISTANT;
c.bindPermission = Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE;
c.settingsAction = Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS;
c.clientLabel = R.string.notification_ranker_binding_label;
@@ -11259,7 +11376,7 @@
c.caption = "notification listener";
c.serviceInterface = NotificationListenerService.SERVICE_INTERFACE;
c.xmlTag = TAG_ENABLED_NOTIFICATION_LISTENERS;
- c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
+ c.secureSettingName = Secure.ENABLED_NOTIFICATION_LISTENERS;
c.bindPermission = android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE;
c.settingsAction = Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
c.clientLabel = R.string.notification_listener_binding_label;
@@ -11667,7 +11784,7 @@
// Managed Services.
if (info.isSystemUi() && old != null && old.getNotification() != null
&& (old.getNotification().flags
- & Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
+ & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
listenerCalls.add(() -> notifyPosted(info, oldSbn, update));
break;
@@ -11696,8 +11813,8 @@
continue;
}
// Grant access before listener is notified
- final int targetUserId = (info.userid == UserHandle.USER_ALL)
- ? UserHandle.USER_SYSTEM : info.userid;
+ final int targetUserId = (info.userid == USER_ALL)
+ ? USER_SYSTEM : info.userid;
updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);
mPackageManagerInternal.grantImplicitAccess(
@@ -11846,8 +11963,8 @@
continue;
}
// Grant or revoke access synchronously
- final int targetUserId = (info.userid == UserHandle.USER_ALL)
- ? UserHandle.USER_SYSTEM : info.userid;
+ final int targetUserId = (info.userid == USER_ALL)
+ ? USER_SYSTEM : info.userid;
if (grant) {
// Grant permissions by passing arguments as if the notification is new.
updateUriPermissions(/* newRecord */ r, /* oldRecord */ null,
@@ -11919,7 +12036,7 @@
}
// Revoke access after all listeners have been updated
- mHandler.post(() -> updateUriPermissions(null, r, null, UserHandle.USER_SYSTEM));
+ mHandler.post(() -> updateUriPermissions(null, r, null, USER_SYSTEM));
}
/**
@@ -12079,7 +12196,7 @@
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
listener.onNotificationPosted(sbnHolder, rankingUpdate);
- } catch (android.os.DeadObjectException ex) {
+ } catch (DeadObjectException ex) {
Slog.wtf(TAG, "unable to notify listener (posted): " + info, ex);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (posted): " + info, ex);
@@ -12102,7 +12219,7 @@
reason = REASON_LISTENER_CANCEL;
}
listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
- } catch (android.os.DeadObjectException ex) {
+ } catch (DeadObjectException ex) {
Slog.wtf(TAG, "unable to notify listener (removed): " + info, ex);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (removed): " + info, ex);
@@ -12114,7 +12231,7 @@
final INotificationListener listener = (INotificationListener) info.service;
try {
listener.onNotificationRankingUpdate(rankingUpdate);
- } catch (android.os.DeadObjectException ex) {
+ } catch (DeadObjectException ex) {
Slog.wtf(TAG, "unable to notify listener (ranking update): " + info, ex);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (ranking update): " + info, ex);
@@ -12333,6 +12450,10 @@
mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL);
}
+ void destroy() {
+ mRm.removeOnRoleHoldersChangedListenerAsUser(this, UserHandle.ALL);
+ }
+
@VisibleForTesting
public boolean isApprovedPackageForRoleForUser(String role, String pkg, int userId) {
return mNonBlockableDefaultApps.get(role).get(userId).contains(pkg);
@@ -12621,7 +12742,7 @@
.setContentIntent(PendingIntent.getActivity(getContext(), 0, tapIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
.setStyle(new Notification.BigTextStyle())
- .setFlag(Notification.FLAG_NO_CLEAR, true)
+ .setFlag(FLAG_NO_CLEAR, true)
.setAutoCancel(true)
.addAction(remindMe)
.addAction(dismiss)
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 97d2620..c69bead 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -79,6 +79,7 @@
import java.io.PrintWriter;
import java.lang.reflect.Array;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -600,8 +601,7 @@
pw.println(prefix + "headsUpContentView="
+ formatRemoteViews(notification.headsUpContentView));
pw.println(prefix + String.format("color=0x%08x", notification.color));
- pw.println(prefix + "timeout="
- + TimeUtils.formatForLogging(notification.getTimeoutAfter()));
+ pw.println(prefix + "timeout=" + Duration.ofMillis(notification.getTimeoutAfter()));
if (notification.actions != null && notification.actions.length > 0) {
pw.println(prefix + "actions={");
final int N = notification.actions.length;
diff --git a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
index 24c1d59..f0358d1 100644
--- a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
@@ -17,6 +17,7 @@
package com.android.server.notification;
import android.content.Context;
+import com.android.internal.compat.IPlatformCompat;
/**
* Extracts signals that will be useful to the {@link NotificationComparator} and caches them
@@ -52,4 +53,6 @@
* DND.
*/
void setZenHelper(ZenModeHelper helper);
+
+ default void setCompatChangeLogger(IPlatformCompat platformCompat){};
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 50ca984..1f2ad07e 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -93,6 +93,8 @@
import java.io.IOException;
import java.io.PrintWriter;
+import java.time.Clock;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -113,6 +115,8 @@
private static final int XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION = 4;
@VisibleForTesting
static final int UNKNOWN_UID = UserHandle.USER_NULL;
+ // The amount of time pacakage preferences can exist without the app being installed.
+ private static final long PREF_GRACE_PERIOD_MS = Duration.ofDays(2).toMillis();
@VisibleForTesting
static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 5000;
@@ -149,6 +153,8 @@
private static final String ATT_USER_DEMOTED_INVALID_MSG_APP = "user_demote_msg_app";
private static final String ATT_SENT_VALID_BUBBLE = "sent_valid_bubble";
+ private static final String ATT_CREATION_TIME = "creation_time";
+
private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
@@ -208,11 +214,13 @@
private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
private final boolean mShowReviewPermissionsNotification;
+ Clock mClock;
+
public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
NotificationChannelLogger notificationChannelLogger,
AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles,
- boolean showReviewPermissionsNotification) {
+ boolean showReviewPermissionsNotification, Clock clock) {
mContext = context;
mZenModeHelper = zenHelper;
mRankingHandler = rankingHandler;
@@ -225,7 +233,7 @@
mShowReviewPermissionsNotification = showReviewPermissionsNotification;
mIsMediaNotificationFilteringEnabled = context.getResources()
.getBoolean(R.bool.config_quickSettingsShowMediaPlayer);
-
+ mClock = clock;
XML_VERSION = 4;
updateBadgingEnabled();
@@ -309,7 +317,7 @@
parser.getAttributeInt(null, ATT_PRIORITY, DEFAULT_PRIORITY),
parser.getAttributeInt(null, ATT_VISIBILITY, DEFAULT_VISIBILITY),
parser.getAttributeBoolean(null, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
- bubblePref);
+ bubblePref, parser.getAttributeLong(null, ATT_CREATION_TIME, mClock.millis()));
r.bubblePreference = bubblePref;
r.priority = parser.getAttributeInt(null, ATT_PRIORITY, DEFAULT_PRIORITY);
r.visibility = parser.getAttributeInt(null, ATT_VISIBILITY, DEFAULT_VISIBILITY);
@@ -463,12 +471,12 @@
// TODO (b/194833441): use permissionhelper instead of DEFAULT_IMPORTANCE
return getOrCreatePackagePreferencesLocked(pkg, UserHandle.getUserId(uid), uid,
DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
- DEFAULT_BUBBLE_PREFERENCE);
+ DEFAULT_BUBBLE_PREFERENCE, mClock.millis());
}
private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
@UserIdInt int userId, int uid, int importance, int priority, int visibility,
- boolean showBadge, int bubblePreference) {
+ boolean showBadge, int bubblePreference, long creationTime) {
final String key = packagePreferencesKey(pkg, uid);
PackagePreferences
r = (uid == UNKNOWN_UID)
@@ -483,6 +491,11 @@
r.visibility = visibility;
r.showBadge = showBadge;
r.bubblePreference = bubblePreference;
+ if (Flags.persistIncompleteRestoreData()) {
+ if (r.uid == UNKNOWN_UID) {
+ r.creationTime = creationTime;
+ }
+ }
try {
createDefaultChannelIfNeededLocked(r);
@@ -496,6 +509,12 @@
mPackagePreferences.put(key, r);
}
}
+ if (r.uid == UNKNOWN_UID) {
+ if (Flags.persistIncompleteRestoreData()
+ && PREF_GRACE_PERIOD_MS < (mClock.millis() - r.creationTime)) {
+ mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, userId));
+ }
+ }
return r;
}
@@ -590,70 +609,16 @@
if (forBackup && UserHandle.getUserId(r.uid) != userId) {
continue;
}
- out.startTag(null, TAG_PACKAGE);
- out.attribute(null, ATT_NAME, r.pkg);
- if (!notifPermissions.isEmpty()) {
- Pair<Integer, String> app = new Pair(r.uid, r.pkg);
- final Pair<Boolean, Boolean> permission = notifPermissions.get(app);
- out.attributeInt(null, ATT_IMPORTANCE,
- permission != null && permission.first ? IMPORTANCE_DEFAULT
- : IMPORTANCE_NONE);
- notifPermissions.remove(app);
- } else {
- if (r.importance != DEFAULT_IMPORTANCE) {
- out.attributeInt(null, ATT_IMPORTANCE, r.importance);
- }
+ writePackageXml(r, out, notifPermissions, forBackup);
+ }
+ }
+ if (Flags.persistIncompleteRestoreData() && !forBackup) {
+ synchronized (mRestoredWithoutUids) {
+ final int N = mRestoredWithoutUids.size();
+ for (int i = 0; i < N; i++) {
+ final PackagePreferences r = mRestoredWithoutUids.valueAt(i);
+ writePackageXml(r, out, notifPermissions, false);
}
- if (r.priority != DEFAULT_PRIORITY) {
- out.attributeInt(null, ATT_PRIORITY, r.priority);
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- out.attributeInt(null, ATT_VISIBILITY, r.visibility);
- }
- if (r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE) {
- out.attributeInt(null, ATT_ALLOW_BUBBLE, r.bubblePreference);
- }
- out.attributeBoolean(null, ATT_SHOW_BADGE, r.showBadge);
- out.attributeInt(null, ATT_APP_USER_LOCKED_FIELDS,
- r.lockedAppFields);
- out.attributeBoolean(null, ATT_SENT_INVALID_MESSAGE,
- r.hasSentInvalidMessage);
- out.attributeBoolean(null, ATT_SENT_VALID_MESSAGE,
- r.hasSentValidMessage);
- out.attributeBoolean(null, ATT_USER_DEMOTED_INVALID_MSG_APP,
- r.userDemotedMsgApp);
- out.attributeBoolean(null, ATT_SENT_VALID_BUBBLE, r.hasSentValidBubble);
-
- if (!forBackup) {
- out.attributeInt(null, ATT_UID, r.uid);
- }
-
- if (r.delegate != null) {
- out.startTag(null, TAG_DELEGATE);
-
- out.attribute(null, ATT_NAME, r.delegate.mPkg);
- out.attributeInt(null, ATT_UID, r.delegate.mUid);
- if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
- out.attributeBoolean(null, ATT_ENABLED, r.delegate.mEnabled);
- }
- out.endTag(null, TAG_DELEGATE);
- }
-
- for (NotificationChannelGroup group : r.groups.values()) {
- group.writeXml(out);
- }
-
- for (NotificationChannel channel : r.channels.values()) {
- if (forBackup) {
- if (!channel.isDeleted()) {
- channel.writeXmlForBackup(out, mContext);
- }
- } else {
- channel.writeXml(out);
- }
- }
-
- out.endTag(null, TAG_PACKAGE);
}
}
// Some apps have permissions set but don't have expanded notification settings
@@ -669,6 +634,80 @@
out.endTag(null, TAG_RANKING);
}
+ public void writePackageXml(PackagePreferences r, TypedXmlSerializer out,
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions,
+ boolean forBackup) throws
+ IOException {
+ out.startTag(null, TAG_PACKAGE);
+ out.attribute(null, ATT_NAME, r.pkg);
+ if (!notifPermissions.isEmpty()) {
+ Pair<Integer, String> app = new Pair(r.uid, r.pkg);
+ final Pair<Boolean, Boolean> permission = notifPermissions.get(app);
+ out.attributeInt(null, ATT_IMPORTANCE,
+ permission != null && permission.first ? IMPORTANCE_DEFAULT
+ : IMPORTANCE_NONE);
+ notifPermissions.remove(app);
+ } else {
+ if (r.importance != DEFAULT_IMPORTANCE) {
+ out.attributeInt(null, ATT_IMPORTANCE, r.importance);
+ }
+ }
+ if (r.priority != DEFAULT_PRIORITY) {
+ out.attributeInt(null, ATT_PRIORITY, r.priority);
+ }
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ out.attributeInt(null, ATT_VISIBILITY, r.visibility);
+ }
+ if (r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE) {
+ out.attributeInt(null, ATT_ALLOW_BUBBLE, r.bubblePreference);
+ }
+ out.attributeBoolean(null, ATT_SHOW_BADGE, r.showBadge);
+ out.attributeInt(null, ATT_APP_USER_LOCKED_FIELDS,
+ r.lockedAppFields);
+ out.attributeBoolean(null, ATT_SENT_INVALID_MESSAGE,
+ r.hasSentInvalidMessage);
+ out.attributeBoolean(null, ATT_SENT_VALID_MESSAGE,
+ r.hasSentValidMessage);
+ out.attributeBoolean(null, ATT_USER_DEMOTED_INVALID_MSG_APP,
+ r.userDemotedMsgApp);
+ out.attributeBoolean(null, ATT_SENT_VALID_BUBBLE, r.hasSentValidBubble);
+
+ if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) {
+ out.attributeLong(null, ATT_CREATION_TIME, r.creationTime);
+ }
+
+ if (!forBackup) {
+ out.attributeInt(null, ATT_UID, r.uid);
+ }
+
+ if (r.delegate != null) {
+ out.startTag(null, TAG_DELEGATE);
+
+ out.attribute(null, ATT_NAME, r.delegate.mPkg);
+ out.attributeInt(null, ATT_UID, r.delegate.mUid);
+ if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
+ out.attributeBoolean(null, ATT_ENABLED, r.delegate.mEnabled);
+ }
+ out.endTag(null, TAG_DELEGATE);
+ }
+
+ for (NotificationChannelGroup group : r.groups.values()) {
+ group.writeXml(out);
+ }
+
+ for (NotificationChannel channel : r.channels.values()) {
+ if (forBackup) {
+ if (!channel.isDeleted()) {
+ channel.writeXmlForBackup(out, mContext);
+ }
+ } else {
+ channel.writeXml(out);
+ }
+ }
+
+ out.endTag(null, TAG_PACKAGE);
+ }
+
/**
* Sets whether bubbles are allowed.
*
@@ -1387,8 +1426,7 @@
public void updateFixedImportance(List<UserInfo> users) {
for (UserInfo user : users) {
List<PackageInfo> packages = mPm.getInstalledPackagesAsUser(
- PackageManager.PackageInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY),
- user.getUserHandle().getIdentifier());
+ 0, user.getUserHandle().getIdentifier());
for (PackageInfo pi : packages) {
boolean fixed = mPermissionHelper.isPermissionFixed(
pi.packageName, user.getUserHandle().getIdentifier());
@@ -2907,6 +2945,7 @@
boolean hasSentValidBubble = false;
boolean migrateToPm = false;
+ long creationTime;
Delegate delegate = null;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 68e0eaa..7756801 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -15,6 +15,9 @@
*/
package com.android.server.notification;
+import static android.app.Flags.restrictAudioAttributesAlarm;
+import static android.app.Flags.restrictAudioAttributesCall;
+import static android.app.Flags.restrictAudioAttributesMedia;
import static android.app.Flags.sortSectionByTime;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.text.TextUtils.formatSimple;
@@ -27,6 +30,7 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.compat.IPlatformCompat;
import com.android.tools.r8.keepanno.annotations.KeepItemKind;
import com.android.tools.r8.keepanno.annotations.KeepTarget;
import com.android.tools.r8.keepanno.annotations.UsesReflection;
@@ -56,7 +60,8 @@
methodName = "<init>")
})
public RankingHelper(Context context, RankingHandler rankingHandler, RankingConfig config,
- ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames) {
+ ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames,
+ IPlatformCompat platformCompat) {
mContext = context;
mRankingHandler = rankingHandler;
if (sortSectionByTime()) {
@@ -75,6 +80,10 @@
extractor.initialize(mContext, usageStats);
extractor.setConfig(config);
extractor.setZenHelper(zenHelper);
+ if (restrictAudioAttributesAlarm() || restrictAudioAttributesMedia()
+ || restrictAudioAttributesCall()) {
+ extractor.setCompatChangeLogger(platformCompat);
+ }
mSignalExtractors[i] = extractor;
} catch (ClassNotFoundException e) {
Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e);
diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java
index fc106b8..86dcecf 100644
--- a/services/core/java/com/android/server/notification/ShortcutHelper.java
+++ b/services/core/java/com/android/server/notification/ShortcutHelper.java
@@ -287,4 +287,11 @@
}
}
}
+
+ void destroy() {
+ if (mLauncherAppsCallbackRegistered) {
+ mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
+ mLauncherAppsCallbackRegistered = false;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/notification/TimeToLiveHelper.java b/services/core/java/com/android/server/notification/TimeToLiveHelper.java
new file mode 100644
index 0000000..2facab7
--- /dev/null
+++ b/services/core/java/com/android/server/notification/TimeToLiveHelper.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
+import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.util.Pair;
+import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.PackageManagerService;
+
+import java.io.PrintWriter;
+import java.util.TreeSet;
+
+/**
+ * Handles canceling notifications when their time to live expires
+ */
+@FlaggedApi(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+public class TimeToLiveHelper {
+ private static final String TAG = TimeToLiveHelper.class.getSimpleName();
+ private static final String ACTION = "com.android.server.notification.TimeToLiveHelper";
+
+ private static final int REQUEST_CODE_TIMEOUT = 1;
+ private static final String SCHEME_TIMEOUT = "timeout";
+ static final String EXTRA_KEY = "key";
+ private final Context mContext;
+ private final NotificationManagerPrivate mNm;
+ private final AlarmManager mAm;
+
+ @VisibleForTesting
+ final TreeSet<Pair<Long, String>> mKeys;
+
+ public TimeToLiveHelper(NotificationManagerPrivate nm, Context context) {
+ mContext = context;
+ mNm = nm;
+ mAm = context.getSystemService(AlarmManager.class);
+ mKeys = new TreeSet<>((left, right) -> Long.compare(left.first, right.first));
+
+ IntentFilter timeoutFilter = new IntentFilter(ACTION);
+ timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
+ mContext.registerReceiver(mNotificationTimeoutReceiver, timeoutFilter,
+ Context.RECEIVER_NOT_EXPORTED);
+ }
+
+ void destroy() {
+ mContext.unregisterReceiver(mNotificationTimeoutReceiver);
+ }
+
+ void dump(PrintWriter pw, String indent) {
+ pw.println(indent + "mKeys " + mKeys);
+ }
+
+ private @NonNull PendingIntent getAlarmPendingIntent(String nextKey, int flags) {
+ flags |= PendingIntent.FLAG_IMMUTABLE;
+ return PendingIntent.getBroadcast(mContext,
+ REQUEST_CODE_TIMEOUT,
+ new Intent(ACTION)
+ .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
+ .setData(new Uri.Builder()
+ .scheme(SCHEME_TIMEOUT)
+ .appendPath(nextKey)
+ .build())
+ .putExtra(EXTRA_KEY, nextKey)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
+ flags);
+ }
+
+ @VisibleForTesting
+ void scheduleTimeoutLocked(NotificationRecord record, long currentTime) {
+ removeMatchingEntry(record.getKey());
+
+ final long timeoutAfter = currentTime + record.getNotification().getTimeoutAfter();
+ if (record.getNotification().getTimeoutAfter() > 0) {
+ final Long currentEarliestTime = mKeys.isEmpty() ? null : mKeys.first().first;
+
+ // Maybe replace alarm with an earlier one
+ if (currentEarliestTime == null || timeoutAfter < currentEarliestTime) {
+ if (currentEarliestTime != null) {
+ cancelFirstAlarm();
+ }
+ mKeys.add(Pair.create(timeoutAfter, record.getKey()));
+ maybeScheduleFirstAlarm();
+ } else {
+ mKeys.add(Pair.create(timeoutAfter, record.getKey()));
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void cancelScheduledTimeoutLocked(NotificationRecord record) {
+ removeMatchingEntry(record.getKey());
+ }
+
+ private void removeMatchingEntry(String key) {
+ if (!mKeys.isEmpty() && key.equals(mKeys.first().second)) {
+ // cancel the first alarm, remove the first entry, maybe schedule the alarm for the new
+ // first entry
+ cancelFirstAlarm();
+ mKeys.remove(mKeys.first());
+ maybeScheduleFirstAlarm();
+ } else {
+ // just remove the entry
+ Pair<Long, String> trackedPair = null;
+ for (Pair<Long, String> entry : mKeys) {
+ if (key.equals(entry.second)) {
+ trackedPair = entry;
+ break;
+ }
+ }
+ if (trackedPair != null) {
+ mKeys.remove(trackedPair);
+ }
+ }
+ }
+
+ private void cancelFirstAlarm() {
+ final PendingIntent pi = getAlarmPendingIntent(mKeys.first().second, FLAG_CANCEL_CURRENT);
+ mAm.cancel(pi);
+ }
+
+ private void maybeScheduleFirstAlarm() {
+ if (!mKeys.isEmpty()) {
+ final PendingIntent piNewFirst = getAlarmPendingIntent(mKeys.first().second,
+ FLAG_UPDATE_CURRENT);
+ mAm.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mKeys.first().first, piNewFirst);
+ }
+ }
+
+ @VisibleForTesting
+ final BroadcastReceiver mNotificationTimeoutReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+ if (ACTION.equals(action)) {
+ Pair<Long, String> earliest = mKeys.first();
+ String key = intent.getStringExtra(EXTRA_KEY);
+ if (!earliest.second.equals(key)) {
+ Slog.wtf(TAG, "Alarm triggered but wasn't the earliest we were tracking");
+ }
+ removeMatchingEntry(key);
+ mNm.timeoutNotification(earliest.second);
+ }
+ }
+ };
+}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 077ed5a..28598711 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -95,3 +95,9 @@
bug: "331967355"
}
+flag {
+ name: "persist_incomplete_restore_data"
+ namespace: "systemui"
+ description: "Stores restore data for not-yet-installed pkgs for 48 hours"
+ bug: "334999659"
+}
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index 5ebcca8..2c14532 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -275,10 +275,7 @@
if (mFrpEnforced) {
automaticallyDeactivateFrpIfPossible();
setOemUnlockEnabledProperty(doGetOemUnlockEnabled());
- // Set the SECURE_FRP_MODE flag, for backward compatibility with clients who use it.
- // They should switch to calling #isFrpActive().
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.SECURE_FRP_MODE, mFrpActive ? 1 : 0);
+ setOldSettingForBackworkCompatibility(mFrpActive);
} else {
formatIfOemUnlockEnabled();
}
@@ -292,6 +289,13 @@
mInitDoneSignal.countDown();
}
+ private void setOldSettingForBackworkCompatibility(boolean isActive) {
+ // Set the SECURE_FRP_MODE flag, for backward compatibility with clients who use it.
+ // They should switch to calling #isFrpActive().
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.SECURE_FRP_MODE, isActive ? 1 : 0);
+ }
+
private void setOemUnlockEnabledProperty(boolean oemUnlockEnabled) {
setProperty(OEM_UNLOCK_PROP, oemUnlockEnabled ? "1" : "0");
}
@@ -628,6 +632,7 @@
Slog.w(TAG, "Upgrading from Android 14 or lower, defaulting FRP secret");
writeFrpMagicAndDefaultSecret();
mFrpActive = false;
+ setOldSettingForBackworkCompatibility(mFrpActive);
return true;
}
@@ -699,6 +704,7 @@
void activateFrp() {
synchronized (mLock) {
mFrpActive = true;
+ setOldSettingForBackworkCompatibility(mFrpActive);
}
}
@@ -740,6 +746,7 @@
if (MessageDigest.isEqual(secret, partitionSecret)) {
mFrpActive = false;
Slog.i(TAG, "FRP secret matched, FRP deactivated.");
+ setOldSettingForBackworkCompatibility(mFrpActive);
return true;
} else {
Slog.e(TAG,
@@ -1315,6 +1322,7 @@
public boolean deactivateFactoryResetProtectionWithoutSecret() {
synchronized (mLock) {
mFrpActive = false;
+ setOldSettingForBackworkCompatibility(/* isActive */ mFrpActive);
}
return true;
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 2a3b939..d41727f 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -48,6 +48,7 @@
import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
+import static com.android.server.pm.PackageManagerException.INTERNAL_ERROR_ARCHIVE_NO_INSTALLER_TITLE;
import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME;
import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
@@ -1050,6 +1051,20 @@
request.setError("Scanning Failed.", e);
return;
}
+ if (request.isArchived()) {
+ final SparseArray<String> responsibleInstallerTitles =
+ PackageArchiver.getResponsibleInstallerTitles(mContext,
+ mPm.snapshotComputer(), request.getInstallSource(),
+ request.getUserId(), mPm.mUserManager.getUserIds());
+ if (responsibleInstallerTitles == null
+ || responsibleInstallerTitles.size() == 0) {
+ request.setError(PackageManagerException.ofInternalError(
+ "Failed to obtain the responsible installer info",
+ INTERNAL_ERROR_ARCHIVE_NO_INSTALLER_TITLE));
+ return;
+ }
+ request.setResponsibleInstallerTitles(responsibleInstallerTitles);
+ }
}
List<ReconciledPackage> reconciledPackages;
@@ -2226,6 +2241,7 @@
// to figure out which users were changed.
mPm.markPackageAsArchivedIfNeeded(ps,
installRequest.getArchivedPackage(),
+ installRequest.getResponsibleInstallerTitles(),
installRequest.getNewUsers());
mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers());
mPm.updateInstantAppInstallerLocked(packageName);
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 4dcee04..6d38517 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -49,6 +49,7 @@
import android.util.ArrayMap;
import android.util.ExceptionUtils;
import android.util.Slog;
+import android.util.SparseArray;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
@@ -130,6 +131,12 @@
@Nullable
private String mApexModuleName;
+ /**
+ * The title of the responsible installer for the archive behavior used
+ */
+ @Nullable
+ private SparseArray<String> mResponsibleInstallerTitles;
+
@Nullable
private ScanResult mScanResult;
@@ -418,6 +425,12 @@
public String getApexModuleName() {
return mApexModuleName;
}
+
+ @Nullable
+ public SparseArray<String> getResponsibleInstallerTitles() {
+ return mResponsibleInstallerTitles;
+ }
+
public boolean isRollback() {
return mInstallArgs != null
&& mInstallArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
@@ -756,6 +769,11 @@
mApexModuleName = apexModuleName;
}
+ public void setResponsibleInstallerTitles(
+ @NonNull SparseArray<String> responsibleInstallerTitles) {
+ mResponsibleInstallerTitles = responsibleInstallerTitles;
+ }
+
public void setPkg(AndroidPackage pkg) {
mPkg = pkg;
}
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index fda4dc6..9bdf613 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -85,13 +85,13 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.SELinux;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.ExceptionUtils;
import android.util.Pair;
import android.util.Slog;
+import android.util.SparseArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -463,8 +463,10 @@
final CompletableFuture<Void> archiveStateStored = new CompletableFuture<>();
mPm.mHandler.post(() -> {
try {
+ final String installerTitle = getResponsibleInstallerTitle(
+ mContext, installerInfo, responsibleInstallerPackage, userId);
var archiveState = createArchiveStateInternal(packageName, userId, mainActivities,
- installerInfo.loadLabel(mContext.getPackageManager()).toString());
+ installerTitle);
storeArchiveState(packageName, archiveState, userId);
archiveStateStored.complete(null);
} catch (IOException | PackageManager.NameNotFoundException e) {
@@ -476,7 +478,7 @@
@Nullable
ArchiveState createArchiveState(@NonNull ArchivedPackageParcel archivedPackage,
- int userId, String installerPackage) {
+ int userId, String installerPackage, String responsibleInstallerTitle) {
ApplicationInfo installerInfo = mPm.snapshotComputer().getApplicationInfo(
installerPackage, /* flags= */ 0, userId);
if (installerInfo == null) {
@@ -484,6 +486,11 @@
Slog.e(TAG, "Couldn't find installer " + installerPackage);
return null;
}
+ if (responsibleInstallerTitle == null) {
+ Slog.e(TAG, "Couldn't get the title of the installer");
+ return null;
+ }
+
final int iconSize = mContext.getSystemService(
ActivityManager.class).getLauncherLargeIconSize();
@@ -508,8 +515,7 @@
archiveActivityInfos.add(activityInfo);
}
- return new ArchiveState(archiveActivityInfos,
- installerInfo.loadLabel(mContext.getPackageManager()).toString());
+ return new ArchiveState(archiveActivityInfos, responsibleInstallerTitle);
} catch (IOException e) {
Slog.e(TAG, "Failed to create archive state", e);
return null;
@@ -1106,10 +1112,61 @@
return DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS;
}
+ private static String getResponsibleInstallerPackage(InstallSource installSource) {
+ return TextUtils.isEmpty(installSource.mUpdateOwnerPackageName)
+ ? installSource.mInstallerPackageName
+ : installSource.mUpdateOwnerPackageName;
+ }
+
+ private static String getResponsibleInstallerTitle(Context context, ApplicationInfo appInfo,
+ String responsibleInstallerPackage, int userId)
+ throws PackageManager.NameNotFoundException {
+ final Context userContext = context.createPackageContextAsUser(
+ responsibleInstallerPackage, /* flags= */ 0, new UserHandle(userId));
+ return appInfo.loadLabel(userContext.getPackageManager()).toString();
+ }
+
static String getResponsibleInstallerPackage(PackageStateInternal ps) {
- return TextUtils.isEmpty(ps.getInstallSource().mUpdateOwnerPackageName)
- ? ps.getInstallSource().mInstallerPackageName
- : ps.getInstallSource().mUpdateOwnerPackageName;
+ return getResponsibleInstallerPackage(ps.getInstallSource());
+ }
+
+ @Nullable
+ static SparseArray<String> getResponsibleInstallerTitles(Context context, Computer snapshot,
+ InstallSource installSource, int requestUserId, int[] allUserIds) {
+ final String responsibleInstallerPackage = getResponsibleInstallerPackage(installSource);
+ final SparseArray<String> responsibleInstallerTitles = new SparseArray<>();
+ try {
+ if (requestUserId != UserHandle.USER_ALL) {
+ final ApplicationInfo responsibleInstallerInfo = snapshot.getApplicationInfo(
+ responsibleInstallerPackage, /* flags= */ 0, requestUserId);
+ if (responsibleInstallerInfo == null) {
+ return null;
+ }
+
+ final String title = getResponsibleInstallerTitle(context,
+ responsibleInstallerInfo, responsibleInstallerPackage, requestUserId);
+ responsibleInstallerTitles.put(requestUserId, title);
+ } else {
+ // Go through all userIds.
+ for (int i = 0; i < allUserIds.length; i++) {
+ final int userId = allUserIds[i];
+ final ApplicationInfo responsibleInstallerInfo = snapshot.getApplicationInfo(
+ responsibleInstallerPackage, /* flags= */ 0, userId);
+ // Can't get the applicationInfo on the user.
+ // Maybe the installer isn't installed on the user.
+ if (responsibleInstallerInfo == null) {
+ continue;
+ }
+
+ final String title = getResponsibleInstallerTitle(context,
+ responsibleInstallerInfo, responsibleInstallerPackage, userId);
+ responsibleInstallerTitles.put(userId, title);
+ }
+ }
+ } catch (PackageManager.NameNotFoundException ex) {
+ return null;
+ }
+ return responsibleInstallerTitles;
}
void notifyUnarchivalListener(int status, String installerPackageName, String appPackageName,
diff --git a/services/core/java/com/android/server/pm/PackageManagerException.java b/services/core/java/com/android/server/pm/PackageManagerException.java
index d69737a..9206759 100644
--- a/services/core/java/com/android/server/pm/PackageManagerException.java
+++ b/services/core/java/com/android/server/pm/PackageManagerException.java
@@ -64,6 +64,7 @@
public static final int INTERNAL_ERROR_APEX_NOT_DIRECTORY = -36;
public static final int INTERNAL_ERROR_APEX_MORE_THAN_ONE_FILE = -37;
public static final int INTERNAL_ERROR_MISSING_USER = -38;
+ public static final int INTERNAL_ERROR_ARCHIVE_NO_INSTALLER_TITLE = -39;
@IntDef(prefix = { "INTERNAL_ERROR_" }, value = {
INTERNAL_ERROR_NATIVE_LIBRARY_COPY,
@@ -103,7 +104,8 @@
INTERNAL_ERROR_STATIC_SHARED_LIB_OVERLAY_TARGETS,
INTERNAL_ERROR_APEX_NOT_DIRECTORY,
INTERNAL_ERROR_APEX_MORE_THAN_ONE_FILE,
- INTERNAL_ERROR_MISSING_USER
+ INTERNAL_ERROR_MISSING_USER,
+ INTERNAL_ERROR_ARCHIVE_NO_INSTALLER_TITLE
})
@Retention(RetentionPolicy.SOURCE)
public @interface InternalErrorCode {}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 614828a..0f4e482 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1523,10 +1523,12 @@
}
void markPackageAsArchivedIfNeeded(PackageSetting pkgSetting,
- ArchivedPackageParcel archivePackage, int[] userIds) {
+ ArchivedPackageParcel archivePackage, SparseArray<String> responsibleInstallerTitles,
+ int[] userIds) {
if (pkgSetting == null || archivePackage == null
- || archivePackage.archivedActivities == null || userIds == null
- || userIds.length == 0) {
+ || archivePackage.archivedActivities == null
+ || responsibleInstallerTitles == null
+ || userIds == null || userIds.length == 0) {
return;
}
@@ -1552,7 +1554,8 @@
}
for (int userId : userIds) {
var archiveState = mInstallerService.mPackageArchiver.createArchiveState(
- archivePackage, userId, responsibleInstallerPackage);
+ archivePackage, userId, responsibleInstallerPackage,
+ responsibleInstallerTitles.get(userId));
if (archiveState != null) {
pkgSetting
.modifyUserState(userId)
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 4c653f6..fe9c3f2 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -50,6 +50,7 @@
import android.content.pm.IPackageManager;
import android.content.pm.IShortcutService;
import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.ShortcutChangeCallback;
import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -151,6 +152,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
@@ -320,12 +322,11 @@
private final Handler mHandler;
- @GuardedBy("mLock")
- private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
+ private final CopyOnWriteArrayList<ShortcutChangeListener> mListeners =
+ new CopyOnWriteArrayList<>();
- @GuardedBy("mLock")
- private final ArrayList<LauncherApps.ShortcutChangeCallback> mShortcutChangeCallbacks =
- new ArrayList<>(1);
+ private final CopyOnWriteArrayList<ShortcutChangeCallback> mShortcutChangeCallbacks =
+ new CopyOnWriteArrayList<>();
private final AtomicLong mRawLastResetTime = new AtomicLong(0);
@@ -1841,18 +1842,11 @@
@UserIdInt final int userId) {
return () -> {
try {
- final ArrayList<ShortcutChangeListener> copy;
- synchronized (mLock) {
- if (!isUserUnlockedL(userId)) {
- return;
- }
-
- copy = new ArrayList<>(mListeners);
+ if (!isUserUnlockedL(userId)) {
+ return;
}
// Note onShortcutChanged() needs to be called with the system service permissions.
- for (int i = copy.size() - 1; i >= 0; i--) {
- copy.get(i).onShortcutChanged(packageName, userId);
- }
+ mListeners.forEach(listener -> listener.onShortcutChanged(packageName, userId));
} catch (Exception ignore) {
}
};
@@ -1867,22 +1861,17 @@
final UserHandle user = UserHandle.of(userId);
injectPostToHandler(() -> {
try {
- final ArrayList<LauncherApps.ShortcutChangeCallback> copy;
- synchronized (mLock) {
- if (!isUserUnlockedL(userId)) {
- return;
- }
-
- copy = new ArrayList<>(mShortcutChangeCallbacks);
+ if (!isUserUnlockedL(userId)) {
+ return;
}
- for (int i = copy.size() - 1; i >= 0; i--) {
+ mShortcutChangeCallbacks.forEach(callback -> {
if (!CollectionUtils.isEmpty(changedList)) {
- copy.get(i).onShortcutsAddedOrUpdated(packageName, changedList, user);
+ callback.onShortcutsAddedOrUpdated(packageName, changedList, user);
}
if (!CollectionUtils.isEmpty(removedList)) {
- copy.get(i).onShortcutsRemoved(packageName, removedList, user);
+ callback.onShortcutsRemoved(packageName, removedList, user);
}
- }
+ });
} catch (Exception ignore) {
}
});
@@ -3425,17 +3414,13 @@
@Override
public void addListener(@NonNull ShortcutChangeListener listener) {
- synchronized (mLock) {
- mListeners.add(Objects.requireNonNull(listener));
- }
+ mListeners.add(Objects.requireNonNull(listener));
}
@Override
public void addShortcutChangeCallback(
@NonNull LauncherApps.ShortcutChangeCallback callback) {
- synchronized (mLock) {
- mShortcutChangeCallbacks.add(Objects.requireNonNull(callback));
- }
+ mShortcutChangeCallbacks.add(Objects.requireNonNull(callback));
}
@Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index bd0501d9..20c5b5f 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -462,6 +462,11 @@
}
@Override
+ public int getNumRegisteredAttributionSources(int uid) {
+ return mAttributionSourceRegistry.getNumRegisteredAttributionSources(uid);
+ }
+
+ @Override
public List<String> getAutoRevokeExemptionRequestedPackages(int userId) {
return getPackagesWithAutoRevokePolicy(AUTO_REVOKE_DISCOURAGED, userId);
}
@@ -938,6 +943,26 @@
}
}
+ public int getNumRegisteredAttributionSources(int uid) {
+ mContext.enforceCallingOrSelfPermission(UPDATE_APP_OPS_STATS,
+ "getting the number of registered AttributionSources requires "
+ + "UPDATE_APP_OPS_STATS");
+ // Influence the system to perform a garbage collection, so the provided number is as
+ // accurate as possible
+ System.gc();
+ System.gc();
+ synchronized (mLock) {
+ int[] numForUid = { 0 };
+ mAttributions.forEach((key, value) -> {
+ if (value.getUid() == uid) {
+ numForUid[0]++;
+ }
+
+ });
+ return numForUid[0];
+ }
+ }
+
private int resolveUid(int uid) {
final VoiceInteractionManagerInternal vimi = LocalServices
.getService(VoiceInteractionManagerInternal.class);
diff --git a/services/core/java/com/android/server/power/Android.bp b/services/core/java/com/android/server/power/Android.bp
deleted file mode 100644
index 5d4065d..0000000
--- a/services/core/java/com/android/server/power/Android.bp
+++ /dev/null
@@ -1,14 +0,0 @@
-aconfig_declarations {
- name: "backstage_power_flags",
- package: "com.android.server.power.optimization",
- container: "system",
- srcs: [
- "stats/*.aconfig",
- ],
-}
-
-java_aconfig_library {
- name: "backstage_power_flags_lib",
- aconfig_declarations: "backstage_power_flags",
- sdk_version: "system_current",
-}
diff --git a/services/core/java/com/android/server/power/FaceDownDetector.java b/services/core/java/com/android/server/power/FaceDownDetector.java
index b237ca2..84ed87a 100644
--- a/services/core/java/com/android/server/power/FaceDownDetector.java
+++ b/services/core/java/com/android/server/power/FaceDownDetector.java
@@ -76,6 +76,8 @@
private static final boolean DEFAULT_FEATURE_ENABLED = true;
private boolean mIsEnabled;
+ // Defaults to true, we only want to disable if this is specifically requested.
+ private boolean mEnabledOverride = true;
private int mSensorMaxLatencyMicros;
@@ -240,6 +242,7 @@
pw.println(" mZAccelerationThreshold=" + mZAccelerationThreshold);
pw.println(" mAccelerationThreshold=" + mAccelerationThreshold);
pw.println(" mTimeThreshold=" + mTimeThreshold);
+ pw.println(" mEnabledOverride=" + mEnabledOverride);
}
@Override
@@ -336,10 +339,9 @@
}
private boolean isEnabled() {
- return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_FEATURE_ENABLED,
- DEFAULT_FEATURE_ENABLED)
- && mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_flipToScreenOffEnabled);
+ return mEnabledOverride && DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_FEATURE_ENABLED, DEFAULT_FEATURE_ENABLED) && mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_flipToScreenOffEnabled);
}
private float getAccelerationThreshold() {
@@ -450,6 +452,15 @@
}
/**
+ * Allows detector to be enabled & disabled.
+ * @param enabled whether to enable detector.
+ */
+ public void setEnabledOverride(boolean enabled) {
+ mEnabledOverride = enabled;
+ mIsEnabled = isEnabled();
+ }
+
+ /**
* Sets how much screen on time might be saved as a result of this detector. Currently used for
* logging purposes.
*/
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 53863aa..eb1f720 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -622,7 +622,6 @@
// Value we store for tracking face down behavior.
@VisibleForTesting
boolean mIsFaceDown = false;
- private boolean mUseFaceDownDetector = true;
private long mLastFlipTime = 0L;
// The screen brightness setting override from the window manager
@@ -3254,7 +3253,7 @@
mScreenTimeoutOverridePolicy.getScreenTimeoutOverrideLocked(
mWakeLockSummary, screenOffTimeout);
}
- if (mIsFaceDown && mUseFaceDownDetector) {
+ if (mIsFaceDown) {
shortestScreenOffTimeout = Math.min(screenDimDuration, shortestScreenOffTimeout);
}
@@ -4702,7 +4701,6 @@
pw.println(" mHoldingDisplaySuspendBlocker=" + mHoldingDisplaySuspendBlocker);
pw.println(" mLastFlipTime=" + mLastFlipTime);
pw.println(" mIsFaceDown=" + mIsFaceDown);
- pw.println(" mUseFaceDownDetector=" + mUseFaceDownDetector);
pw.println();
pw.println("Settings and Configuration:");
@@ -6927,7 +6925,7 @@
public void setUseFaceDownDetector(boolean enable) {
final long ident = Binder.clearCallingIdentity();
try {
- mUseFaceDownDetector = enable;
+ mFaceDownDetector.setEnabledOverride(enable);
} finally {
Binder.restoreCallingIdentity(ident);
}
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 d060c7c..54cb9c9 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -4885,7 +4885,6 @@
if (type == WAKE_TYPE_PARTIAL) {
// Only care about partial wake locks, since full wake locks
// will be canceled when the user puts the screen to sleep.
- aggregateLastWakeupUptimeLocked(elapsedRealtimeMs, uptimeMs);
if (historyName == null) {
historyName = name;
}
@@ -5205,20 +5204,14 @@
}
@GuardedBy("this")
- void aggregateLastWakeupUptimeLocked(long elapsedRealtimeMs, long uptimeMs) {
+ public void noteWakeupReasonLocked(String reason, long elapsedRealtimeMs, long uptimeMs) {
if (mLastWakeupReason != null) {
long deltaUptimeMs = uptimeMs - mLastWakeupUptimeMs;
SamplingTimer timer = getWakeupReasonTimerLocked(mLastWakeupReason);
timer.add(deltaUptimeMs * 1000, 1, elapsedRealtimeMs); // time in in microseconds
mFrameworkStatsLogger.kernelWakeupReported(deltaUptimeMs * 1000, mLastWakeupReason,
mLastWakeupElapsedTimeMs);
- mLastWakeupReason = null;
}
- }
-
- @GuardedBy("this")
- public void noteWakeupReasonLocked(String reason, long elapsedRealtimeMs, long uptimeMs) {
- aggregateLastWakeupUptimeLocked(elapsedRealtimeMs, uptimeMs);
mHistory.recordWakeupEvent(elapsedRealtimeMs, uptimeMs, reason);
mLastWakeupReason = reason;
mLastWakeupUptimeMs = uptimeMs;
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index b1b2cc9..f53a1b0 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -49,7 +49,6 @@
private static final long ENERGY_UNSPECIFIED = -1;
private static final int DEFAULT_CPU_POWER_BRACKETS = 3;
private static final int DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER = 2;
- private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000;
interface Injector {
Handler getHandler();
@@ -76,7 +75,6 @@
private CpuScalingPolicies mCpuScalingPolicies;
private PowerProfile mPowerProfile;
private KernelCpuStatsReader mKernelCpuStatsReader;
- private PowerStatsUidResolver mUidResolver;
private ConsumedEnergyRetriever mConsumedEnergyRetriever;
private IntSupplier mVoltageSupplier;
private int mDefaultCpuPowerBrackets;
@@ -97,7 +95,8 @@
private long[] mLastConsumedEnergyUws;
public CpuPowerStatsCollector(Injector injector, long throttlePeriodMs) {
- super(injector.getHandler(), throttlePeriodMs, injector.getClock());
+ super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
+ injector.getClock());
mInjector = injector;
}
@@ -113,7 +112,6 @@
mCpuScalingPolicies = mInjector.getCpuScalingPolicies();
mPowerProfile = mInjector.getPowerProfile();
mKernelCpuStatsReader = mInjector.getKernelCpuStatsReader();
- mUidResolver = mInjector.getUidResolver();
mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
mVoltageSupplier = mInjector.getVoltageSupplier();
mDefaultCpuPowerBrackets = mInjector.getDefaultCpuPowerBrackets();
@@ -421,7 +419,8 @@
boolean nonzero = false;
for (int bracket = powerBracketCount - 1; bracket >= 0; bracket--) {
- long delta = timeByPowerBracket[bracket] - uidStats.timeByPowerBracket[bracket];
+ long delta = Math.max(0,
+ timeByPowerBracket[bracket] - uidStats.timeByPowerBracket[bracket]);
if (delta != 0) {
nonzero = true;
}
@@ -447,6 +446,12 @@
}
}
+ @Override
+ protected void onUidRemoved(int uid) {
+ super.onUidRemoved(uid);
+ mUidStats.remove(uid);
+ }
+
/**
* Native class that retrieves CPU stats from the kernel.
*/
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
index 8c154e4..7bc68175 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
@@ -89,7 +89,6 @@
private PowerStats mPowerStats;
private long[] mDeviceStats;
- private PowerStatsUidResolver mPowerStatsUidResolver;
private volatile TelephonyManager mTelephonyManager;
private LongSupplier mCallDurationSupplier;
private LongSupplier mScanDurationSupplier;
@@ -106,7 +105,8 @@
private long mLastScanDuration;
public MobileRadioPowerStatsCollector(Injector injector, long throttlePeriodMs) {
- super(injector.getHandler(), throttlePeriodMs, injector.getClock());
+ super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
+ injector.getClock());
mInjector = injector;
}
@@ -130,7 +130,6 @@
return false;
}
- mPowerStatsUidResolver = mInjector.getUidResolver();
mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
mVoltageSupplier = mInjector.getVoltageSupplier();
@@ -310,7 +309,7 @@
continue;
}
- int uid = mPowerStatsUidResolver.mapUid(uidDelta.getUid());
+ int uid = mUidResolver.mapUid(uidDelta.getUid());
long[] stats = mPowerStats.uidStats.get(uid);
if (stats == null) {
stats = new long[mLayout.getUidStatsArrayLength()];
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
index 5dd11db..b82c021 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
@@ -53,6 +53,7 @@
private static final int MILLIVOLTS_PER_VOLT = 1000;
private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000;
private final Handler mHandler;
+ protected final PowerStatsUidResolver mUidResolver;
protected final Clock mClock;
private final long mThrottlePeriodMs;
private final Runnable mCollectAndDeliverStats = this::collectAndDeliverStats;
@@ -63,9 +64,25 @@
@SuppressWarnings("unchecked")
private volatile List<Consumer<PowerStats>> mConsumerList = Collections.emptyList();
- public PowerStatsCollector(Handler handler, long throttlePeriodMs, Clock clock) {
+ public PowerStatsCollector(Handler handler, long throttlePeriodMs,
+ PowerStatsUidResolver uidResolver, Clock clock) {
mHandler = handler;
mThrottlePeriodMs = throttlePeriodMs;
+ mUidResolver = uidResolver;
+ mUidResolver.addListener(new PowerStatsUidResolver.Listener() {
+ @Override
+ public void onIsolatedUidAdded(int isolatedUid, int parentUid) {
+ }
+
+ @Override
+ public void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid) {
+ }
+
+ @Override
+ public void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid) {
+ mHandler.post(()->onUidRemoved(isolatedUid));
+ }
+ });
mClock = clock;
}
@@ -203,6 +220,9 @@
done.block();
}
+ protected void onUidRemoved(int uid) {
+ }
+
/** Calculate charge consumption (in microcoulombs) from a given energy and voltage */
protected static long uJtoUc(long deltaEnergyUj, int avgVoltageMv) {
// To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
similarity index 100%
rename from packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
rename to services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
similarity index 100%
rename from packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
rename to services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index bb4876b..587be07 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -16,6 +16,7 @@
package com.android.server.security;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
@@ -27,6 +28,7 @@
import android.os.Environment;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.PermissionEnforcer;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -79,7 +81,11 @@
return LocalServices.getService(FileIntegrityService.class);
}
- private final IBinder mService = new IFileIntegrityService.Stub() {
+ private final class BinderService extends IFileIntegrityService.Stub {
+ BinderService(Context context) {
+ super(PermissionEnforcer.fromContext(context));
+ }
+
@Override
public boolean isApkVeritySupported() {
return VerityUtils.isFsVeritySupported();
@@ -168,8 +174,10 @@
}
@Override
+ @EnforcePermission(android.Manifest.permission.SETUP_FSVERITY)
public int setupFsverity(android.os.IInstalld.IFsveritySetupAuthToken authToken,
String filePath, String packageName) throws RemoteException {
+ setupFsverity_enforcePermission();
Objects.requireNonNull(authToken);
Objects.requireNonNull(filePath);
Objects.requireNonNull(packageName);
@@ -181,10 +189,12 @@
throw new RemoteException(e);
}
}
- };
+ }
+ private final IBinder mService;
public FileIntegrityService(final Context context) {
super(context);
+ mService = new BinderService(context);
try {
sCertFactory = CertificateFactory.getInstance("X.509");
} catch (CertificateException e) {
diff --git a/services/core/java/com/android/server/selinux/QuotaLimiter.java b/services/core/java/com/android/server/selinux/QuotaLimiter.java
index e89ddfd..34d18ce 100644
--- a/services/core/java/com/android/server/selinux/QuotaLimiter.java
+++ b/services/core/java/com/android/server/selinux/QuotaLimiter.java
@@ -34,10 +34,10 @@
private final Clock mClock;
private final Duration mWindowSize;
- private final int mMaxPermits;
- private long mCurrentWindow = 0;
- private int mPermitsGranted = 0;
+ private int mMaxPermits;
+ private long mCurrentWindow;
+ private int mPermitsGranted;
@VisibleForTesting
QuotaLimiter(Clock clock, Duration windowSize, int maxPermits) {
@@ -75,4 +75,8 @@
return false;
}
+
+ public void setMaxPermits(int maxPermits) {
+ this.mMaxPermits = maxPermits;
+ }
}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
index 8d8d596..d69150d 100644
--- a/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
@@ -15,35 +15,66 @@
*/
package com.android.server.selinux;
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.Arrays;
import java.util.Iterator;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
import java.util.stream.Stream;
/** Builder for SelinuxAuditLogs. */
class SelinuxAuditLogBuilder {
- // Currently logs collection is hardcoded for the sdk_sandbox_audit.
- private static final String SDK_SANDBOX_AUDIT = "sdk_sandbox_audit";
- static final Matcher SCONTEXT_MATCHER =
- Pattern.compile(
- "u:r:(?<stype>"
- + SDK_SANDBOX_AUDIT
- + "):s0(:c)?(?<scategories>((,c)?\\d+)+)*")
- .matcher("");
+ private static final String TAG = "SelinuxAuditLogs";
- static final Matcher TCONTEXT_MATCHER =
- Pattern.compile("u:object_r:(?<ttype>\\w+):s0(:c)?(?<tcategories>((,c)?\\d+)+)*")
- .matcher("");
+ // This config indicates which Selinux logs for source domains to collect. The string will be
+ // inserted into a regex, so it must follow the regex syntax. For example, a valid value would
+ // be "system_server|untrusted_app".
+ @VisibleForTesting static final String CONFIG_SELINUX_AUDIT_DOMAIN = "selinux_audit_domain";
+ private static final Matcher NO_OP_MATCHER = Pattern.compile("no-op^").matcher("");
+ private static final String TCONTEXT_PATTERN =
+ "u:object_r:(?<ttype>\\w+):s0(:c)?(?<tcategories>((,c)?\\d+)+)*";
+ private static final String PATH_PATTERN = "\"(?<path>/\\w+(/\\w+)?)(/\\w+)*\"";
- static final Matcher PATH_MATCHER =
- Pattern.compile("\"(?<path>/\\w+(/\\w+)?)(/\\w+)*\"").matcher("");
+ @VisibleForTesting final Matcher mScontextMatcher;
+ @VisibleForTesting final Matcher mTcontextMatcher;
+ @VisibleForTesting final Matcher mPathMatcher;
private Iterator<String> mTokens;
private final SelinuxAuditLog mAuditLog = new SelinuxAuditLog();
+ SelinuxAuditLogBuilder() {
+ Matcher scontextMatcher = NO_OP_MATCHER;
+ Matcher tcontextMatcher = NO_OP_MATCHER;
+ Matcher pathMatcher = NO_OP_MATCHER;
+ try {
+ scontextMatcher =
+ Pattern.compile(
+ TextUtils.formatSimple(
+ "u:r:(?<stype>%s):s0(:c)?(?<scategories>((,c)?\\d+)+)*",
+ DeviceConfig.getString(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ CONFIG_SELINUX_AUDIT_DOMAIN,
+ "no_match^")))
+ .matcher("");
+ tcontextMatcher = Pattern.compile(TCONTEXT_PATTERN).matcher("");
+ pathMatcher = Pattern.compile(PATH_PATTERN).matcher("");
+ } catch (PatternSyntaxException e) {
+ Slog.e(TAG, "Invalid pattern, setting every matcher to no-op.", e);
+ }
+
+ mScontextMatcher = scontextMatcher;
+ mTcontextMatcher = tcontextMatcher;
+ mPathMatcher = pathMatcher;
+ }
+
void reset(String denialString) {
mTokens =
Arrays.asList(
@@ -82,18 +113,18 @@
mAuditLog.mPermissions = permissionsStream.build().toArray(String[]::new);
break;
case "scontext":
- if (!nextTokenMatches(SCONTEXT_MATCHER)) {
+ if (!nextTokenMatches(mScontextMatcher)) {
return null;
}
- mAuditLog.mSType = SCONTEXT_MATCHER.group("stype");
- mAuditLog.mSCategories = toCategories(SCONTEXT_MATCHER.group("scategories"));
+ mAuditLog.mSType = mScontextMatcher.group("stype");
+ mAuditLog.mSCategories = toCategories(mScontextMatcher.group("scategories"));
break;
case "tcontext":
- if (!nextTokenMatches(TCONTEXT_MATCHER)) {
+ if (!nextTokenMatches(mTcontextMatcher)) {
return null;
}
- mAuditLog.mTType = TCONTEXT_MATCHER.group("ttype");
- mAuditLog.mTCategories = toCategories(TCONTEXT_MATCHER.group("tcategories"));
+ mAuditLog.mTType = mTcontextMatcher.group("ttype");
+ mAuditLog.mTCategories = toCategories(mTcontextMatcher.group("tcategories"));
break;
case "tclass":
if (!mTokens.hasNext()) {
@@ -102,8 +133,8 @@
mAuditLog.mTClass = mTokens.next();
break;
case "path":
- if (nextTokenMatches(PATH_MATCHER)) {
- mAuditLog.mPath = PATH_MATCHER.group("path");
+ if (nextTokenMatches(mPathMatcher)) {
+ mAuditLog.mPath = mPathMatcher.group("path");
}
break;
case "permissive":
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
index 03822aa..c655d46 100644
--- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
@@ -18,10 +18,12 @@
import android.util.EventLog;
import android.util.EventLog.Event;
import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog;
+import com.android.server.utils.Slogf;
import java.io.IOException;
import java.time.Instant;
@@ -37,6 +39,7 @@
class SelinuxAuditLogsCollector {
private static final String TAG = "SelinuxAuditLogs";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String SELINUX_PATTERN = "^.*\\bavc:\\s+(?<denial>.*)$";
@@ -48,13 +51,17 @@
@VisibleForTesting Instant mLastWrite = Instant.MIN;
- final AtomicBoolean mStopRequested = new AtomicBoolean(false);
+ AtomicBoolean mStopRequested = new AtomicBoolean(false);
SelinuxAuditLogsCollector(RateLimiter rateLimiter, QuotaLimiter quotaLimiter) {
mRateLimiter = rateLimiter;
mQuotaLimiter = quotaLimiter;
}
+ public void setStopRequested(boolean stopRequested) {
+ mStopRequested.set(stopRequested);
+ }
+
/**
* Collect and push SELinux audit logs for the provided {@code tagCode}.
*
@@ -66,7 +73,7 @@
boolean quotaExceeded = writeAuditLogs(logLines);
if (quotaExceeded) {
- Log.w(TAG, "Too many SELinux logs in the queue, I am giving up.");
+ Slog.w(TAG, "Too many SELinux logs in the queue, I am giving up.");
mLastWrite = latestTimestamp; // next run we will ignore all these logs.
logLines.clear();
}
@@ -79,7 +86,7 @@
try {
EventLog.readEvents(new int[] {tagCode}, events);
} catch (IOException e) {
- Log.e(TAG, "Error reading event logs", e);
+ Slog.e(TAG, "Error reading event logs", e);
}
Instant latestTimestamp = mLastWrite;
@@ -102,6 +109,7 @@
private boolean writeAuditLogs(Queue<Event> logLines) {
final SelinuxAuditLogBuilder auditLogBuilder = new SelinuxAuditLogBuilder();
+ int auditsWritten = 0;
while (!mStopRequested.get() && !logLines.isEmpty()) {
Event event = logLines.poll();
@@ -118,6 +126,9 @@
}
if (!mQuotaLimiter.acquire()) {
+ if (DEBUG) {
+ Slogf.d(TAG, "Running out of quota after %d logs.", auditsWritten);
+ }
return true;
}
mRateLimiter.acquire();
@@ -133,12 +144,16 @@
auditLog.mTClass,
auditLog.mPath,
auditLog.mPermissive);
+ auditsWritten++;
if (logTime.isAfter(mLastWrite)) {
mLastWrite = logTime;
}
}
+ if (DEBUG) {
+ Slogf.d(TAG, "Written %d logs", auditsWritten);
+ }
return false;
}
}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java
new file mode 100644
index 0000000..0092c37
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.util.Slog;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class handles the start and stop requests for the logs collector job, in particular making
+ * sure that at most one job is running at any given moment.
+ */
+final class SelinuxAuditLogsJob {
+
+ private static final String TAG = "SelinuxAuditLogs";
+
+ private final AtomicBoolean mIsRunning = new AtomicBoolean(false);
+ private final SelinuxAuditLogsCollector mAuditLogsCollector;
+
+ SelinuxAuditLogsJob(SelinuxAuditLogsCollector auditLogsCollector) {
+ mAuditLogsCollector = auditLogsCollector;
+ }
+
+ void requestStop() {
+ mAuditLogsCollector.mStopRequested.set(true);
+ }
+
+ boolean isRunning() {
+ return mIsRunning.get();
+ }
+
+ public void start(JobService jobService, JobParameters params) {
+ mAuditLogsCollector.mStopRequested.set(false);
+ if (mIsRunning.get()) {
+ Slog.i(TAG, "Selinux audit job is already running, ignore start request.");
+ return;
+ }
+ mIsRunning.set(true);
+ boolean done = mAuditLogsCollector.collect(SelinuxAuditLogsService.AUDITD_TAG_CODE);
+ if (done) {
+ jobService.jobFinished(params, /* wantsReschedule= */ false);
+ }
+ mIsRunning.set(false);
+ }
+}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
index 8a661bc..d46e891 100644
--- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
@@ -23,14 +23,16 @@
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
import android.util.EventLog;
-import android.util.Log;
+import android.util.Slog;
import java.time.Duration;
+import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
/**
* Scheduled jobs related to logging of SELinux denials and audits. The job runs daily on idle
@@ -43,58 +45,68 @@
static final int AUDITD_TAG_CODE = EventLog.getTagCode("auditd");
+ private static final String CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS =
+ "selinux_audit_job_frequency_hours";
+ private static final String CONFIG_SELINUX_ENABLE_AUDIT_JOB = "selinux_enable_audit_job";
+ private static final String CONFIG_SELINUX_AUDIT_CAP = "selinux_audit_cap";
+ private static final int MAX_PERMITS_CAP_DEFAULT = 50000;
+
private static final int SELINUX_AUDIT_JOB_ID = 25327386;
- private static final JobInfo SELINUX_AUDIT_JOB =
- new JobInfo.Builder(
- SELINUX_AUDIT_JOB_ID,
- new ComponentName("android", SelinuxAuditLogsService.class.getName()))
- .setPeriodic(TimeUnit.DAYS.toMillis(1))
- .setRequiresDeviceIdle(true)
- .setRequiresCharging(true)
- .setRequiresBatteryNotLow(true)
- .build();
+ private static final ComponentName SELINUX_AUDIT_JOB_COMPONENT =
+ new ComponentName("android", SelinuxAuditLogsService.class.getName());
private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
- private static final AtomicReference<Boolean> IS_RUNNING = new AtomicReference<>(false);
- // Audit logging is subject to both rate and quota limiting. We can only push one atom every 10
- // milliseconds, and no more than 50K atoms can be pushed each day.
- private static final SelinuxAuditLogsCollector AUDIT_LOGS_COLLECTOR =
- new SelinuxAuditLogsCollector(
- new RateLimiter(/* window= */ Duration.ofMillis(10)),
- new QuotaLimiter(/* maxPermitsPerDay= */ 50000));
+ // Audit logging is subject to both rate and quota limiting. A {@link RateLimiter} makes sure
+ // that we push no more than one atom every 10 milliseconds. A {@link QuotaLimiter} caps the
+ // number of atoms pushed per day to CONFIG_SELINUX_AUDIT_CAP. The quota limiter is static
+ // because new job executions happen in a new instance of this class. Making the quota limiter
+ // an instance reference would reset the quota limitations between jobs executions.
+ private static final Duration RATE_LIMITER_WINDOW = Duration.ofMillis(10);
+ private static final QuotaLimiter QUOTA_LIMITER =
+ new QuotaLimiter(
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ CONFIG_SELINUX_AUDIT_CAP,
+ MAX_PERMITS_CAP_DEFAULT));
+ private static final SelinuxAuditLogsJob LOGS_COLLECTOR_JOB =
+ new SelinuxAuditLogsJob(
+ new SelinuxAuditLogsCollector(
+ new RateLimiter(RATE_LIMITER_WINDOW), QUOTA_LIMITER));
/** Schedule jobs with the {@link JobScheduler}. */
public static void schedule(Context context) {
if (!selinuxSdkSandboxAudit()) {
- Log.d(TAG, "SelinuxAuditLogsService not enabled");
+ Slog.d(TAG, "SelinuxAuditLogsService not enabled");
return;
}
if (AUDITD_TAG_CODE == -1) {
- Log.e(TAG, "auditd is not a registered tag on this system");
+ Slog.e(TAG, "auditd is not a registered tag on this system");
return;
}
- if (context.getSystemService(JobScheduler.class)
- .forNamespace(SELINUX_AUDIT_NAMESPACE)
- .schedule(SELINUX_AUDIT_JOB)
- == JobScheduler.RESULT_FAILURE) {
- Log.e(TAG, "SelinuxAuditLogsService could not be started.");
- }
+ LogsCollectorJobScheduler propertiesListener =
+ new LogsCollectorJobScheduler(
+ context.getSystemService(JobScheduler.class)
+ .forNamespace(SELINUX_AUDIT_NAMESPACE));
+ propertiesListener.schedule();
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_ADSERVICES, context.getMainExecutor(), propertiesListener);
}
@Override
public boolean onStartJob(JobParameters params) {
if (params.getJobId() != SELINUX_AUDIT_JOB_ID) {
- Log.e(TAG, "The job id does not match the expected selinux job id.");
+ Slog.e(TAG, "The job id does not match the expected selinux job id.");
+ return false;
+ }
+ if (!selinuxSdkSandboxAudit()) {
+ Slog.i(TAG, "Selinux audit job disabled.");
return false;
}
- AUDIT_LOGS_COLLECTOR.mStopRequested.set(false);
- IS_RUNNING.set(true);
- EXECUTOR_SERVICE.execute(new LogsCollectorJob(this, params));
-
+ EXECUTOR_SERVICE.execute(() -> LOGS_COLLECTOR_JOB.start(this, params));
return true; // the job is running
}
@@ -104,29 +116,69 @@
return false;
}
- AUDIT_LOGS_COLLECTOR.mStopRequested.set(true);
- return IS_RUNNING.get();
+ if (LOGS_COLLECTOR_JOB.isRunning()) {
+ LOGS_COLLECTOR_JOB.requestStop();
+ return true;
+ }
+ return false;
}
- private static class LogsCollectorJob implements Runnable {
- private final JobService mAuditLogService;
- private final JobParameters mParams;
+ /**
+ * This class is in charge of scheduling the job service, and keeping the scheduling up to date
+ * when the parameters change.
+ */
+ private static final class LogsCollectorJobScheduler
+ implements DeviceConfig.OnPropertiesChangedListener {
- LogsCollectorJob(JobService auditLogService, JobParameters params) {
- mAuditLogService = auditLogService;
- mParams = params;
+ private final JobScheduler mJobScheduler;
+
+ private LogsCollectorJobScheduler(JobScheduler jobScheduler) {
+ mJobScheduler = jobScheduler;
}
@Override
- public void run() {
- IS_RUNNING.updateAndGet(
- isRunning -> {
- boolean done = AUDIT_LOGS_COLLECTOR.collect(AUDITD_TAG_CODE);
- if (done) {
- mAuditLogService.jobFinished(mParams, /* wantsReschedule= */ false);
- }
- return !done;
- });
+ public void onPropertiesChanged(Properties changedProperties) {
+ Set<String> keyset = changedProperties.getKeyset();
+
+ if (keyset.contains(CONFIG_SELINUX_AUDIT_CAP)) {
+ QUOTA_LIMITER.setMaxPermits(
+ changedProperties.getInt(
+ CONFIG_SELINUX_AUDIT_CAP, MAX_PERMITS_CAP_DEFAULT));
+ }
+
+ if (keyset.contains(CONFIG_SELINUX_ENABLE_AUDIT_JOB)) {
+ boolean enabled =
+ changedProperties.getBoolean(
+ CONFIG_SELINUX_ENABLE_AUDIT_JOB, /* defaultValue= */ false);
+ if (enabled) {
+ schedule();
+ } else {
+ mJobScheduler.cancel(SELINUX_AUDIT_JOB_ID);
+ }
+ } else if (keyset.contains(CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS)) {
+ // The job frequency changed, reschedule.
+ schedule();
+ }
+ }
+
+ private void schedule() {
+ long frequencyMillis =
+ TimeUnit.HOURS.toMillis(
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS,
+ 24));
+ if (mJobScheduler.schedule(
+ new JobInfo.Builder(SELINUX_AUDIT_JOB_ID, SELINUX_AUDIT_JOB_COMPONENT)
+ .setPeriodic(frequencyMillis)
+ .setRequiresDeviceIdle(true)
+ .setRequiresBatteryNotLow(true)
+ .build())
+ == JobScheduler.RESULT_FAILURE) {
+ Slog.e(TAG, "SelinuxAuditLogsService could not be scheduled.");
+ } else {
+ Slog.d(TAG, "SelinuxAuditLogsService scheduled successfully.");
+ }
}
}
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index b5df30f..e3e478d 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -61,6 +61,7 @@
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
+import static com.android.server.stats.Flags.statsPullNetworkStatsManagerInitOrderFix;
import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
@@ -355,7 +356,17 @@
private TelephonyManager mTelephony;
private UwbManager mUwbManager;
private SubscriptionManager mSubscriptionManager;
- private NetworkStatsManager mNetworkStatsManager;
+
+ /**
+ * NetworkStatsManager initialization happens from one thread before any worker thread
+ * is going to access the networkStatsManager instance:
+ * - @initNetworkStatsManager() - initialization happens no worker thread to access are
+ * active yet
+ * - @initAndRegisterNetworkStatsPullers Network stats dependant pullers can only be
+ * initialized after service is ready. Worker thread is spawn here only after the
+ * initialization is completed in a thread safe way (no async access expected)
+ */
+ private NetworkStatsManager mNetworkStatsManager = null;
@GuardedBy("mKernelWakelockLock")
private KernelWakelockReader mKernelWakelockReader;
@@ -420,6 +431,12 @@
public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER =
addMobileBytesTransferByProcStatePuller();
+ /**
+ * Whether or not to enable the mNetworkStatsManager initialization order fix
+ */
+ private static final boolean ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX =
+ statsPullNetworkStatsManagerInitOrderFix();
+
// Puller locks
private final Object mDataBytesTransferLock = new Object();
private final Object mBluetoothBytesTransferLock = new Object();
@@ -823,6 +840,9 @@
registerEventListeners();
});
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
+ initNetworkStatsManager();
+ }
BackgroundThread.getHandler().post(() -> {
// Network stats related pullers can only be initialized after service is ready.
initAndRegisterNetworkStatsPullers();
@@ -843,7 +863,9 @@
mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
- mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
+ if (!ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
+ initNetworkStatsManager();
+ }
// Initialize DiskIO
mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
@@ -1019,6 +1041,24 @@
}
}
+ /**
+ * Calling getNetworkStatsManager() before PHASE_THIRD_PARTY_APPS_CAN_START is unexpected
+ * Callers use before PHASE_THIRD_PARTY_APPS_CAN_START stage is not legit
+ */
+ @NonNull
+ private NetworkStatsManager getNetworkStatsManager() {
+ if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
+ if (mNetworkStatsManager == null) {
+ throw new IllegalStateException("NetworkStatsManager is not ready");
+ }
+ }
+ return mNetworkStatsManager;
+ }
+
+ private void initNetworkStatsManager() {
+ mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
+ }
+
private void initAndRegisterNetworkStatsPullers() {
if (DEBUG) {
Slog.d(TAG, "Registering NetworkStats pullers with statsd");
@@ -1514,11 +1554,11 @@
// I/O and also block main thread when polling.
// Consider making perfd queries NetworkStatsService directly.
if (template.getMatchRule() == MATCH_WIFI && template.getSubscriberIds().isEmpty()) {
- mNetworkStatsManager.forceUpdate();
+ getNetworkStatsManager().forceUpdate();
}
final android.app.usage.NetworkStats queryNonTaggedStats =
- mNetworkStatsManager.querySummary(
+ getNetworkStatsManager().querySummary(
template, currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
currentTimeInMillis);
@@ -1528,7 +1568,7 @@
if (!includeTags) return nonTaggedStats;
final android.app.usage.NetworkStats queryTaggedStats =
- mNetworkStatsManager.queryTaggedSummary(template,
+ getNetworkStatsManager().queryTaggedSummary(template,
currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
currentTimeInMillis);
final NetworkStats taggedStats =
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index 101b98e..c479c6d 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -7,4 +7,12 @@
description: "Adds mobile_bytes_transfer_by_proc_state atom with system server side aggregation"
bug: "309512867"
is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+ name: "stats_pull_network_stats_manager_init_order_fix"
+ namespace: "statsd"
+ description: "Fix the mNetworkStatsManager initialization order"
+ bug: "331989853"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
index 10e868d..c1d92cf 100644
--- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java
+++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
@@ -119,8 +119,13 @@
}
@Override
- public void onStart() {
- publishBinderService(TRACING_SERVICE_PROXY_BINDER_NAME, mTracingServiceProxy);
+ public void onStart() {}
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
+ publishBinderService(TRACING_SERVICE_PROXY_BINDER_NAME, mTracingServiceProxy);
+ }
}
private void notifyTraceur(boolean sessionStolen) {
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
index 4a81c95..440d2514 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
@@ -89,8 +89,34 @@
* @param ownerId the removing client id of the owner.
*/
public void removeOwner(int ownerId) {
- mAvailableSessionNum += mOwnerClientIdsToSessionNum.get(ownerId);
- mOwnerClientIdsToSessionNum.remove(ownerId);
+ if (mOwnerClientIdsToSessionNum.containsKey(ownerId)) {
+ mAvailableSessionNum += mOwnerClientIdsToSessionNum.get(ownerId);
+ mOwnerClientIdsToSessionNum.remove(ownerId);
+ }
+ }
+
+ /**
+ * Remove a single session from resource
+ *
+ * @param ownerId the client Id of the owner of the session
+ */
+ public void removeSession(int ownerId) {
+ if (mOwnerClientIdsToSessionNum.containsKey(ownerId)) {
+ int sessionNum = mOwnerClientIdsToSessionNum.get(ownerId);
+ if (sessionNum > 0) {
+ mOwnerClientIdsToSessionNum.put(ownerId, --sessionNum);
+ mAvailableSessionNum++;
+ }
+ }
+ }
+
+ /**
+ * Check if there are any open sessions owned by a client
+ *
+ * @param ownerId the client Id of the owner of the sessions
+ */
+ public boolean hasOpenSessions(int ownerId) {
+ return mOwnerClientIdsToSessionNum.get(ownerId) > 0;
}
public Set<Integer> getOwnerClientIds() {
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 cddc79d..0afb049 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -1924,11 +1924,13 @@
ownerProfile.useCiCam(grantingId);
}
- private void updateCasClientMappingOnRelease(
- @NonNull CasResource releasingCas, int ownerClientId) {
- ClientProfile ownerProfile = getClientProfile(ownerClientId);
- releasingCas.removeOwner(ownerClientId);
- ownerProfile.releaseCas();
+ private void updateCasClientMappingOnRelease(@NonNull CasResource cas, int ownerClientId) {
+ cas.removeSession(ownerClientId);
+ if (!cas.hasOpenSessions(ownerClientId)) {
+ ClientProfile ownerProfile = getClientProfile(ownerClientId);
+ cas.removeOwner(ownerClientId);
+ ownerProfile.releaseCas();
+ }
}
private void updateCiCamClientMappingOnRelease(
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index b7d8cfc..e944eca 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -193,6 +193,10 @@
@GuardedBy("mLock")
private int mTotalStarted = 0;
+ /** The total number of timers that were restarted without an explicit cancel. */
+ @GuardedBy("mLock")
+ private int mTotalRestarted = 0;
+
/** The total number of errors detected. */
@GuardedBy("mLock")
private int mTotalErrors = 0;
@@ -434,10 +438,10 @@
@Override
void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
synchronized (mLock) {
- if (mTimerIdMap.containsKey(arg)) {
- // There is an existing timer. Cancel it.
- cancel(arg);
- }
+ // If there is an existing timer, cancel it. This is a nop if the timer does not
+ // exist.
+ if (cancel(arg)) mTotalRestarted++;
+
int timerId = nativeAnrTimerStart(mNative, pid, uid, timeoutMs, mExtend);
if (timerId > 0) {
mTimerIdMap.put(arg, timerId);
@@ -546,9 +550,7 @@
private Integer removeLocked(V arg) {
Integer r = mTimerIdMap.remove(arg);
if (r != null) {
- synchronized (mTimerArgMap) {
- mTimerArgMap.remove(r);
- }
+ mTimerArgMap.remove(r);
}
return r;
}
@@ -672,8 +674,8 @@
synchronized (mLock) {
pw.format("timer: %s\n", mLabel);
pw.increaseIndent();
- pw.format("started=%d maxStarted=%d running=%d expired=%d errors=%d\n",
- mTotalStarted, mMaxStarted, mTimerIdMap.size(),
+ pw.format("started=%d maxStarted=%d restarted=%d running=%d expired=%d errors=%d\n",
+ mTotalStarted, mMaxStarted, mTotalRestarted, mTimerIdMap.size(),
mTotalExpired, mTotalErrors);
pw.decreaseIndent();
mFeature.dump(pw, false);
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index 1383708..6a4c9c2 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.net.IpSecTransformState;
import android.net.vcn.FeatureFlags;
import android.net.vcn.FeatureFlagsImpl;
import android.os.Looper;
@@ -34,7 +35,6 @@
@NonNull private final Looper mLooper;
@NonNull private final VcnNetworkProvider mVcnNetworkProvider;
@NonNull private final FeatureFlags mFeatureFlags;
- @NonNull private final android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags;
private final boolean mIsInTestMode;
public VcnContext(
@@ -49,7 +49,6 @@
// Auto-generated class
mFeatureFlags = new FeatureFlagsImpl();
- mCoreNetFeatureFlags = new android.net.platform.flags.FeatureFlagsImpl();
}
@NonNull
@@ -76,7 +75,16 @@
}
public boolean isFlagIpSecTransformStateEnabled() {
- return mCoreNetFeatureFlags.ipsecTransformState();
+ // TODO: b/328844044: Ideally this code should gate the behavior by checking the
+ // android.net.platform.flags.ipsec_transform_state flag but that flag is not accessible
+ // right now. We should either update the code when the flag is accessible or remove the
+ // legacy behavior after VIC SDK finalization
+ try {
+ new IpSecTransformState.Builder();
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
}
@NonNull
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index ed9fa65..3619253 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -16,8 +16,10 @@
package com.android.server.vcn.routeselection;
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -38,6 +40,10 @@
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.server.vcn.VcnContext;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.util.BitSet;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@@ -56,8 +62,51 @@
public class IpSecPacketLossDetector extends NetworkMetricMonitor {
private static final String TAG = IpSecPacketLossDetector.class.getSimpleName();
+ private static final int PACKET_LOSS_PERCENT_UNAVAILABLE = -1;
+
+ // Ignore the packet loss detection result if the expected packet number is smaller than 10.
+ // Solarwinds NPM uses 10 ICMP echos to calculate packet loss rate (as per
+ // https://thwack.solarwinds.com/products/network-performance-monitor-npm/f/forum/63829/how-is-packet-loss-calculated)
@VisibleForTesting(visibility = Visibility.PRIVATE)
- static final int PACKET_LOSS_UNAVALAIBLE = -1;
+ static final int MIN_VALID_EXPECTED_RX_PACKET_NUM = 10;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"PACKET_LOSS_"},
+ value = {
+ PACKET_LOSS_RATE_VALID,
+ PACKET_LOSS_RATE_INVALID,
+ PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP,
+ })
+ @Target({ElementType.TYPE_USE})
+ private @interface PacketLossResultType {}
+
+ /** Indicates a valid packet loss rate is available */
+ private static final int PACKET_LOSS_RATE_VALID = 0;
+
+ /**
+ * Indicates that the detector cannot get a valid packet loss rate due to one of the following
+ * reasons:
+ *
+ * <ul>
+ * <li>The replay window did not proceed and thus all packets might have been delivered out of
+ * order
+ * <li>The expected received packet number is too small and thus the detection result is not
+ * reliable
+ * <li>There are unexpected errors
+ * </ul>
+ */
+ private static final int PACKET_LOSS_RATE_INVALID = 1;
+
+ /**
+ * The sequence number increase is unusually large and might be caused an intentional leap on
+ * the server's downlink
+ *
+ * <p>Inbound sequence number will not always increase consecutively. During load balancing the
+ * server might add a big leap on the sequence number intentionally. In such case a high packet
+ * loss rate does not always indicate a lossy network
+ */
+ private static final int PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP = 2;
// For VoIP, losses between 5% and 10% of the total packet stream will affect the quality
// significantly (as per "Computer Networking for LANS to WANS: Hardware, Software and
@@ -68,8 +117,12 @@
private static final int POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT = 20;
+ // By default, there's no maximum limit enforced
+ private static final int MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED = -1;
+
private long mPollIpSecStateIntervalMs;
- private final int mPacketLossRatePercentThreshold;
+ private int mPacketLossRatePercentThreshold;
+ private int mMaxSeqNumIncreasePerSecond;
@NonNull private final Handler mHandler;
@NonNull private final PowerManager mPowerManager;
@@ -108,6 +161,7 @@
mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
+ mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
// Register for system broadcasts to monitor idle mode change
final IntentFilter intentFilter = new IntentFilter();
@@ -172,6 +226,24 @@
return IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT;
}
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static int getMaxSeqNumIncreasePerSecond(@Nullable PersistableBundleWrapper carrierConfig) {
+ int maxSeqNumIncrease = MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED;
+ if (Flags.handleSeqNumLeap() && carrierConfig != null) {
+ maxSeqNumIncrease =
+ carrierConfig.getInt(
+ VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY,
+ MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED);
+ }
+
+ if (maxSeqNumIncrease < MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) {
+ logE(TAG, "Invalid value of MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY " + maxSeqNumIncrease);
+ return MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED;
+ }
+
+ return maxSeqNumIncrease;
+ }
+
@Override
protected void onSelectedUnderlyingNetworkChanged() {
if (!isSelectedUnderlyingNetwork()) {
@@ -207,6 +279,11 @@
// The already scheduled event will not be affected. The followup events will be scheduled
// with the new interval
mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
+
+ if (Flags.handleSeqNumLeap()) {
+ mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
+ mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
+ }
}
@Override
@@ -307,30 +384,40 @@
return;
}
- final int packetLossRate =
+ final PacketLossCalculationResult calculateResult =
mPacketLossCalculator.getPacketLossRatePercentage(
- mLastIpSecTransformState, state, getLogPrefix());
+ mLastIpSecTransformState,
+ state,
+ mMaxSeqNumIncreasePerSecond,
+ getLogPrefix());
- if (packetLossRate == PACKET_LOSS_UNAVALAIBLE) {
+ if (calculateResult.getResultType() == PACKET_LOSS_RATE_INVALID) {
return;
}
final String logMsg =
- "packetLossRate: "
- + packetLossRate
+ "calculateResult: "
+ + calculateResult
+ "% in the past "
+ (state.getTimestampMillis()
- mLastIpSecTransformState.getTimestampMillis())
+ "ms";
mLastIpSecTransformState = state;
- if (packetLossRate < mPacketLossRatePercentThreshold) {
+ if (calculateResult.getPacketLossRatePercent() < mPacketLossRatePercentThreshold) {
logV(logMsg);
+
+ // In both "valid" or "unusual_seq_num_leap" cases, notify that the network has passed
+ // the validation
onValidationResultReceivedInternal(false /* isFailed */);
} else {
logInfo(logMsg);
- onValidationResultReceivedInternal(true /* isFailed */);
+ if (calculateResult.getResultType() == PACKET_LOSS_RATE_VALID) {
+ onValidationResultReceivedInternal(true /* isFailed */);
+ }
+
+ // In both "valid" or "unusual_seq_num_leap" cases, trigger network validation
if (Flags.validateNetworkOnIpsecLoss()) {
// Trigger re-validation of the underlying network; if it fails, the VCN will
// attempt to migrate away.
@@ -343,9 +430,10 @@
@VisibleForTesting(visibility = Visibility.PRIVATE)
public static class PacketLossCalculator {
/** Calculate the packet loss rate between two timestamps */
- public int getPacketLossRatePercentage(
+ public PacketLossCalculationResult getPacketLossRatePercentage(
@NonNull IpSecTransformState oldState,
@NonNull IpSecTransformState newState,
+ int maxSeqNumIncreasePerSecond,
String logPrefix) {
logVIpSecTransform("oldState", oldState, logPrefix);
logVIpSecTransform("newState", newState, logPrefix);
@@ -359,7 +447,23 @@
if (oldSeqHi == newSeqHi || newSeqHi < replayWindowSize) {
// The replay window did not proceed and all packets might have been delivered out
// of order
- return PACKET_LOSS_UNAVALAIBLE;
+ return PacketLossCalculationResult.invalid();
+ }
+
+ boolean isUnusualSeqNumLeap = false;
+
+ // Handle sequence number leap
+ if (Flags.handleSeqNumLeap()
+ && maxSeqNumIncreasePerSecond != MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) {
+ final long timeDiffMillis =
+ newState.getTimestampMillis() - oldState.getTimestampMillis();
+ final long maxSeqNumIncrease = timeDiffMillis * maxSeqNumIncreasePerSecond / 1000;
+
+ // Sequence numbers are unsigned 32-bit values. If maxSeqNumIncrease overflows,
+ // isUnusualSeqNumLeap can never be true.
+ if (maxSeqNumIncrease >= 0 && newSeqHi - oldSeqHi >= maxSeqNumIncrease) {
+ isUnusualSeqNumLeap = true;
+ }
}
// Get the expected packet count by assuming there is no packet loss. In this case, SA
@@ -381,15 +485,23 @@
+ " actualPktCntDiff: "
+ actualPktCntDiff);
+ if (Flags.handleSeqNumLeap() && expectedPktCntDiff < MIN_VALID_EXPECTED_RX_PACKET_NUM) {
+ // The sample size is too small to ensure a reliable detection result
+ return PacketLossCalculationResult.invalid();
+ }
+
if (expectedPktCntDiff < 0
|| expectedPktCntDiff == 0
|| actualPktCntDiff < 0
|| actualPktCntDiff > expectedPktCntDiff) {
logWtf(TAG, "Impossible values for expectedPktCntDiff or" + " actualPktCntDiff");
- return PACKET_LOSS_UNAVALAIBLE;
+ return PacketLossCalculationResult.invalid();
}
- return 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
+ final int percent = 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
+ return isUnusualSeqNumLeap
+ ? PacketLossCalculationResult.unusualSeqNumLeap(percent)
+ : PacketLossCalculationResult.valid(percent);
}
}
@@ -409,4 +521,64 @@
private static long getPacketCntInReplayWindow(@NonNull IpSecTransformState state) {
return BitSet.valueOf(state.getReplayBitmap()).cardinality();
}
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static class PacketLossCalculationResult {
+ @PacketLossResultType private final int mResultType;
+ private final int mPacketLossRatePercent;
+
+ private PacketLossCalculationResult(@PacketLossResultType int type, int percent) {
+ mResultType = type;
+ mPacketLossRatePercent = percent;
+ }
+
+ /** Construct an instance that contains a valid packet loss rate */
+ public static PacketLossCalculationResult valid(int percent) {
+ return new PacketLossCalculationResult(PACKET_LOSS_RATE_VALID, percent);
+ }
+
+ /** Construct an instance indicating the inability to get a valid packet loss rate */
+ public static PacketLossCalculationResult invalid() {
+ return new PacketLossCalculationResult(
+ PACKET_LOSS_RATE_INVALID, PACKET_LOSS_PERCENT_UNAVAILABLE);
+ }
+
+ /** Construct an instance indicating that there is an unusual sequence number leap */
+ public static PacketLossCalculationResult unusualSeqNumLeap(int percent) {
+ return new PacketLossCalculationResult(PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP, percent);
+ }
+
+ @PacketLossResultType
+ public int getResultType() {
+ return mResultType;
+ }
+
+ public int getPacketLossRatePercent() {
+ return mPacketLossRatePercent;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mResultType, mPacketLossRatePercent);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (!(other instanceof PacketLossCalculationResult)) {
+ return false;
+ }
+
+ final PacketLossCalculationResult rhs = (PacketLossCalculationResult) other;
+ return mResultType == rhs.mResultType
+ && mPacketLossRatePercent == rhs.mPacketLossRatePercent;
+ }
+
+ @Override
+ public String toString() {
+ return "mResultType: "
+ + mResultType
+ + " | mPacketLossRatePercent: "
+ + mPacketLossRatePercent;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
index a1b212f..b9b1060 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
@@ -272,6 +272,11 @@
}
}
+ protected static void logE(String className, String msgWithPrefix) {
+ Slog.w(className, msgWithPrefix);
+ LOCAL_LOG.log("[ERROR ] " + className + msgWithPrefix);
+ }
+
protected static void logWtf(String className, String msgWithPrefix) {
Slog.wtf(className, msgWithPrefix);
LOCAL_LOG.log("[WTF ] " + className + msgWithPrefix);
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index b33fa6f..f82ff67 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -101,7 +101,9 @@
}
@Override
- public void registerVibratorController(IVibratorController controller) {
+ public void registerVibratorController(@NonNull IVibratorController controller) {
+ Objects.requireNonNull(controller);
+
synchronized (mLock) {
mVibratorControllerHolder.setVibratorController(controller);
}
@@ -134,6 +136,7 @@
public void setVibrationParams(@SuppressLint("ArrayReturn") VibrationParam[] params,
@NonNull IVibratorController token) {
Objects.requireNonNull(token);
+ requireContainsNoNullElement(params);
synchronized (mLock) {
if (mVibratorControllerHolder.getVibratorController() == null) {
@@ -148,6 +151,13 @@
+ "controller doesn't match the registered one. " + this);
return;
}
+ if (params == null) {
+ // Adaptive haptics scales cannot be set to null. Ignoring request.
+ Slog.d(TAG,
+ "New vibration params received but are null. New vibration "
+ + "params ignored.");
+ return;
+ }
updateAdaptiveHapticsScales(params);
recordUpdateVibrationParams(params, /* fromRequest= */ false);
@@ -181,6 +191,7 @@
public void onRequestVibrationParamsComplete(
@NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result) {
Objects.requireNonNull(requestToken);
+ requireContainsNoNullElement(result);
synchronized (mLock) {
if (mVibrationParamRequest == null) {
@@ -202,6 +213,13 @@
long latencyMs = SystemClock.uptimeMillis() - mVibrationParamRequest.uptimeMs;
mStatsLogger.logVibrationParamRequestLatency(mVibrationParamRequest.uid, latencyMs);
+ if (result == null) {
+ Slog.d(TAG,
+ "New vibration params received but are null. New vibration "
+ + "params ignored.");
+ return;
+ }
+
updateAdaptiveHapticsScales(result);
endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ false);
recordUpdateVibrationParams(result, /* fromRequest= */ true);
@@ -401,10 +419,9 @@
*
* @param params the new vibration params.
*/
- private void updateAdaptiveHapticsScales(@Nullable VibrationParam[] params) {
- if (params == null) {
- return;
- }
+ private void updateAdaptiveHapticsScales(@NonNull VibrationParam[] params) {
+ Objects.requireNonNull(params);
+
for (VibrationParam param : params) {
if (param.getTag() != VibrationParam.scale) {
Slog.e(TAG, "Unsupported vibration param: " + param);
@@ -448,11 +465,10 @@
mVibrationScaler.updateAdaptiveHapticsScale(usageHint, scale);
}
- private void recordUpdateVibrationParams(@Nullable VibrationParam[] params,
+ private void recordUpdateVibrationParams(@NonNull VibrationParam[] params,
boolean fromRequest) {
- if (params == null) {
- return;
- }
+ Objects.requireNonNull(params);
+
VibrationParamsRecords.Operation operation =
fromRequest ? VibrationParamsRecords.Operation.PULL
: VibrationParamsRecords.Operation.PUSH;
@@ -474,6 +490,13 @@
VibrationParamsRecords.Operation.CLEAR, createTime, typesMask, NO_SCALE));
}
+ private void requireContainsNoNullElement(VibrationParam[] params) {
+ if (ArrayUtils.contains(params, null)) {
+ throw new IllegalArgumentException(
+ "Invalid vibration params received: null values are not permitted.");
+ }
+ }
+
/**
* Keep records of {@link VibrationParam} values received by this service from a registered
* {@link VibratorController} and provide debug information for this service.
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 5e34596..a821f545 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -35,6 +35,7 @@
import android.provider.Settings;
import android.util.AndroidRuntimeException;
import android.util.Log;
+import android.util.Slog;
import android.webkit.UserPackage;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
@@ -201,6 +202,7 @@
ActivityManager.getService().killPackageDependents(packageName,
UserHandle.USER_ALL);
} catch (RemoteException e) {
+ Slog.wtf(TAG, "failed to call killPackageDependents for " + packageName, e);
}
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 532ff98..dcf20f9 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -186,9 +186,12 @@
}
onWebViewProviderChanged(mCurrentWebViewPackage);
}
+ } catch (WebViewPackageMissingException e) {
+ Slog.e(TAG, "Could not find valid WebView package to create relro with", e);
} catch (Throwable t) {
- // Log and discard errors at this stage as we must not crash the system server.
- Slog.e(TAG, "error preparing webview provider from system server", t);
+ // We don't know a case when this should happen but we log and discard errors at this
+ // stage as we must not crash the system server.
+ Slog.wtf(TAG, "error preparing webview provider from system server", t);
}
if (getCurrentWebViewPackage() == null) {
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index fb338ba..993597e 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -247,9 +247,12 @@
attemptRepair();
}
+ } catch (WebViewPackageMissingException e) {
+ Slog.e(TAG, "Could not find valid WebView package to create relro with", e);
} catch (Throwable t) {
- // Log and discard errors at this stage as we must not crash the system server.
- Slog.e(TAG, "error preparing webview provider from system server", t);
+ // We don't know a case when this should happen but we log and discard errors at this
+ // stage as we must not crash the system server.
+ Slog.wtf(TAG, "error preparing webview provider from system server", t);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
index 9647a62..521f64f 100644
--- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -389,7 +389,11 @@
return mCheckedOptions;
}
- /** Returns the {@link Runnable} object to clear options Animation. */
+ /**
+ * Returns the {@link Runnable} object to clear options Animation. Note that the runnable
+ * should not be executed inside a lock because the implementation of runnable holds window
+ * manager's lock.
+ */
@Nullable
public Runnable getClearOptionsAnimationRunnable() {
return mClearOptionsAnimation;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 83745ed..54e932a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -658,6 +658,8 @@
*/
private CompatDisplayInsets mCompatDisplayInsets;
+ private final TaskFragment.ConfigOverrideHint mResolveConfigHint;
+
private static ConstrainDisplayApisConfig sConstrainDisplayApisConfig;
boolean pendingVoiceInteractionStart; // Waiting for activity-invoked voice session
@@ -819,6 +821,12 @@
@Nullable
private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
+ // Bounds populated in resolveAspectRatioRestriction when this activity is letterboxed for
+ // aspect ratio. If not null, they are used as parent container in
+ // resolveSizeCompatModeConfiguration and in a constructor of CompatDisplayInsets.
+ @Nullable
+ private Rect mLetterboxBoundsForAspectRatio;
+
// Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
// requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
// and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
@@ -2110,6 +2118,10 @@
mLetterboxUiController = new LetterboxUiController(mWmService, this);
mCameraCompatControlEnabled = mWmService.mContext.getResources()
.getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled);
+ mResolveConfigHint = new TaskFragment.ConfigOverrideHint();
+ mResolveConfigHint.mUseLegacyInsetsForStableBounds =
+ mWmService.mFlags.mInsetsDecoupledConfiguration
+ && !info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED);
mTargetSdk = info.applicationInfo.targetSdkVersion;
@@ -4260,7 +4272,8 @@
PendingIntentRecord rec = apr.get();
if (rec != null) {
mAtmService.mPendingIntentController.cancelIntentSender(rec,
- false /* cleanActivity */);
+ false /* cleanActivity */,
+ PendingIntentRecord.CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED);
}
}
pendingResults = null;
@@ -4478,6 +4491,7 @@
getDisplayContent().mOpeningApps.remove(this);
getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this);
mWmService.mSnapshotController.onAppRemoved(this);
+ mAtmService.mStartingProcessActivities.remove(this);
mTaskSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
mTaskSupervisor.mStoppingActivities.remove(this);
@@ -6220,6 +6234,10 @@
}
boolean shouldBeVisible() {
+ return shouldBeVisible(false /* ignoringKeyguard */);
+ }
+
+ boolean shouldBeVisible(boolean ignoringKeyguard) {
final Task task = getTask();
if (task == null) {
return false;
@@ -6227,7 +6245,7 @@
final boolean behindOccludedContainer = !task.shouldBeVisible(null /* starting */)
|| task.getOccludingActivityAbove(this) != null;
- return shouldBeVisible(behindOccludedContainer, false /* ignoringKeyguard */);
+ return shouldBeVisible(behindOccludedContainer, ignoringKeyguard);
}
void makeVisibleIfNeeded(ActivityRecord starting, boolean reportToClient) {
@@ -8430,10 +8448,15 @@
fullConfig.windowConfiguration.getRotation());
}
+ final Rect letterboxedContainerBounds =
+ mLetterboxBoundsForFixedOrientationAndAspectRatio != null
+ ? mLetterboxBoundsForFixedOrientationAndAspectRatio
+ : mLetterboxBoundsForAspectRatio;
// The role of CompatDisplayInsets is like the override bounds.
mCompatDisplayInsets =
new CompatDisplayInsets(
- mDisplayContent, this, mLetterboxBoundsForFixedOrientationAndAspectRatio);
+ mDisplayContent, this, letterboxedContainerBounds,
+ mResolveConfigHint.mUseLegacyInsetsForStableBounds);
}
private void clearSizeCompatModeAttributes() {
@@ -8505,6 +8528,7 @@
mIsAspectRatioApplied = false;
mIsEligibleForFixedOrientationLetterbox = false;
mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
+ mLetterboxBoundsForAspectRatio = null;
// Can't use resolvedConfig.windowConfiguration.getWindowingMode() because it can be
// different from windowing mode of the task (PiP) during transition from fullscreen to PiP
@@ -8540,13 +8564,14 @@
// If the activity has requested override bounds, the configuration needs to be
// computed accordingly.
if (!matchParentBounds()) {
- getTaskFragment().computeConfigResourceOverrides(resolvedConfig,
- newParentConfiguration);
+ computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
}
// If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
- // are already calculated in resolveFixedOrientationConfiguration.
+ // are already calculated in resolveFixedOrientationConfiguration, or if in size compat
+ // mode, it should already be calculated in resolveSizeCompatModeConfiguration.
// Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
- } else if (!isLetterboxedForFixedOrientationAndAspectRatio()
+ }
+ if (!isLetterboxedForFixedOrientationAndAspectRatio() && !mInSizeCompatModeForBounds
&& !mLetterboxUiController.hasFullscreenOverride()) {
resolveAspectRatioRestriction(newParentConfiguration);
}
@@ -8630,16 +8655,15 @@
if (mDisplayContent == null) {
return;
}
- final Rect fullBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+ final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
int rotation = newParentConfiguration.windowConfiguration.getRotation();
if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
rotation = mDisplayContent.getRotation();
}
- if (!mWmService.mFlags.mInsetsDecoupledConfiguration
- || info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
+ if (!mResolveConfigHint.mUseLegacyInsetsForStableBounds
|| getCompatDisplayInsets() != null
- || isFloating(parentWindowingMode) || fullBounds == null
- || fullBounds.isEmpty() || rotation == ROTATION_UNDEFINED) {
+ || isFloating(parentWindowingMode) || parentAppBounds == null
+ || parentAppBounds.isEmpty() || rotation == ROTATION_UNDEFINED) {
// If the insets configuration decoupled logic is not enabled for the app, or the app
// already has a compat override, or the context doesn't contain enough info to
// calculate the override, skip the override.
@@ -8647,15 +8671,20 @@
}
// Override starts here.
- final Rect stableInsets = mDisplayContent.getDisplayPolicy().getDecorInsetsInfo(
- rotation, fullBounds.width(), fullBounds.height()).mOverrideConfigInsets;
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
+ : mDisplayContent.mBaseDisplayWidth;
+ final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
+ : mDisplayContent.mBaseDisplayHeight;
+ final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy()
+ .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets;
// This should be the only place override the configuration for ActivityRecord. Override
// the value if not calculated yet.
Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
if (outAppBounds == null || outAppBounds.isEmpty()) {
- inOutConfig.windowConfiguration.setAppBounds(fullBounds);
+ inOutConfig.windowConfiguration.setAppBounds(parentAppBounds);
outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- outAppBounds.inset(stableInsets);
+ outAppBounds.inset(nonDecorInsets);
}
float density = inOutConfig.densityDpi;
if (density == Configuration.DENSITY_DPI_UNDEFINED) {
@@ -8679,11 +8708,10 @@
// For the case of PIP transition and multi-window environment, the
// smallestScreenWidthDp is handled already. Override only if the app is in
// fullscreen.
- final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
DisplayInfo info = new DisplayInfo();
mDisplayContent.getDisplay().getDisplayInfo(info);
- mDisplayContent.computeSizeRanges(info, rotated, info.logicalWidth,
- info.logicalHeight, mDisplayContent.getDisplayMetrics().density,
+ mDisplayContent.computeSizeRanges(info, rotated, dw, dh,
+ mDisplayContent.getDisplayMetrics().density,
inOutConfig, true /* overrideConfig */);
}
@@ -8696,14 +8724,12 @@
}
}
- /**
- * @return The orientation to use to understand if reachability is enabled.
- */
- @Configuration.Orientation
- int getOrientationForReachability() {
- return mLetterboxUiController.hasInheritedLetterboxBehavior()
- ? mLetterboxUiController.getInheritedOrientation()
- : getRequestedConfigurationOrientation();
+ private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
+ @NonNull Configuration parentConfig) {
+ task.computeConfigResourceOverrides(resolvedConfig, parentConfig, mResolveConfigHint);
+ // Reset the temp info which should only take effect for the specified computation.
+ mResolveConfigHint.mTmpCompatInsets = null;
+ mResolveConfigHint.mTmpOverrideDisplayInfo = null;
}
/**
@@ -8846,7 +8872,7 @@
}
// Since bounds has changed, the configuration needs to be computed accordingly.
- getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
+ computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
// The position of configuration bounds were calculated in screen space because that is
// easier to resolve the relative position in parent container. However, if the activity is
@@ -8940,17 +8966,18 @@
* to compute the stable bounds.
* @param outStableBounds will store the stable bounds, which are the bounds with insets
* applied, if orientation is not respected when insets are applied.
- * Otherwise outStableBounds will be empty. Stable bounds should be used
- * to compute letterboxed bounds if orientation is not respected when
- * insets are applied.
+ * Stable bounds should be used to compute letterboxed bounds if
+ * orientation is not respected when insets are applied.
+ * @param outNonDecorBounds will store the non decor bounds, which are the bounds with non
+ * decor insets applied, like display cutout and nav bar.
*/
- private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outStableBounds) {
+ private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outStableBounds,
+ Rect outNonDecorBounds) {
outStableBounds.setEmpty();
if (mDisplayContent == null) {
return true;
}
- if (mWmService.mFlags.mInsetsDecoupledConfiguration
- && info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)) {
+ if (!mResolveConfigHint.mUseLegacyInsetsForStableBounds) {
// No insets should be considered any more.
return true;
}
@@ -8967,8 +8994,9 @@
? getFixedRotationTransformDisplayInfo()
: mDisplayContent.getDisplayInfo();
final Task task = getTask();
- task.calculateInsetFrames(mTmpBounds /* outNonDecorBounds */,
- outStableBounds /* outStableBounds */, parentBounds /* bounds */, di);
+ task.calculateInsetFrames(outNonDecorBounds /* outNonDecorBounds */,
+ outStableBounds /* outStableBounds */, parentBounds /* bounds */, di,
+ mResolveConfigHint.mUseLegacyInsetsForStableBounds);
final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
// If orientation does not match the orientation with insets applied, then a
@@ -8978,9 +9006,6 @@
// have the desired orientation.
final boolean orientationRespectedWithInsets = orientation == orientationWithInsets
|| orientationWithInsets == requestedOrientation;
- if (orientationRespectedWithInsets) {
- outStableBounds.setEmpty();
- }
return orientationRespectedWithInsets;
}
@@ -9006,9 +9031,10 @@
private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) {
final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
final Rect stableBounds = new Rect();
+ final Rect outNonDecorBounds = mTmpBounds;
// If orientation is respected when insets are applied, then stableBounds will be empty.
boolean orientationRespectedWithInsets =
- orientationRespectedWithInsets(parentBounds, stableBounds);
+ orientationRespectedWithInsets(parentBounds, stableBounds, outNonDecorBounds);
if (orientationRespectedWithInsets && handlesOrientationChangeFromDescendant(
getOverrideOrientation())) {
// No need to letterbox because of fixed orientation. Display will handle
@@ -9025,7 +9051,10 @@
final Rect resolvedBounds =
getResolvedOverrideConfiguration().windowConfiguration.getBounds();
- final int parentOrientation = newParentConfig.orientation;
+ final int stableBoundsOrientation = stableBounds.width() > stableBounds.height()
+ ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+ final int parentOrientation = mResolveConfigHint.mUseLegacyInsetsForStableBounds
+ ? stableBoundsOrientation : newParentConfig.orientation;
// If the activity requires a different orientation (either by override or activityInfo),
// make it fit the available bounds by scaling down its bounds.
@@ -9040,7 +9069,8 @@
}
final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
- if (compatDisplayInsets != null && !compatDisplayInsets.mIsInFixedOrientationLetterbox) {
+ if (compatDisplayInsets != null
+ && !compatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) {
// App prefers to keep its original size.
// If the size compat is from previous fixed orientation letterboxing, we may want to
// have fixed orientation letterbox again, otherwise it will show the size compat
@@ -9048,10 +9078,12 @@
return;
}
+ final Rect parentAppBounds = mResolveConfigHint.mUseLegacyInsetsForStableBounds
+ ? outNonDecorBounds : newParentConfig.windowConfiguration.getAppBounds();
// TODO(b/182268157): Explore using only one type of parentBoundsWithInsets, either app
// bounds or stable bounds to unify aspect ratio logic.
final Rect parentBoundsWithInsets = orientationRespectedWithInsets
- ? newParentConfig.windowConfiguration.getAppBounds() : stableBounds;
+ ? parentAppBounds : stableBounds;
final Rect containingBounds = new Rect();
final Rect containingBoundsWithInsets = new Rect();
// Need to shrink the containing bounds into a square because the parent orientation
@@ -9128,8 +9160,8 @@
// Calculate app bounds using fixed orientation bounds because they will be needed later
// for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
- getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(),
- newParentConfig, compatDisplayInsets);
+ mResolveConfigHint.mTmpCompatInsets = compatDisplayInsets;
+ computeConfigByResolveHint(getResolvedOverrideConfiguration(), newParentConfig);
mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds);
}
@@ -9170,8 +9202,9 @@
if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
// Compute the configuration based on the resolved bounds. If aspect ratio doesn't
// restrict, the bounds should be the requested override bounds.
- getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
- getFixedRotationTransformDisplayInfo());
+ mResolveConfigHint.mTmpOverrideDisplayInfo = getFixedRotationTransformDisplayInfo();
+ computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
+ mLetterboxBoundsForAspectRatio = new Rect(resolvedBounds);
}
}
@@ -9235,8 +9268,8 @@
// Use resolvedBounds to compute other override configurations such as appBounds. The bounds
// are calculated in compat container space. The actual position on screen will be applied
// later, so the calculation is simpler that doesn't need to involve offset from parent.
- getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
- compatDisplayInsets);
+ mResolveConfigHint.mTmpCompatInsets = compatDisplayInsets;
+ computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
// Use current screen layout as source because the size of app is independent to parent.
resolvedConfig.screenLayout = computeScreenLayout(
getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
@@ -10509,8 +10542,7 @@
if (!getTurnScreenOnFlag()) {
return false;
}
- final Task rootTask = getRootTask();
- return mCurrentLaunchCanTurnScreenOn && rootTask != null
+ return mCurrentLaunchCanTurnScreenOn
&& mTaskSupervisor.getKeyguardController().checkKeyguardVisibility(this);
}
@@ -10736,10 +10768,10 @@
/** Whether the {@link Task} windowingMode represents a floating window*/
final boolean mIsFloating;
/**
- * Whether is letterboxed because of fixed orientation when the unresizable activity is
- * first shown.
+ * Whether is letterboxed because of fixed orientation or aspect ratio when
+ * the unresizable activity is first shown.
*/
- final boolean mIsInFixedOrientationLetterbox;
+ final boolean mIsInFixedOrientationOrAspectRatioLetterbox;
/**
* The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It
* is used to compute the appBounds.
@@ -10754,7 +10786,7 @@
/** Constructs the environment to simulate the bounds behavior of the given container. */
CompatDisplayInsets(DisplayContent display, ActivityRecord container,
- @Nullable Rect fixedOrientationBounds) {
+ @Nullable Rect letterboxedContainerBounds, boolean useOverrideInsets) {
mOriginalRotation = display.getRotation();
mIsFloating = container.getWindowConfiguration().tasksAreFloating();
mOriginalRequestedOrientation = container.getRequestedConfigurationOrientation();
@@ -10769,22 +10801,22 @@
mNonDecorInsets[rotation] = emptyRect;
mStableInsets[rotation] = emptyRect;
}
- mIsInFixedOrientationLetterbox = false;
+ mIsInFixedOrientationOrAspectRatioLetterbox = false;
return;
}
final Task task = container.getTask();
- mIsInFixedOrientationLetterbox = fixedOrientationBounds != null;
+ mIsInFixedOrientationOrAspectRatioLetterbox = letterboxedContainerBounds != null;
// Store the bounds of the Task for the non-resizable activity to use in size compat
// mode so that the activity will not be resized regardless the windowing mode it is
// currently in.
- // When an activity needs to be letterboxed because of fixed orientation, use fixed
- // orientation bounds instead of task bounds since the activity will be displayed
- // within these even if it is in size compat mode.
- final Rect filledContainerBounds = mIsInFixedOrientationLetterbox
- ? fixedOrientationBounds
+ // When an activity needs to be letterboxed because of fixed orientation or aspect
+ // ratio, use resolved bounds instead of task bounds since the activity will be
+ // displayed within these even if it is in size compat mode.
+ final Rect filledContainerBounds = mIsInFixedOrientationOrAspectRatioLetterbox
+ ? letterboxedContainerBounds
: task != null ? task.getBounds() : display.getBounds();
final int filledContainerRotation = task != null
? task.getConfiguration().windowConfiguration.getRotation()
@@ -10806,8 +10838,13 @@
final int dh = rotated ? display.mBaseDisplayWidth : display.mBaseDisplayHeight;
final DisplayPolicy.DecorInsets.Info decorInfo =
policy.getDecorInsetsInfo(rotation, dw, dh);
- mNonDecorInsets[rotation].set(decorInfo.mNonDecorInsets);
- mStableInsets[rotation].set(decorInfo.mConfigInsets);
+ if (useOverrideInsets) {
+ mStableInsets[rotation].set(decorInfo.mOverrideConfigInsets);
+ mNonDecorInsets[rotation].set(decorInfo.mOverrideNonDecorInsets);
+ } else {
+ mStableInsets[rotation].set(decorInfo.mConfigInsets);
+ mNonDecorInsets[rotation].set(decorInfo.mNonDecorInsets);
+ }
if (unfilledContainerBounds == null) {
continue;
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 182e1c1..2cccd33 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -523,8 +523,11 @@
void onActivityLaunched(TaskInfo taskInfo, ActivityRecord r) {
final SparseArray<ActivityInterceptorCallback> callbacks =
mService.getActivityInterceptorCallbacks();
- ActivityInterceptorCallback.ActivityInterceptorInfo info = getInterceptorInfo(
- r::clearOptionsAnimationForSiblings);
+ final ActivityInterceptorCallback.ActivityInterceptorInfo info = getInterceptorInfo(() -> {
+ synchronized (mService.mGlobalLock) {
+ r.clearOptionsAnimationForSiblings();
+ }
+ });
for (int i = 0; i < callbacks.size(); i++) {
final ActivityInterceptorCallback callback = callbacks.valueAt(i);
callback.onActivityLaunched(taskInfo, r.info, info);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 2c39c58..08baf3b 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2524,7 +2524,9 @@
// If the caller has asked not to resume at this point, we make note
// of this in the record so that we can skip it when trying to find
// the top running activity.
- if (!r.showToCurrentUser() || mLaunchTaskBehind) {
+ final boolean canShowActivity = r.showToCurrentUser();
+ if (!canShowActivity) Slog.w(TAG, "Can't resume non-current user r=" + r);
+ if (!canShowActivity || mLaunchTaskBehind) {
r.delayedResume = true;
mDoResume = false;
} else {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a5687e6..a739e57 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -390,6 +390,9 @@
final VisibleActivityProcessTracker mVisibleActivityProcessTracker;
+ /** The starting activities which are waiting for their processes to attach. */
+ final ArrayList<ActivityRecord> mStartingProcessActivities = new ArrayList<>();
+
/* Global service lock used by the package the owns this service. */
final WindowManagerGlobalLock mGlobalLock = new WindowManagerGlobalLock();
/**
@@ -883,6 +886,7 @@
mRecentTasks.onSystemReadyLocked();
mTaskSupervisor.onSystemReady();
mActivityClientController.onSystemReady();
+ mAppWarnings.onSystemReady();
// TODO(b/258792202) Cleanup once ASM is ready to launch
ActivitySecurityModelFeatureFlags.initialize(mContext.getMainExecutor());
mGrammaticalManagerInternal = LocalServices.getService(
@@ -3787,7 +3791,8 @@
// Continue the pausing process after entering pip.
if (r.isState(PAUSING) && r.mPauseSchedulePendingForPip) {
r.getTask().schedulePauseActivity(r, false /* userLeaving */,
- false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip");
+ false /* pauseImmediately */, true /* autoEnteringPip */,
+ "auto-pip");
}
r.mAutoEnteringPip = false;
}
@@ -4323,6 +4328,9 @@
if (mDemoteTopAppReasons != 0) {
pw.println(" mDemoteTopAppReasons=" + mDemoteTopAppReasons);
}
+ if (!mStartingProcessActivities.isEmpty()) {
+ pw.println(" mStartingProcessActivities=" + mStartingProcessActivities);
+ }
}
if (!printedAnything) {
@@ -5170,6 +5178,13 @@
void startProcessAsync(ActivityRecord activity, boolean knownToBeDead, boolean isTop,
String hostingType) {
+ if (!mStartingProcessActivities.contains(activity)) {
+ mStartingProcessActivities.add(activity);
+ } else if (mProcessNames.get(
+ activity.processName, activity.info.applicationInfo.uid) != null) {
+ // The process is already starting. Wait for it to attach.
+ return;
+ }
try {
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "dispatchingStartProcess:"
@@ -6166,7 +6181,20 @@
@Override
public void onProcessRemoved(String name, int uid) {
synchronized (mGlobalLockWithoutBoost) {
- mProcessNames.remove(name, uid);
+ final WindowProcessController proc = mProcessNames.remove(name, uid);
+ if (proc != null && !mStartingProcessActivities.isEmpty()) {
+ for (int i = mStartingProcessActivities.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = mStartingProcessActivities.get(i);
+ if (uid == r.info.applicationInfo.uid && name.equals(r.processName)) {
+ Slog.w(TAG, proc + " is removed with pending start " + r);
+ mStartingProcessActivities.remove(i);
+ // If visible, finish it to avoid getting stuck on screen.
+ if (r.isVisibleRequested()) {
+ r.finishIfPossible("starting-proc-removed", false /* oomAdj */);
+ }
+ }
+ }
+ }
}
}
@@ -6326,7 +6354,7 @@
public void onPackageDataCleared(String name, int userId) {
synchronized (mGlobalLock) {
mCompatModePackages.handlePackageDataClearedLocked(name);
- mAppWarnings.onPackageDataCleared(name);
+ mAppWarnings.onPackageDataCleared(name, userId);
mPackageConfigPersister.onPackageDataCleared(name, userId);
}
}
@@ -6334,7 +6362,7 @@
@Override
public void onPackageUninstalled(String name, int userId) {
synchronized (mGlobalLock) {
- mAppWarnings.onPackageUninstalled(name);
+ mAppWarnings.onPackageUninstalled(name, userId);
mCompatModePackages.handlePackageUninstalledLocked(name);
mPackageConfigPersister.onPackageUninstall(name, userId);
}
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index ad5f442..9fd543f 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -16,8 +16,14 @@
package com.android.server.wm;
+import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
+import static android.os.UserManager.isHeadlessSystemUserMode;
+import static android.os.UserManager.isVisibleBackgroundUsersEnabled;
+
import android.annotation.NonNull;
import android.annotation.UiThread;
+import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
@@ -26,17 +32,21 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.DisplayMetrics;
+import android.util.Pair;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
@@ -44,6 +54,8 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.IoThread;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -65,19 +77,30 @@
public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04;
public static final int FLAG_HIDE_DEPRECATED_ABI = 0x08;
+ /**
+ * Map of package flags for each user.
+ * Key: {@literal Pair<userId, packageName>}
+ * Value: Flags
+ */
@GuardedBy("mPackageFlags")
- private final ArrayMap<String, Integer> mPackageFlags = new ArrayMap<>();
+ private final ArrayMap<Pair<Integer, String>, Integer> mPackageFlags = new ArrayMap<>();
private final ActivityTaskManagerService mAtm;
- private final Context mUiContext;
private final WriteConfigTask mWriteConfigTask;
private final UiHandler mUiHandler;
private final AtomicFile mConfigFile;
- private UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
- private UnsupportedCompileSdkDialog mUnsupportedCompileSdkDialog;
- private DeprecatedTargetSdkVersionDialog mDeprecatedTargetSdkVersionDialog;
- private DeprecatedAbiDialog mDeprecatedAbiDialog;
+ private UserManagerInternal mUserManagerInternal;
+
+ /**
+ * Maps of app warning dialogs for each user.
+ * Key: userId
+ * Value: The warning dialog for specific user
+ */
+ private SparseArray<UnsupportedDisplaySizeDialog> mUnsupportedDisplaySizeDialogs;
+ private SparseArray<UnsupportedCompileSdkDialog> mUnsupportedCompileSdkDialogs;
+ private SparseArray<DeprecatedTargetSdkVersionDialog> mDeprecatedTargetSdkVersionDialogs;
+ private SparseArray<DeprecatedAbiDialog> mDeprecatedAbiDialogs;
/** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
private final ArraySet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities =
@@ -92,12 +115,35 @@
public AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler,
Handler uiHandler, File systemDir) {
mAtm = atm;
- mUiContext = uiContext;
mWriteConfigTask = new WriteConfigTask();
mUiHandler = new UiHandler(uiHandler.getLooper());
mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME), "warnings-config");
+ }
+ /**
+ * Called when ActivityManagerService receives its systemReady call during boot.
+ */
+ void onSystemReady() {
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
readConfigFromFileAmsThread();
+
+ if (!isVisibleBackgroundUsersEnabled()) {
+ return;
+ }
+
+ mUserManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ // Ignore profile user.
+ if (!user.isFull()) {
+ return;
+ }
+ // Dismiss all warnings and clear all package flags for the user.
+ mUiHandler.hideDialogsForPackage(/* name= */ null, user.id);
+ clearAllPackageFlagsForUser(user.id);
+ }
+ });
}
/**
@@ -227,18 +273,20 @@
* Called by ActivityManagerService when package data has been cleared.
*
* @param name the package whose data has been cleared
+ * @param userId the user where the package resides.
*/
- public void onPackageDataCleared(String name) {
- removePackageAndHideDialogs(name);
+ public void onPackageDataCleared(String name, int userId) {
+ removePackageAndHideDialogs(name, userId);
}
/**
* Called by ActivityManagerService when a package has been uninstalled.
*
* @param name the package that has been uninstalled
+ * @param userId the user where the package resides.
*/
- public void onPackageUninstalled(String name) {
- removePackageAndHideDialogs(name);
+ public void onPackageUninstalled(String name, int userId) {
+ removePackageAndHideDialogs(name, userId);
}
/**
@@ -251,11 +299,24 @@
/**
* Does what it says on the tin.
*/
- private void removePackageAndHideDialogs(String name) {
- mUiHandler.hideDialogsForPackage(name);
+ private void removePackageAndHideDialogs(String name, int userId) {
+ // Per-user AppWarnings only affects the behavior of the devices that enable the visible
+ // background users.
+ // To preserve existing behavior of the other devices, handle AppWarnings as a system user
+ // regardless of the actual user.
+ if (!isVisibleBackgroundUsersEnabled()) {
+ userId = USER_SYSTEM;
+ } else {
+ // If the userId is of a profile, use the parent user ID,
+ // since the warning dialogs and the flags for a package are handled per profile group.
+ userId = mUserManagerInternal.getProfileParentId(userId);
+ }
+
+ mUiHandler.hideDialogsForPackage(name, userId);
synchronized (mPackageFlags) {
- if (mPackageFlags.remove(name) != null) {
+ final Pair<Integer, String> packageKey = Pair.create(userId, name);
+ if (mPackageFlags.remove(packageKey) != null) {
mWriteConfigTask.schedule();
}
}
@@ -268,10 +329,14 @@
*/
@UiThread
private void hideUnsupportedDisplaySizeDialogUiThread() {
- if (mUnsupportedDisplaySizeDialog != null) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
+ if (mUnsupportedDisplaySizeDialogs == null) {
+ return;
}
+
+ for (int i = 0; i < mUnsupportedDisplaySizeDialogs.size(); i++) {
+ mUnsupportedDisplaySizeDialogs.valueAt(i).dismiss();
+ }
+ mUnsupportedDisplaySizeDialogs.clear();
}
/**
@@ -282,16 +347,24 @@
* @param ar record for the activity that triggered the warning
*/
@UiThread
- private void showUnsupportedDisplaySizeDialogUiThread(ActivityRecord ar) {
- if (mUnsupportedDisplaySizeDialog != null) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
+ private void showUnsupportedDisplaySizeDialogUiThread(@NonNull ActivityRecord ar) {
+ final int userId = getUserIdForActivity(ar);
+ UnsupportedDisplaySizeDialog unsupportedDisplaySizeDialog;
+ if (mUnsupportedDisplaySizeDialogs != null) {
+ unsupportedDisplaySizeDialog = mUnsupportedDisplaySizeDialogs.get(userId);
+ if (unsupportedDisplaySizeDialog != null) {
+ unsupportedDisplaySizeDialog.dismiss();
+ mUnsupportedDisplaySizeDialogs.remove(userId);
+ }
}
- if (ar != null && !hasPackageFlag(
- ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) {
- mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
- AppWarnings.this, mUiContext, ar.info.applicationInfo);
- mUnsupportedDisplaySizeDialog.show();
+ if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) {
+ unsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
+ AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId);
+ unsupportedDisplaySizeDialog.show();
+ if (mUnsupportedDisplaySizeDialogs == null) {
+ mUnsupportedDisplaySizeDialogs = new SparseArray<>();
+ }
+ mUnsupportedDisplaySizeDialogs.put(userId, unsupportedDisplaySizeDialog);
}
}
@@ -303,16 +376,24 @@
* @param ar record for the activity that triggered the warning
*/
@UiThread
- private void showUnsupportedCompileSdkDialogUiThread(ActivityRecord ar) {
- if (mUnsupportedCompileSdkDialog != null) {
- mUnsupportedCompileSdkDialog.dismiss();
- mUnsupportedCompileSdkDialog = null;
+ private void showUnsupportedCompileSdkDialogUiThread(@NonNull ActivityRecord ar) {
+ final int userId = getUserIdForActivity(ar);
+ UnsupportedCompileSdkDialog unsupportedCompileSdkDialog;
+ if (mUnsupportedCompileSdkDialogs != null) {
+ unsupportedCompileSdkDialog = mUnsupportedCompileSdkDialogs.get(userId);
+ if (unsupportedCompileSdkDialog != null) {
+ unsupportedCompileSdkDialog.dismiss();
+ mUnsupportedCompileSdkDialogs.remove(userId);
+ }
}
- if (ar != null && !hasPackageFlag(
- ar.packageName, FLAG_HIDE_COMPILE_SDK)) {
- mUnsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog(
- AppWarnings.this, mUiContext, ar.info.applicationInfo);
- mUnsupportedCompileSdkDialog.show();
+ if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_COMPILE_SDK)) {
+ unsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog(
+ AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId);
+ unsupportedCompileSdkDialog.show();
+ if (mUnsupportedCompileSdkDialogs == null) {
+ mUnsupportedCompileSdkDialogs = new SparseArray<>();
+ }
+ mUnsupportedCompileSdkDialogs.put(userId, unsupportedCompileSdkDialog);
}
}
@@ -324,16 +405,24 @@
* @param ar record for the activity that triggered the warning
*/
@UiThread
- private void showDeprecatedTargetSdkDialogUiThread(ActivityRecord ar) {
- if (mDeprecatedTargetSdkVersionDialog != null) {
- mDeprecatedTargetSdkVersionDialog.dismiss();
- mDeprecatedTargetSdkVersionDialog = null;
+ private void showDeprecatedTargetSdkDialogUiThread(@NonNull ActivityRecord ar) {
+ final int userId = getUserIdForActivity(ar);
+ DeprecatedTargetSdkVersionDialog deprecatedTargetSdkVersionDialog;
+ if (mDeprecatedTargetSdkVersionDialogs != null) {
+ deprecatedTargetSdkVersionDialog = mDeprecatedTargetSdkVersionDialogs.get(userId);
+ if (deprecatedTargetSdkVersionDialog != null) {
+ deprecatedTargetSdkVersionDialog.dismiss();
+ mDeprecatedTargetSdkVersionDialogs.remove(userId);
+ }
}
- if (ar != null && !hasPackageFlag(
- ar.packageName, FLAG_HIDE_DEPRECATED_SDK)) {
- mDeprecatedTargetSdkVersionDialog = new DeprecatedTargetSdkVersionDialog(
- AppWarnings.this, mUiContext, ar.info.applicationInfo);
- mDeprecatedTargetSdkVersionDialog.show();
+ if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_DEPRECATED_SDK)) {
+ deprecatedTargetSdkVersionDialog = new DeprecatedTargetSdkVersionDialog(
+ AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId);
+ deprecatedTargetSdkVersionDialog.show();
+ if (mDeprecatedTargetSdkVersionDialogs == null) {
+ mDeprecatedTargetSdkVersionDialogs = new SparseArray<>();
+ }
+ mDeprecatedTargetSdkVersionDialogs.put(userId, deprecatedTargetSdkVersionDialog);
}
}
@@ -345,16 +434,24 @@
* @param ar record for the activity that triggered the warning
*/
@UiThread
- private void showDeprecatedAbiDialogUiThread(ActivityRecord ar) {
- if (mDeprecatedAbiDialog != null) {
- mDeprecatedAbiDialog.dismiss();
- mDeprecatedAbiDialog = null;
+ private void showDeprecatedAbiDialogUiThread(@NonNull ActivityRecord ar) {
+ final int userId = getUserIdForActivity(ar);
+ DeprecatedAbiDialog deprecatedAbiDialog;
+ if (mDeprecatedAbiDialogs != null) {
+ deprecatedAbiDialog = mDeprecatedAbiDialogs.get(userId);
+ if (deprecatedAbiDialog != null) {
+ deprecatedAbiDialog.dismiss();
+ mDeprecatedAbiDialogs.remove(userId);
+ }
}
- if (ar != null && !hasPackageFlag(
- ar.packageName, FLAG_HIDE_DEPRECATED_ABI)) {
- mDeprecatedAbiDialog = new DeprecatedAbiDialog(
- AppWarnings.this, mUiContext, ar.info.applicationInfo);
- mDeprecatedAbiDialog.show();
+ if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_DEPRECATED_ABI)) {
+ deprecatedAbiDialog = new DeprecatedAbiDialog(
+ AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId);
+ deprecatedAbiDialog.show();
+ if (mDeprecatedAbiDialogs == null) {
+ mDeprecatedAbiDialogs = new SparseArray<>();
+ }
+ mDeprecatedAbiDialogs.put(userId, deprecatedAbiDialog);
}
}
@@ -365,65 +462,84 @@
*
* @param name the package for which warnings should be dismissed, or {@code null} to dismiss
* all warnings
+ * @param userId the user where the package resides.
*/
@UiThread
- private void hideDialogsForPackageUiThread(String name) {
+ private void hideDialogsForPackageUiThread(String name, int userId) {
// Hides the "unsupported display" dialog if necessary.
- if (mUnsupportedDisplaySizeDialog != null && (name == null || name.equals(
- mUnsupportedDisplaySizeDialog.mPackageName))) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
+ if (mUnsupportedDisplaySizeDialogs != null) {
+ UnsupportedDisplaySizeDialog unsupportedDisplaySizeDialog =
+ mUnsupportedDisplaySizeDialogs.get(userId);
+ if (unsupportedDisplaySizeDialog != null && (name == null || name.equals(
+ unsupportedDisplaySizeDialog.mPackageName))) {
+ unsupportedDisplaySizeDialog.dismiss();
+ mUnsupportedDisplaySizeDialogs.remove(userId);
+ }
}
// Hides the "unsupported compile SDK" dialog if necessary.
- if (mUnsupportedCompileSdkDialog != null && (name == null || name.equals(
- mUnsupportedCompileSdkDialog.mPackageName))) {
- mUnsupportedCompileSdkDialog.dismiss();
- mUnsupportedCompileSdkDialog = null;
+ if (mUnsupportedCompileSdkDialogs != null) {
+ UnsupportedCompileSdkDialog unsupportedCompileSdkDialog =
+ mUnsupportedCompileSdkDialogs.get(userId);
+ if (unsupportedCompileSdkDialog != null && (name == null || name.equals(
+ unsupportedCompileSdkDialog.mPackageName))) {
+ unsupportedCompileSdkDialog.dismiss();
+ mUnsupportedCompileSdkDialogs.remove(userId);
+ }
}
// Hides the "deprecated target sdk version" dialog if necessary.
- if (mDeprecatedTargetSdkVersionDialog != null && (name == null || name.equals(
- mDeprecatedTargetSdkVersionDialog.mPackageName))) {
- mDeprecatedTargetSdkVersionDialog.dismiss();
- mDeprecatedTargetSdkVersionDialog = null;
+ if (mDeprecatedTargetSdkVersionDialogs != null) {
+ DeprecatedTargetSdkVersionDialog deprecatedTargetSdkVersionDialog =
+ mDeprecatedTargetSdkVersionDialogs.get(userId);
+ if (deprecatedTargetSdkVersionDialog != null && (name == null || name.equals(
+ deprecatedTargetSdkVersionDialog.mPackageName))) {
+ deprecatedTargetSdkVersionDialog.dismiss();
+ mDeprecatedTargetSdkVersionDialogs.remove(userId);
+ }
}
// Hides the "deprecated abi" dialog if necessary.
- if (mDeprecatedAbiDialog != null && (name == null || name.equals(
- mDeprecatedAbiDialog.mPackageName))) {
- mDeprecatedAbiDialog.dismiss();
- mDeprecatedAbiDialog = null;
+ if (mDeprecatedAbiDialogs != null) {
+ DeprecatedAbiDialog deprecatedAbiDialog = mDeprecatedAbiDialogs.get(userId);
+ if (deprecatedAbiDialog != null && (name == null || name.equals(
+ deprecatedAbiDialog.mPackageName))) {
+ deprecatedAbiDialog.dismiss();
+ mDeprecatedAbiDialogs.remove(userId);
+ }
}
}
/**
* Returns the value of the flag for the given package.
*
+ * @param userId the user where the package resides.
* @param name the package from which to retrieve the flag
* @param flag the bitmask for the flag to retrieve
* @return {@code true} if the flag is enabled, {@code false} otherwise
*/
- boolean hasPackageFlag(String name, int flag) {
- return (getPackageFlags(name) & flag) == flag;
+ boolean hasPackageFlag(int userId, String name, int flag) {
+ return (getPackageFlags(userId, name) & flag) == flag;
}
/**
* Sets the flag for the given package to the specified value.
*
+ * @param userId the user where the package resides.
* @param name the package on which to set the flag
* @param flag the bitmask for flag to set
* @param enabled the value to set for the flag
*/
- void setPackageFlag(String name, int flag, boolean enabled) {
+ void setPackageFlag(int userId, String name, int flag, boolean enabled) {
synchronized (mPackageFlags) {
- final int curFlags = getPackageFlags(name);
+ final int curFlags = getPackageFlags(userId, name);
final int newFlags = enabled ? (curFlags | flag) : (curFlags & ~flag);
if (curFlags != newFlags) {
+ final Pair<Integer, String> packageKey = Pair.create(userId, name);
if (newFlags != 0) {
- mPackageFlags.put(name, newFlags);
+ mPackageFlags.put(packageKey, newFlags);
} else {
- mPackageFlags.remove(name);
+ mPackageFlags.remove(packageKey);
}
mWriteConfigTask.schedule();
}
@@ -433,13 +549,95 @@
/**
* Returns the bitmask of flags set for the specified package.
*/
- private int getPackageFlags(String name) {
+ private int getPackageFlags(int userId, String packageName) {
synchronized (mPackageFlags) {
- return mPackageFlags.getOrDefault(name, 0);
+ final Pair<Integer, String> packageKey = Pair.create(userId, packageName);
+ return mPackageFlags.getOrDefault(packageKey, 0);
}
}
/**
+ * Clear all the package flags for given user.
+ */
+ private void clearAllPackageFlagsForUser(int userId) {
+ synchronized (mPackageFlags) {
+ boolean hasPackageFlagsForUser = false;
+ for (int i = mPackageFlags.size() - 1; i >= 0; i--) {
+ Pair<Integer, String> key = mPackageFlags.keyAt(i);
+ if (key.first == userId) {
+ hasPackageFlagsForUser = true;
+ mPackageFlags.remove(key);
+ }
+ }
+
+ if (hasPackageFlagsForUser) {
+ mWriteConfigTask.schedule();
+ }
+ }
+ }
+
+ /**
+ * Returns the user ID for handling AppWarnings per user.
+ * Per-user AppWarnings only affects the behavior of the devices that enable
+ * the visible background users.
+ * If the device doesn't enable visible background users, it will return the system user ID
+ * for handling AppWarnings as a system user regardless of the actual user
+ * to preserve existing behavior of the device.
+ * Otherwise, it will return the main user (i.e., not a profile) that is assigned to the display
+ * where the activity is launched.
+ */
+ private @UserIdInt int getUserIdForActivity(@NonNull ActivityRecord ar) {
+ if (!isVisibleBackgroundUsersEnabled()) {
+ return USER_SYSTEM;
+ }
+
+ if (ar.mUserId == USER_SYSTEM) {
+ return getUserAssignedToDisplay(ar.mDisplayContent.getDisplayId());
+ }
+
+ return mUserManagerInternal.getProfileParentId(ar.mUserId);
+ }
+
+ /**
+ * Returns the UI context for handling AppWarnings per user.
+ * Per-user AppWarnings only affects the behavior of the devices that enable
+ * the visible background users.
+ * If the device enables the visible background users, it will return the UI context associated
+ * with the assigned user and the display where the activity is launched.
+ * If the HSUM device doesn't enable the visible background users, it will return the UI context
+ * associated with the current user and the default display.
+ * Otherwise, it will return the UI context associated with the system user and the default
+ * display.
+ */
+ private Context getUiContextForActivity(@NonNull ActivityRecord ar) {
+ if (!isVisibleBackgroundUsersEnabled()) {
+ if (!isHeadlessSystemUserMode()) {
+ return mAtm.getUiContext();
+ }
+
+ Context uiContextForCurrentUser = mAtm.getUiContext().createContextAsUser(
+ new UserHandle(mAtm.getCurrentUserId()), /* flags= */ 0);
+ return uiContextForCurrentUser;
+ }
+
+ DisplayContent dc = ar.mDisplayContent;
+ Context systemUiContext = dc.getDisplayPolicy().getSystemUiContext();
+ int assignedUser = getUserAssignedToDisplay(dc.getDisplayId());
+ Context uiContextForUser = systemUiContext.createContextAsUser(
+ new UserHandle(assignedUser), /* flags= */ 0);
+ return uiContextForUser;
+ }
+
+ /**
+ * Returns the main user that is assigned to the display.
+ *
+ * See {@link UserManagerInternal#getUserAssignedToDisplay(int)}.
+ */
+ private @UserIdInt int getUserAssignedToDisplay(int displayId) {
+ return mUserManagerInternal.getUserAssignedToDisplay(displayId);
+ }
+
+ /**
* Handles messages on the system process UI thread.
*/
private final class UiHandler extends Handler {
@@ -470,7 +668,8 @@
} break;
case MSG_HIDE_DIALOGS_FOR_PACKAGE: {
final String name = (String) msg.obj;
- hideDialogsForPackageUiThread(name);
+ final int userId = (int) msg.arg1;
+ hideDialogsForPackageUiThread(name, userId);
} break;
case MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG: {
final ActivityRecord ar = (ActivityRecord) msg.obj;
@@ -508,20 +707,24 @@
obtainMessage(MSG_SHOW_DEPRECATED_ABI_DIALOG, r).sendToTarget();
}
- public void hideDialogsForPackage(String name) {
- obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, name).sendToTarget();
+ public void hideDialogsForPackage(String name, int userId) {
+ obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, userId, 0, name).sendToTarget();
}
}
static class BaseDialog {
final AppWarnings mManager;
+ final Context mUiContext;
final String mPackageName;
+ final int mUserId;
AlertDialog mDialog;
private BroadcastReceiver mCloseReceiver;
- BaseDialog(AppWarnings manager, String packageName) {
+ BaseDialog(AppWarnings manager, Context uiContext, String packageName, int userId) {
mManager = manager;
+ mUiContext = uiContext;
mPackageName = packageName;
+ mUserId = userId;
}
@UiThread
@@ -532,11 +735,11 @@
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
- mManager.mUiHandler.hideDialogsForPackage(mPackageName);
+ mManager.mUiHandler.hideDialogsForPackage(mPackageName, mUserId);
}
}
};
- mManager.mUiContext.registerReceiver(mCloseReceiver,
+ mUiContext.registerReceiver(mCloseReceiver,
new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
Context.RECEIVER_EXPORTED);
}
@@ -548,7 +751,7 @@
void dismiss() {
if (mDialog == null) return;
if (mCloseReceiver != null) {
- mManager.mUiContext.unregisterReceiver(mCloseReceiver);
+ mUiContext.unregisterReceiver(mCloseReceiver);
mCloseReceiver = null;
}
mDialog.dismiss();
@@ -558,12 +761,13 @@
private final class WriteConfigTask implements Runnable {
private static final long WRITE_CONFIG_DELAY_MS = 10000;
- final AtomicReference<ArrayMap<String, Integer>> mPendingPackageFlags =
+ final AtomicReference<ArrayMap<Pair<Integer, String>, Integer>> mPendingPackageFlags =
new AtomicReference<>();
@Override
public void run() {
- final ArrayMap<String, Integer> packageFlags = mPendingPackageFlags.getAndSet(null);
+ final ArrayMap<Pair<Integer, String>, Integer> packageFlags =
+ mPendingPackageFlags.getAndSet(null);
if (packageFlags != null) {
writeConfigToFile(packageFlags);
}
@@ -579,7 +783,7 @@
/** Writes the configuration file. */
@WorkerThread
- private void writeConfigToFile(@NonNull ArrayMap<String, Integer> packageFlags) {
+ private void writeConfigToFile(@NonNull ArrayMap<Pair<Integer, String>, Integer> packageFlags) {
FileOutputStream fos = null;
try {
fos = mConfigFile.startWrite();
@@ -590,13 +794,16 @@
out.startTag(null, "packages");
for (int i = 0; i < packageFlags.size(); i++) {
- final String pkg = packageFlags.keyAt(i);
+ final Pair<Integer, String> key = packageFlags.keyAt(i);
+ final int userId = key.first;
+ final String packageName = key.second;
final int mode = packageFlags.valueAt(i);
if (mode == 0) {
continue;
}
out.startTag(null, "package");
- out.attribute(null, "name", pkg);
+ out.attributeInt(null, "user", userId);
+ out.attribute(null, "name", packageName);
out.attributeInt(null, "flags", mode);
out.endTag(null, "package");
}
@@ -616,7 +823,7 @@
/**
* Reads the configuration file and populates the package flags.
* <p>
- * <strong>Note:</strong> Must be called from the constructor (and thus on the
+ * <strong>Note:</strong> Must be called from #onSystemReady() (and thus on the
* ActivityManagerService thread) since we don't synchronize on config.
*/
private void readConfigFromFileAmsThread() {
@@ -639,21 +846,58 @@
String tagName = parser.getName();
if ("packages".equals(tagName)) {
eventType = parser.next();
+ boolean writeConfigToFileNeeded = false;
do {
if (eventType == XmlPullParser.START_TAG) {
tagName = parser.getName();
if (parser.getDepth() == 2) {
if ("package".equals(tagName)) {
+ final int userId = parser.getAttributeInt(
+ null, "user", USER_NULL);
final String name = parser.getAttributeValue(null, "name");
if (name != null) {
int flagsInt = parser.getAttributeInt(null, "flags", 0);
- mPackageFlags.put(name, flagsInt);
+ if (userId != USER_NULL) {
+ final Pair<Integer, String> packageKey =
+ Pair.create(userId, name);
+ mPackageFlags.put(packageKey, flagsInt);
+ } else {
+ // This is for compatibility with existing configuration
+ // file written from legacy logic(pre-V) which does not have
+ // the flags per-user. (b/296334639)
+ writeConfigToFileNeeded = true;
+ if (!isVisibleBackgroundUsersEnabled()) {
+ // To preserve existing behavior of the devices that
+ // doesn't enable visible background users, populate
+ // the flags for a package as the system user.
+ final Pair<Integer, String> packageKey =
+ Pair.create(USER_SYSTEM, name);
+ mPackageFlags.put(packageKey, flagsInt);
+ } else {
+ // To manage the flags per user in the device that
+ // enable visible background users, populate the flags
+ // for all existing non-profile human user.
+ UserInfo[] users = mUserManagerInternal.getUserInfos();
+ for (UserInfo userInfo : users) {
+ if (!userInfo.isFull()) {
+ continue;
+ }
+ final Pair<Integer, String> packageKey =
+ Pair.create(userInfo.id, name);
+ mPackageFlags.put(packageKey, flagsInt);
+ }
+ }
+ }
}
}
}
}
eventType = parser.next();
} while (eventType != XmlPullParser.END_DOCUMENT);
+
+ if (writeConfigToFileNeeded) {
+ mWriteConfigTask.schedule();
+ }
}
} catch (XmlPullParserException e) {
Slog.w(TAG, "Error reading package metadata", e);
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index d709fa5..d0c6e15 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -811,6 +811,13 @@
return mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
}
+ boolean shouldPauseTouch(WindowContainer wc) {
+ // Once the close transition is ready, it means the onBackInvoked callback has invoked, and
+ // app is ready to trigger next transition, no matter what it will be.
+ return mAnimationHandler.mComposed && mWaitTransitionFinish == null
+ && mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
+ }
+
/**
* Cleanup animation, this can either happen when legacy transition ready, or when the Shell
* transition finish.
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 8020516..d70a880 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -109,7 +109,8 @@
this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId),
new DisplayManagerFlags().isConnectedDisplayManagementEnabled()
&& !new DisplayManagerFlags()
- .isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
+ .isPixelAnisotropyCorrectionInLogicalDisplayEnabled()
+ && displayContent.getDisplayInfo().type == Display.TYPE_EXTERNAL);
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/DeprecatedAbiDialog.java b/services/core/java/com/android/server/wm/DeprecatedAbiDialog.java
index e96208d..46b34a1 100644
--- a/services/core/java/com/android/server/wm/DeprecatedAbiDialog.java
+++ b/services/core/java/com/android/server/wm/DeprecatedAbiDialog.java
@@ -28,8 +28,8 @@
class DeprecatedAbiDialog extends AppWarnings.BaseDialog {
DeprecatedAbiDialog(final AppWarnings manager, Context context,
- ApplicationInfo appInfo) {
- super(manager, appInfo.packageName);
+ ApplicationInfo appInfo, int userId) {
+ super(manager, context, appInfo.packageName, userId);
final PackageManager pm = context.getPackageManager();
final CharSequence label = appInfo.loadSafeLabel(pm,
@@ -41,7 +41,7 @@
final AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setPositiveButton(R.string.ok, (dialog, which) ->
manager.setPackageFlag(
- mPackageName, AppWarnings.FLAG_HIDE_DEPRECATED_ABI, true))
+ mUserId, mPackageName, AppWarnings.FLAG_HIDE_DEPRECATED_ABI, true))
.setMessage(message)
.setTitle(label);
diff --git a/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java b/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java
index 1a7a9b2..ce42385 100644
--- a/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java
+++ b/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java
@@ -31,8 +31,8 @@
class DeprecatedTargetSdkVersionDialog extends AppWarnings.BaseDialog {
DeprecatedTargetSdkVersionDialog(final AppWarnings manager, Context context,
- ApplicationInfo appInfo) {
- super(manager, appInfo.packageName);
+ ApplicationInfo appInfo, int userId) {
+ super(manager, context, appInfo.packageName, userId);
final PackageManager pm = context.getPackageManager();
final CharSequence label = appInfo.loadSafeLabel(pm,
@@ -44,7 +44,7 @@
final AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setPositiveButton(R.string.ok, (dialog, which) ->
manager.setPackageFlag(
- mPackageName, AppWarnings.FLAG_HIDE_DEPRECATED_SDK, true))
+ mUserId, mPackageName, AppWarnings.FLAG_HIDE_DEPRECATED_SDK, true))
.setMessage(message)
.setTitle(label);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 739f76e..00d42e0 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -197,6 +197,7 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.WorkSource;
import android.provider.Settings;
import android.util.ArrayMap;
@@ -289,6 +290,9 @@
static final float INVALID_DPI = 0.0f;
+ private final boolean mVisibleBackgroundUserEnabled =
+ UserManager.isVisibleBackgroundUsersEnabled();
+
@IntDef(prefix = { "FORCE_SCALING_MODE_" }, value = {
FORCE_SCALING_MODE_AUTO,
FORCE_SCALING_MODE_DISABLED
@@ -1999,7 +2003,12 @@
}
// Update directly because the app which will change the orientation of display is ready.
if (mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */)) {
- sendNewConfiguration();
+ // Run rotation change on display thread. See Transition#shouldApplyOnDisplayThread().
+ mWmService.mH.post(() -> {
+ synchronized (mWmService.mGlobalLock) {
+ sendNewConfiguration();
+ }
+ });
return;
}
if (mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange()) {
@@ -2698,11 +2707,15 @@
* Returns true if the specified UID has access to this display.
*/
boolean hasAccess(int uid) {
- int userId = UserHandle.getUserId(uid);
- boolean isUserVisibleOnDisplay = mWmService.mUmInternal.isUserVisible(
- userId, mDisplayId);
- return mDisplay.hasAccess(uid)
- && (userId == UserHandle.USER_SYSTEM || isUserVisibleOnDisplay);
+ if (!mDisplay.hasAccess(uid)) {
+ return false;
+ }
+ if (!mVisibleBackgroundUserEnabled) {
+ return true;
+ }
+ final int userId = UserHandle.getUserId(uid);
+ return userId == UserHandle.USER_SYSTEM
+ || mWmService.mUmInternal.isUserVisible(userId, mDisplayId);
}
boolean isPrivate() {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 5e0d4f9..84dadab 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -41,7 +41,6 @@
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
@@ -986,19 +985,6 @@
+ " to fit insets. fitInsetsTypes=" + WindowInsets.Type.toString(
attrs.getFitInsetsTypes()));
}
- if ((attrs.privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
- && attrs.layoutInDisplayCutoutMode
- != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
- // A non-translucent main window of the app enforced to go edge-to-edge
- // isn't allowed to fit display cutout, or it will cause software bezels.
- throw new IllegalArgumentException("Illegal attributes: Main window of "
- + win.mActivityRecord.getName() + " that isn't translucent and"
- + " targets SDK level " + win.mActivityRecord.mTargetSdk
- + " (>= 35) trying to specify layoutInDisplayCutoutMode as '"
- + WindowManager.LayoutParams.layoutInDisplayCutoutModeToString(
- attrs.layoutInDisplayCutoutMode)
- + "' instead of 'always'");
- }
}
break;
}
@@ -1939,6 +1925,11 @@
*/
final Rect mOverrideConfigInsets = new Rect();
+ /**
+ * Override value of mNonDecorInsets for app compatibility purpose.
+ */
+ final Rect mOverrideNonDecorInsets = new Rect();
+
/** The display frame available after excluding {@link #mNonDecorInsets}. */
final Rect mNonDecorFrame = new Rect();
@@ -1954,6 +1945,11 @@
*/
final Rect mOverrideConfigFrame = new Rect();
+ /**
+ * Override value of mNonDecorFrame for app compatibility purpose.
+ */
+ final Rect mOverrideNonDecorFrame = new Rect();
+
private boolean mNeedUpdate = true;
InsetsState update(DisplayContent dc, int rotation, int w, int h) {
@@ -1973,17 +1969,26 @@
? configInsets
: insetsState.calculateInsets(displayFrame,
dc.mWmService.mOverrideConfigTypes, true /* ignoreVisibility */);
+ final Insets overrideDecorInsets = dc.mWmService.mDecorTypes
+ == dc.mWmService.mOverrideDecorTypes
+ ? decor
+ : insetsState.calculateInsets(displayFrame,
+ dc.mWmService.mOverrideDecorTypes, true /* ignoreVisibility */);
mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
mConfigInsets.set(configInsets.left, configInsets.top, configInsets.right,
configInsets.bottom);
mOverrideConfigInsets.set(overrideConfigInsets.left, overrideConfigInsets.top,
overrideConfigInsets.right, overrideConfigInsets.bottom);
+ mOverrideNonDecorInsets.set(overrideDecorInsets.left, overrideDecorInsets.top,
+ overrideDecorInsets.right, overrideDecorInsets.bottom);
mNonDecorFrame.set(displayFrame);
mNonDecorFrame.inset(mNonDecorInsets);
mConfigFrame.set(displayFrame);
mConfigFrame.inset(mConfigInsets);
mOverrideConfigFrame.set(displayFrame);
mOverrideConfigFrame.inset(mOverrideConfigInsets);
+ mOverrideNonDecorFrame.set(displayFrame);
+ mOverrideNonDecorFrame.inset(mOverrideNonDecorInsets);
mNeedUpdate = false;
return insetsState;
}
@@ -1992,9 +1997,11 @@
mNonDecorInsets.set(other.mNonDecorInsets);
mConfigInsets.set(other.mConfigInsets);
mOverrideConfigInsets.set(other.mOverrideConfigInsets);
+ mOverrideNonDecorInsets.set(other.mOverrideNonDecorInsets);
mNonDecorFrame.set(other.mNonDecorFrame);
mConfigFrame.set(other.mConfigFrame);
mOverrideConfigFrame.set(other.mOverrideConfigFrame);
+ mOverrideNonDecorFrame.set(other.mOverrideNonDecorFrame);
mNeedUpdate = false;
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 45cf10b..5aa0ed7c 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -327,14 +327,14 @@
R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat(
R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
- mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat(
- R.dimen.config_letterboxHorizontalPositionMultiplier);
- mLetterboxVerticalPositionMultiplier = mContext.getResources().getFloat(
- R.dimen.config_letterboxVerticalPositionMultiplier);
- mLetterboxBookModePositionMultiplier = mContext.getResources().getFloat(
- R.dimen.config_letterboxBookModePositionMultiplier);
- mLetterboxTabletopModePositionMultiplier = mContext.getResources().getFloat(
- R.dimen.config_letterboxTabletopModePositionMultiplier);
+ setLetterboxHorizontalPositionMultiplier(mContext.getResources().getFloat(
+ R.dimen.config_letterboxHorizontalPositionMultiplier));
+ setLetterboxVerticalPositionMultiplier(mContext.getResources().getFloat(
+ R.dimen.config_letterboxVerticalPositionMultiplier));
+ setLetterboxBookModePositionMultiplier(mContext.getResources().getFloat(
+ R.dimen.config_letterboxBookModePositionMultiplier));
+ setLetterboxTabletopModePositionMultiplier(mContext.getResources()
+ .getFloat(R.dimen.config_letterboxTabletopModePositionMultiplier));
mIsHorizontalReachabilityEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsHorizontalReachabilityEnabled);
mIsVerticalReachabilityEnabled = mContext.getResources().getBoolean(
@@ -657,29 +657,8 @@
* right side.
*/
float getLetterboxHorizontalPositionMultiplier(boolean isInBookMode) {
- if (isInBookMode) {
- if (mLetterboxBookModePositionMultiplier < 0.0f
- || mLetterboxBookModePositionMultiplier > 1.0f) {
- Slog.w(TAG,
- "mLetterboxBookModePositionMultiplier out of bounds (isInBookMode=true): "
- + mLetterboxBookModePositionMultiplier);
- // Default to left position if invalid value is provided.
- return 0.0f;
- } else {
- return mLetterboxBookModePositionMultiplier;
- }
- } else {
- if (mLetterboxHorizontalPositionMultiplier < 0.0f
- || mLetterboxHorizontalPositionMultiplier > 1.0f) {
- Slog.w(TAG,
- "mLetterboxBookModePositionMultiplier out of bounds (isInBookMode=false):"
- + mLetterboxBookModePositionMultiplier);
- // Default to central position if invalid value is provided.
- return 0.5f;
- } else {
- return mLetterboxHorizontalPositionMultiplier;
- }
- }
+ return isInBookMode ? mLetterboxBookModePositionMultiplier
+ : mLetterboxHorizontalPositionMultiplier;
}
/*
@@ -689,37 +668,28 @@
* bottom side.
*/
float getLetterboxVerticalPositionMultiplier(boolean isInTabletopMode) {
- if (isInTabletopMode) {
- return (mLetterboxTabletopModePositionMultiplier < 0.0f
- || mLetterboxTabletopModePositionMultiplier > 1.0f)
- // Default to top position if invalid value is provided.
- ? 0.0f : mLetterboxTabletopModePositionMultiplier;
- } else {
- return (mLetterboxVerticalPositionMultiplier < 0.0f
- || mLetterboxVerticalPositionMultiplier > 1.0f)
- // Default to central position if invalid value is provided.
- ? 0.5f : mLetterboxVerticalPositionMultiplier;
- }
+ return isInTabletopMode ? mLetterboxTabletopModePositionMultiplier
+ : mLetterboxVerticalPositionMultiplier;
}
/**
- * Overrides horizontal position of a center of the letterboxed app window. If given value < 0
- * or > 1, then it and a value of {@link
- * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} are ignored and
- * central position (0.5) is used.
+ * Overrides horizontal position of a center of the letterboxed app window.
+ *
+ * @throws IllegalArgumentException If given value < 0 or > 1.
*/
void setLetterboxHorizontalPositionMultiplier(float multiplier) {
- mLetterboxHorizontalPositionMultiplier = multiplier;
+ mLetterboxHorizontalPositionMultiplier = assertValidMultiplier(multiplier,
+ "mLetterboxHorizontalPositionMultiplier");
}
/**
- * Overrides vertical position of a center of the letterboxed app window. If given value < 0
- * or > 1, then it and a value of {@link
- * com.android.internal.R.dimen.config_letterboxVerticalPositionMultiplier} are ignored and
- * central position (0.5) is used.
+ * Overrides vertical position of a center of the letterboxed app window.
+ *
+ * @throws IllegalArgumentException If given value < 0 or > 1.
*/
void setLetterboxVerticalPositionMultiplier(float multiplier) {
- mLetterboxVerticalPositionMultiplier = multiplier;
+ mLetterboxVerticalPositionMultiplier = assertValidMultiplier(multiplier,
+ "mLetterboxVerticalPositionMultiplier");
}
/**
@@ -740,6 +710,28 @@
com.android.internal.R.dimen.config_letterboxVerticalPositionMultiplier);
}
+ /**
+ * Sets tabletop mode position multiplier.
+ *
+ * @throws IllegalArgumentException If given value < 0 or > 1.
+ */
+ @VisibleForTesting
+ void setLetterboxTabletopModePositionMultiplier(float multiplier) {
+ mLetterboxTabletopModePositionMultiplier = assertValidMultiplier(multiplier,
+ "mLetterboxTabletopModePositionMultiplier");
+ }
+
+ /**
+ * Sets tabletop mode position multiplier.
+ *
+ * @throws IllegalArgumentException If given value < 0 or > 1.
+ */
+ @VisibleForTesting
+ void setLetterboxBookModePositionMultiplier(float multiplier) {
+ mLetterboxBookModePositionMultiplier = assertValidMultiplier(multiplier,
+ "mLetterboxBookModePositionMultiplier");
+ }
+
/*
* Whether horizontal reachability repositioning is allowed for letterboxed fullscreen apps in
* landscape device orientation.
@@ -1356,4 +1348,21 @@
void resetUserAppAspectRatioFullscreenEnabled() {
setUserAppAspectRatioFullscreenOverrideEnabled(false);
}
+
+ /**
+ * Checks whether the multiplier is between [0,1].
+ *
+ * @param multiplierName sent in the exception if multiplier is invalid, for easier debugging.
+ *
+ * @return multiplier, if valid
+ * @throws IllegalArgumentException if outside bounds.
+ */
+ private float assertValidMultiplier(float multiplier, String multiplierName)
+ throws IllegalArgumentException {
+ if (multiplier < 0.0f || multiplier > 1.0f) {
+ throw new IllegalArgumentException("Trying to set " + multiplierName
+ + " out of bounds: " + multiplier);
+ }
+ return multiplier;
+ }
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index f220c9d..cb5ad91 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1308,7 +1308,8 @@
}
final boolean shouldShowLetterboxUi =
- (mActivityRecord.isInLetterboxAnimation() || isSurfaceVisible(mainWindow))
+ (mActivityRecord.isInLetterboxAnimation() || mActivityRecord.isVisible()
+ || mActivityRecord.isVisibleRequested())
&& mainWindow.areAppWindowBoundsLetterboxed()
// Check for FLAG_SHOW_WALLPAPER explicitly instead of using
// WindowContainer#showWallpaper because the later will return true when this
@@ -1320,12 +1321,6 @@
return shouldShowLetterboxUi;
}
- @VisibleForTesting
- boolean isSurfaceVisible(WindowState mainWindow) {
- return mainWindow.isOnScreen() && (mActivityRecord.isVisible()
- || mActivityRecord.isVisibleRequested());
- }
-
private Color getLetterboxBackgroundColor() {
final WindowState w = mActivityRecord.findMainWindow();
if (w == null || w.isLetterboxedForDisplayCutout()) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3eea6ac..1e88fe4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -72,7 +72,6 @@
import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
import static com.android.server.wm.Task.REPARENT_LEAVE_ROOT_TASK_IN_PLACE;
import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
-import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -270,8 +269,6 @@
private int mTmpTaskLayerRank;
private final RankTaskLayersRunnable mRankTaskLayersRunnable = new RankTaskLayersRunnable();
- private final AttachApplicationHelper mAttachApplicationHelper = new AttachApplicationHelper();
-
private String mDestroyAllActivitiesReason;
private final Runnable mDestroyAllActivitiesRunnable = new Runnable() {
@Override
@@ -1838,11 +1835,39 @@
}
boolean attachApplication(WindowProcessController app) throws RemoteException {
- try {
- return mAttachApplicationHelper.process(app);
- } finally {
- mAttachApplicationHelper.reset();
+ final ArrayList<ActivityRecord> activities = mService.mStartingProcessActivities;
+ RemoteException remoteException = null;
+ boolean hasActivityStarted = false;
+ for (int i = activities.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = activities.get(i);
+ if (app.mUid != r.info.applicationInfo.uid || !app.mName.equals(r.processName)) {
+ // The attaching process does not match the starting activity.
+ continue;
+ }
+ // Consume the pending record.
+ activities.remove(i);
+ final TaskFragment tf = r.getTaskFragment();
+ if (tf == null || r.finishing || r.app != null
+ // Ignore keyguard because the app may use show-when-locked when creating.
+ || !r.shouldBeVisible(true /* ignoringKeyguard */)
+ || !r.showToCurrentUser()) {
+ continue;
+ }
+ try {
+ final boolean canResume = r.isFocusable() && r == tf.topRunningActivity();
+ if (mTaskSupervisor.realStartActivityLocked(r, app, canResume,
+ true /* checkConfig */)) {
+ hasActivityStarted = true;
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception in new process when starting " + r, e);
+ remoteException = e;
+ }
}
+ if (remoteException != null) {
+ throw remoteException;
+ }
+ return hasActivityStarted;
}
/**
@@ -2378,6 +2403,14 @@
return false;
}
+ return resumeFocusedTasksTopActivitiesUnchecked(targetRootTask, target, targetOptions,
+ deferPause);
+ }
+
+ @VisibleForTesting
+ boolean resumeFocusedTasksTopActivitiesUnchecked(
+ Task targetRootTask, ActivityRecord target, ActivityOptions targetOptions,
+ boolean deferPause) {
boolean result = false;
if (targetRootTask != null && (targetRootTask.isTopRootTaskInDisplayArea()
|| getTopDisplayFocusedRootTask() == targetRootTask)) {
@@ -3740,67 +3773,4 @@
}
}
}
-
- private class AttachApplicationHelper implements Consumer<Task>, Predicate<ActivityRecord> {
- private boolean mHasActivityStarted;
- private RemoteException mRemoteException;
- private WindowProcessController mApp;
- private ActivityRecord mTop;
-
- void reset() {
- mHasActivityStarted = false;
- mRemoteException = null;
- mApp = null;
- mTop = null;
- }
-
- boolean process(WindowProcessController app) throws RemoteException {
- mApp = app;
- for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
- getChildAt(displayNdx).forAllRootTasks(this);
- if (mRemoteException != null) {
- throw mRemoteException;
- }
- }
- if (!mHasActivityStarted) {
- ensureActivitiesVisible();
- }
- return mHasActivityStarted;
- }
-
- @Override
- public void accept(Task rootTask) {
- if (mRemoteException != null) {
- return;
- }
- if (rootTask.getVisibility(null /* starting */)
- == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
- return;
- }
- mTop = rootTask.topRunningActivity();
- rootTask.forAllActivities(this);
- }
-
- @Override
- public boolean test(ActivityRecord r) {
- if (r.finishing || !r.showToCurrentUser() || !r.visibleIgnoringKeyguard
- || r.app != null || mApp.mUid != r.info.applicationInfo.uid
- || !mApp.mName.equals(r.processName)) {
- return false;
- }
-
- try {
- if (mTaskSupervisor.realStartActivityLocked(r, mApp,
- mTop == r && r.getTask().canBeResumed(r) /* andResume */,
- true /* checkConfig */)) {
- mHasActivityStarted = true;
- }
- } catch (RemoteException e) {
- Slog.w(TAG, "Exception in new application when starting activity " + mTop, e);
- mRemoteException = e;
- return true;
- }
- return false;
- }
- }
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d79d113..91860a6 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4748,14 +4748,6 @@
// transferring the transform on the leash to the task, reset this state once we're
// moving out of pip
setCanAffectSystemUiFlags(true);
- // Turn on userLeaveHint so other app can enter PiP mode.
- mTaskSupervisor.mUserLeaving = true;
- // Allow entering PiP from current top most activity when we are leaving PiP.
- final Task topFocused = mRootWindowContainer.getTopDisplayFocusedRootTask();
- if (topFocused != null) {
- final ActivityRecord ar = topFocused.getTopResumedActivity();
- enableEnterPipOnTaskSwitch(ar, null /* toFrontTask */, ar, null /* opts */);
- }
mRootWindowContainer.notifyActivityPipModeChanged(this, null);
}
if (likelyResolvedMode == WINDOWING_MODE_PINNED) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 218fb7f..fc85af5 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -906,8 +906,12 @@
return mForceTranslucent;
}
- void setForceTranslucent(boolean set) {
+ boolean setForceTranslucent(boolean set) {
+ if (mForceTranslucent == set) {
+ return false;
+ }
mForceTranslucent = set;
+ return true;
}
boolean isLeafTaskFragment() {
@@ -2188,38 +2192,20 @@
return getTask() != null ? getTask().mTaskId : INVALID_TASK_ID;
}
+ static class ConfigOverrideHint {
+ @Nullable DisplayInfo mTmpOverrideDisplayInfo;
+ @Nullable ActivityRecord.CompatDisplayInsets mTmpCompatInsets;
+ boolean mUseLegacyInsetsForStableBounds;
+ }
+
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig) {
- computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
- null /* compatInsets */);
- }
-
- void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
- @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) {
- if (overrideDisplayInfo != null) {
- // Make sure the screen related configs can be computed by the provided display info.
- inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
- invalidateAppBoundsConfig(inOutConfig);
- }
- computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo,
- null /* compatInsets */);
- }
-
- void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
- @NonNull Configuration parentConfig,
- @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
- if (compatInsets != null) {
- // Make sure the app bounds can be computed by the compat insets.
- invalidateAppBoundsConfig(inOutConfig);
- }
- computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
- compatInsets);
+ computeConfigResourceOverrides(inOutConfig, parentConfig, null /* configOverrideHint */);
}
/**
* Forces the app bounds related configuration can be computed by
- * {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo,
- * ActivityRecord.CompatDisplayInsets)}.
+ * {@link #computeConfigResourceOverrides(Configuration, Configuration, ConfigOverrideHint)}.
*/
private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) {
final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds();
@@ -2239,8 +2225,24 @@
* just be inherited from the parent configuration.
**/
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
- @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo,
- @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
+ @NonNull Configuration parentConfig, @Nullable ConfigOverrideHint overrideHint) {
+ DisplayInfo overrideDisplayInfo = null;
+ ActivityRecord.CompatDisplayInsets compatInsets = null;
+ boolean useLegacyInsetsForStableBounds = false;
+ if (overrideHint != null) {
+ overrideDisplayInfo = overrideHint.mTmpOverrideDisplayInfo;
+ compatInsets = overrideHint.mTmpCompatInsets;
+ useLegacyInsetsForStableBounds = overrideHint.mUseLegacyInsetsForStableBounds;
+ if (overrideDisplayInfo != null) {
+ // Make sure the screen related configs can be computed by the provided
+ // display info.
+ inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
+ }
+ if (overrideDisplayInfo != null || compatInsets != null) {
+ // Make sure the app bounds can be computed by the compat insets.
+ invalidateAppBoundsConfig(inOutConfig);
+ }
+ }
int windowingMode = inOutConfig.windowConfiguration.getWindowingMode();
if (windowingMode == WINDOWING_MODE_UNDEFINED) {
windowingMode = parentConfig.windowConfiguration.getWindowingMode();
@@ -2309,7 +2311,8 @@
// area, i.e. the screen area without the system bars.
// The non decor inset are areas that could never be removed in Honeycomb. See
// {@link WindowManagerPolicy#getNonDecorInsetsLw}.
- calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di);
+ calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di,
+ useLegacyInsetsForStableBounds);
} else {
// Apply the given non-decor and stable insets to calculate the corresponding bounds
// for screen size of configuration.
@@ -2407,9 +2410,11 @@
* @param outNonDecorBounds where to place bounds with non-decor insets applied.
* @param outStableBounds where to place bounds with stable insets applied.
* @param bounds the bounds to inset.
+ * @param useLegacyInsetsForStableBounds {@code true} if we need to use the legacy insets frame
+ * for apps targeting U or before when calculating stable bounds.
*/
void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
- DisplayInfo displayInfo) {
+ DisplayInfo displayInfo, boolean useLegacyInsetsForStableBounds) {
outNonDecorBounds.set(bounds);
outStableBounds.set(bounds);
if (mDisplayContent == null) {
@@ -2420,8 +2425,13 @@
final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
final DisplayPolicy.DecorInsets.Info info = policy.getDecorInsetsInfo(
displayInfo.rotation, displayInfo.logicalWidth, displayInfo.logicalHeight);
- intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
- intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
+ if (!useLegacyInsetsForStableBounds) {
+ intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
+ intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
+ } else {
+ intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mOverrideConfigInsets);
+ intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mOverrideNonDecorInsets);
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java b/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java
index f376e8b..0655068 100644
--- a/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java
+++ b/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java
@@ -32,8 +32,8 @@
class UnsupportedCompileSdkDialog extends AppWarnings.BaseDialog {
UnsupportedCompileSdkDialog(final AppWarnings manager, Context context,
- ApplicationInfo appInfo) {
- super(manager, appInfo.packageName);
+ ApplicationInfo appInfo, int userId) {
+ super(manager, context, appInfo.packageName, userId);
final PackageManager pm = context.getPackageManager();
final CharSequence label = appInfo.loadSafeLabel(pm,
@@ -68,6 +68,6 @@
final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox);
alwaysShow.setChecked(true);
alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
- mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked));
+ mUserId, mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked));
}
}
diff --git a/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java b/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java
index b11c22d..5e40d9c 100644
--- a/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java
+++ b/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java
@@ -30,8 +30,8 @@
class UnsupportedDisplaySizeDialog extends AppWarnings.BaseDialog {
UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context,
- ApplicationInfo appInfo) {
- super(manager, appInfo.packageName);
+ ApplicationInfo appInfo, int userId) {
+ super(manager, context, appInfo.packageName, userId);
final PackageManager pm = context.getPackageManager();
final CharSequence label = appInfo.loadSafeLabel(pm,
@@ -59,6 +59,6 @@
final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox);
alwaysShow.setChecked(true);
alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
- mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked));
+ mUserId, mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked));
}
}
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 5c24eee..9d1551c 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -193,11 +193,11 @@
if (mVisibleRequested != visible) {
// Before setting mVisibleRequested so we can track changes.
final WindowState wpTarget = mDisplayContent.mWallpaperController.getWallpaperTarget();
- final boolean isTargetNotCollectedActivity = wpTarget != null
- && wpTarget.mActivityRecord != null
- && !mTransitionController.isCollecting(wpTarget.mActivityRecord);
- // Skip collecting requesting-invisible wallpaper if the wallpaper target is an activity
- // and it is not collected. Because the visibility change may be called after the
+ final boolean isTargetNotCollectedActivity = wpTarget == null
+ || (wpTarget.mActivityRecord != null
+ && !mTransitionController.isCollecting(wpTarget.mActivityRecord));
+ // Skip collecting requesting-invisible wallpaper if the wallpaper target is empty or
+ // a non-collected activity. Because the visibility change may be called after the
// transition of activity is finished, e.g. WallpaperController#hideWallpapers from
// hiding surface of the target. Then if there is a next transition, the wallpaper
// change may be collected into the unrelated transition and cause a weird animation.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ed88b5a..1496ae0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -570,6 +570,8 @@
final int mOverrideConfigTypes;
+ final int mOverrideDecorTypes;
+
final boolean mLimitedAlphaCompositing;
final int mMaxUiWidth;
@@ -1255,10 +1257,13 @@
if (isScreenSizeDecoupledFromStatusBarAndCutout && !mFlags.mInsetsDecoupledConfiguration) {
// If the global new behavior is not there, but the partial decouple flag is on.
mOverrideConfigTypes = 0;
+ mOverrideDecorTypes = 0;
} else {
mOverrideConfigTypes =
WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
| WindowInsets.Type.navigationBars();
+ mOverrideDecorTypes = WindowInsets.Type.displayCutout()
+ | WindowInsets.Type.navigationBars();
}
mLetterboxConfiguration = new LetterboxConfiguration(
@@ -2446,6 +2451,9 @@
ProtoLog.i(WM_DEBUG_SCREEN_ON,
"Relayout %s: oldVis=%d newVis=%d. %s", win, oldVisibility,
viewVisibility, new RuntimeException().fillInStackTrace());
+ if (becameVisible) {
+ onWindowVisible(win);
+ }
win.setDisplayLayoutNeeded();
win.mGivenInsetsPending = (flags & WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
@@ -10168,7 +10176,7 @@
* Called to notify WMS that the specified window has become visible. This shows a Toast if the
* window is deemed to hold sensitive content.
*/
- void onWindowVisible(@NonNull WindowState w) {
+ private void onWindowVisible(@NonNull WindowState w) {
showToastIfBlockingScreenCapture(w);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 731184f..1f06bfa 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -848,7 +848,12 @@
return -1;
}
synchronized (mInternal.mGlobalLock) {
- mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier);
+ try {
+ mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier);
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println("Error: invalid multiplier value " + e);
+ return -1;
+ }
}
return 0;
}
@@ -867,7 +872,12 @@
return -1;
}
synchronized (mInternal.mGlobalLock) {
- mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(multiplier);
+ try {
+ mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(multiplier);
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println("Error: invalid multiplier value " + e);
+ return -1;
+ }
}
return 0;
}
@@ -1539,9 +1549,9 @@
pw.println(" both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will");
pw.println(" be ignored and framework implementation will determine aspect ratio.");
pw.println(" --cornerRadius radius");
- pw.println(" Corners radius for activities in the letterbox mode. If radius < 0,");
- pw.println(" both it and R.integer.config_letterboxActivityCornersRadius will be");
- pw.println(" ignored and corners of the activity won't be rounded.");
+ pw.println(" Corners radius (in pixels) for activities in the letterbox mode.");
+ pw.println(" If radius < 0, both R.integer.config_letterboxActivityCornersRadius");
+ pw.println(" and it will be ignored and corners of the activity won't be rounded.");
pw.println(" --backgroundType [reset|solid_color|app_color_background");
pw.println(" |app_color_background_floating|wallpaper]");
pw.println(" Type of background used in the letterbox mode.");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index e1ad1be..4180475 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -69,6 +69,7 @@
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
+import static com.android.server.wm.ActivityTaskManagerService.isPip2ExperimentEnabled;
import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
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;
@@ -608,6 +609,11 @@
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId);
mService.deferWindowLayout();
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */);
+ final boolean shouldDeferTransitionReady = transition != null && !t.isEmpty()
+ && (transition.isCollecting() || Flags.alwaysDeferTransitionWhenApplyWct());
+ if (shouldDeferTransitionReady) {
+ transition.deferTransitionReady();
+ }
try {
final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>();
if (transition != null) {
@@ -760,6 +766,9 @@
mService.mWindowManager.mWindowPlacerLocked.requestTraversal();
}
} finally {
+ if (shouldDeferTransitionReady) {
+ transition.continueTransitionReady();
+ }
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
mService.continueWindowLayout();
}
@@ -829,18 +838,21 @@
}
private int applyTaskChanges(Task tr, WindowContainerTransaction.Change c) {
+ final boolean wasPrevFocusableAndVisible = tr.isFocusableAndVisible();
+
int effects = applyChanges(tr, c);
final SurfaceControl.Transaction t = c.getBoundsChangeTransaction();
if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
if (tr.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, c.getHidden())) {
- effects = TRANSACT_EFFECTS_LIFECYCLE;
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
}
if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) {
- tr.setForceTranslucent(c.getForceTranslucent());
- effects = TRANSACT_EFFECTS_LIFECYCLE;
+ if (tr.setForceTranslucent(c.getForceTranslucent())) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
}
if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING) != 0) {
@@ -873,8 +885,17 @@
boolean canEnterPip = activity.checkEnterPictureInPictureState(
"applyTaskChanges", true /* beforeStopping */);
if (canEnterPip) {
- canEnterPip = mService.mActivityClientController
- .requestPictureInPictureMode(activity);
+ mService.mTaskSupervisor.beginDeferResume();
+ try {
+ canEnterPip = mService.mActivityClientController
+ .requestPictureInPictureMode(activity);
+ } finally {
+ mService.mTaskSupervisor.endDeferResume();
+ if (canEnterPip && !isPip2ExperimentEnabled()) {
+ // Wait until the transaction is applied to only resume once.
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ }
}
if (!canEnterPip) {
// Restore the flag to its previous state when the activity cannot enter PIP.
@@ -883,6 +904,11 @@
}
}
+ // Activity in this Task may resume/pause when enter/exit pip.
+ if (wasPrevFocusableAndVisible != tr.isFocusableAndVisible()) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+
return effects;
}
@@ -947,8 +973,9 @@
}
}
if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) {
- taskFragment.setForceTranslucent(c.getForceTranslucent());
- effects = TRANSACT_EFFECTS_LIFECYCLE;
+ if (taskFragment.setForceTranslucent(c.getForceTranslucent())) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
}
effects |= applyChanges(taskFragment, c);
@@ -1083,14 +1110,8 @@
launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
final SafeActivityOptions safeOptions =
SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid);
- if (transition != null) {
- transition.deferTransitionReady();
- }
waitAsyncStart(() -> mService.mTaskSupervisor.startActivityFromRecents(
caller.mPid, caller.mUid, taskId, safeOptions));
- if (transition != null) {
- transition.continueTransitionReady();
- }
break;
}
case HIERARCHY_OP_TYPE_REORDER:
@@ -1168,17 +1189,11 @@
activityOptions.setCallerDisplayId(DEFAULT_DISPLAY);
}
final Bundle options = activityOptions != null ? activityOptions.toBundle() : null;
- if (transition != null) {
- transition.deferTransitionReady();
- }
int res = waitAsyncStart(() -> mService.mAmInternal.sendIntentSender(
hop.getPendingIntent().getTarget(),
hop.getPendingIntent().getWhitelistToken(), 0 /* code */,
hop.getActivityIntent(), resolvedType, null /* finishReceiver */,
null /* requiredPermission */, options));
- if (transition != null) {
- transition.continueTransitionReady();
- }
if (ActivityManager.isStartResultSuccessful(res)) {
effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
@@ -1551,26 +1566,32 @@
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);
- if (!taskFragment.hasChild()) {
- // Ensure that the child layers are updated if the TaskFragment is empty.
- task.assignChildLayers();
+ if (task.mChildren.peekFirst() != taskFragment) {
+ task.mChildren.remove(taskFragment);
+ task.mChildren.add(0, taskFragment);
+ if (!taskFragment.hasChild()) {
+ // Ensure that the child layers are updated if the TaskFragment is
+ // empty.
+ task.assignChildLayers();
+ }
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
- 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);
- if (!taskFragment.hasChild()) {
- // Ensure that the child layers are updated if the TaskFragment is empty.
- task.assignChildLayers();
+ if (task.mChildren.peekLast() != taskFragment) {
+ task.mChildren.remove(taskFragment);
+ task.mChildren.add(taskFragment);
+ if (!taskFragment.hasChild()) {
+ // Ensure that the child layers are updated if the TaskFragment is
+ // empty.
+ task.assignChildLayers();
+ }
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
break;
}
@@ -2293,6 +2314,9 @@
TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
taskFragment.setTaskFragmentOrganizer(organizerToken,
ownerActivity.getUid(), ownerActivity.info.processName);
+ if (mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder())) {
+ taskFragment.setOverrideOrientation(creationParams.getOverrideOrientation());
+ }
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 4d9fc6c..2fcee50 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -28,7 +28,6 @@
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.PowerManager.DRAW_WAKE_LOCK;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.permission.flags.Flags.sensitiveContentImprovements;
import static android.view.SurfaceControl.Transaction;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
@@ -2139,9 +2138,6 @@
}
}
setDisplayLayoutNeeded();
- if (sensitiveContentImprovements() && visible) {
- mWmService.onWindowVisible(this);
- }
}
}
@@ -2974,6 +2970,11 @@
return true;
}
+ // Do not allow back predictive animation target to receive touch, app can trigger an
+ // unexpected transition so basically unable to polish it.
+ if (mWmService.mAtmService.mBackNavigationController.shouldPauseTouch(mActivityRecord)) {
+ return false;
+ }
return !mActivityRecord.getTask().getRootTask().shouldIgnoreInput()
&& mActivityRecord.isVisibleRequested();
}
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index b999305f..736b051 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -1,12 +1,8 @@
-# Display
-per-file com_android_server_lights_LightsService.cpp = [email protected], [email protected]
-
# Input
per-file com_android_server_input_* = file:/INPUT_OWNERS
# Power
-per-file com_android_server_HardwarePropertiesManagerService.cpp = [email protected], [email protected]
-per-file com_android_server_power_PowerManagerService.* = [email protected], [email protected]
+per-file com_android_server_HardwarePropertiesManagerService.cpp = file:/services/core/java/com/android/server/power/OWNERS
# BatteryStats
per-file com_android_server_am_BatteryStatsService.cpp = file:/BATTERY_STATS_OWNERS
@@ -16,6 +12,7 @@
per-file com_android_server_Usb* = file:/services/usb/OWNERS
per-file com_android_server_Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
per-file com_android_server_accessibility_* = file:/services/accessibility/OWNERS
+per-file com_android_server_display_* = file:/services/core/java/com/android/server/display/OWNERS
per-file com_android_server_hdmi_* = file:/core/java/android/hardware/hdmi/OWNERS
per-file com_android_server_lights_* = file:/services/core/java/com/android/server/lights/OWNERS
per-file com_android_server_location_* = file:/location/java/android/location/OWNERS
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 88c47f3..2f880ba 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -128,7 +128,8 @@
jmethodID getVirtualKeyQuietTimeMillis;
jmethodID getExcludedDeviceNames;
jmethodID getInputPortAssociations;
- jmethodID getInputUniqueIdAssociations;
+ jmethodID getInputUniqueIdAssociationsByPort;
+ jmethodID getInputUniqueIdAssociationsByDescriptor;
jmethodID getDeviceTypeAssociations;
jmethodID getKeyboardLayoutAssociations;
jmethodID getHoverTapTimeout;
@@ -634,10 +635,13 @@
env->DeleteLocalRef(portAssociations);
}
- outConfig->uniqueIdAssociations =
- readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo
- .getInputUniqueIdAssociations,
- "getInputUniqueIdAssociations");
+ outConfig->uniqueIdAssociationsByPort = readMapFromInterleavedJavaArray<
+ std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByPort,
+ "getInputUniqueIdAssociationsByPort");
+
+ outConfig->uniqueIdAssociationsByDescriptor = readMapFromInterleavedJavaArray<
+ std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor,
+ "getInputUniqueIdAssociationsByDescriptor");
outConfig->deviceTypeAssociations =
readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo
@@ -3090,8 +3094,11 @@
GET_METHOD_ID(gServiceClassInfo.getInputPortAssociations, clazz,
"getInputPortAssociations", "()[Ljava/lang/String;");
- GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociations, clazz,
- "getInputUniqueIdAssociations", "()[Ljava/lang/String;");
+ GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociationsByPort, clazz,
+ "getInputUniqueIdAssociationsByPort", "()[Ljava/lang/String;");
+
+ GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor, clazz,
+ "getInputUniqueIdAssociationsByDescriptor", "()[Ljava/lang/String;");
GET_METHOD_ID(gServiceClassInfo.getDeviceTypeAssociations, clazz, "getDeviceTypeAssociations",
"()[Ljava/lang/String;");
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index 8ca5333..da95666 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -487,7 +487,6 @@
timer_id_t front = headTimerId();
auto found = running_.find(key);
if (found != running_.end()) running_.erase(found);
- if (front != headTimerId()) restartLocked();
}
// Remove every timer associated with the service.
@@ -501,7 +500,6 @@
i++;
}
}
- if (front != headTimerId()) restartLocked();
}
// Return the number of timers still running.
diff --git a/services/core/jni/linux/usb/f_accessory.h b/services/core/jni/linux/usb/f_accessory.h
new file mode 100644
index 0000000..abd864c
--- /dev/null
+++ b/services/core/jni/linux/usb/f_accessory.h
@@ -0,0 +1,34 @@
+/*
+ * This file is auto-generated. Modifications will be lost.
+ *
+ * See https://android.googlesource.com/platform/bionic/+/master/libc/kernel/
+ * for more information.
+ */
+#ifndef _UAPI_LINUX_USB_F_ACCESSORY_H
+#define _UAPI_LINUX_USB_F_ACCESSORY_H
+#define USB_ACCESSORY_VENDOR_ID 0x18D1
+#define USB_ACCESSORY_PRODUCT_ID 0x2D00
+#define USB_ACCESSORY_ADB_PRODUCT_ID 0x2D01
+#define ACCESSORY_STRING_MANUFACTURER 0
+#define ACCESSORY_STRING_MODEL 1
+#define ACCESSORY_STRING_DESCRIPTION 2
+#define ACCESSORY_STRING_VERSION 3
+#define ACCESSORY_STRING_URI 4
+#define ACCESSORY_STRING_SERIAL 5
+#define ACCESSORY_GET_PROTOCOL 51
+#define ACCESSORY_SEND_STRING 52
+#define ACCESSORY_START 53
+#define ACCESSORY_REGISTER_HID 54
+#define ACCESSORY_UNREGISTER_HID 55
+#define ACCESSORY_SET_HID_REPORT_DESC 56
+#define ACCESSORY_SEND_HID_EVENT 57
+#define ACCESSORY_SET_AUDIO_MODE 58
+#define ACCESSORY_GET_STRING_MANUFACTURER _IOW('M', 1, char[256])
+#define ACCESSORY_GET_STRING_MODEL _IOW('M', 2, char[256])
+#define ACCESSORY_GET_STRING_DESCRIPTION _IOW('M', 3, char[256])
+#define ACCESSORY_GET_STRING_VERSION _IOW('M', 4, char[256])
+#define ACCESSORY_GET_STRING_URI _IOW('M', 5, char[256])
+#define ACCESSORY_GET_STRING_SERIAL _IOW('M', 6, char[256])
+#define ACCESSORY_IS_START_REQUESTED _IO('M', 7)
+#define ACCESSORY_GET_AUDIO_MODE _IO('M', 8)
+#endif
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index bb46c44..73e53e2 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -28,7 +28,6 @@
import android.credentials.selection.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.service.credentials.CallingAppInfo;
import android.util.Slog;
@@ -151,7 +150,7 @@
}
@Override
- public void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver) {
+ public void onUiCancellation(boolean isUserCancellation) {
// Not needed since UI is not involved
}
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 3513cb5..9781fb9 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -31,7 +31,6 @@
import android.credentials.selection.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.PermissionUtils;
import android.util.Slog;
@@ -164,7 +163,7 @@
}
@Override
- public void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver) {
+ public void onUiCancellation(boolean isUserCancellation) {
String exception = CreateCredentialException.TYPE_USER_CANCELED;
String message = "User cancelled the selector";
if (!isUserCancellation) {
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index bfa2d61..e73dacb 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -15,8 +15,6 @@
*/
package com.android.server.credentials;
-import static android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER;
-
import android.annotation.NonNull;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -82,25 +80,18 @@
UserSelectionDialogResult selection = UserSelectionDialogResult
.fromResultData(resultData);
if (selection != null) {
- ResultReceiver resultReceiver = resultData.getParcelable(
- EXTRA_FINAL_RESPONSE_RECEIVER,
- ResultReceiver.class);
- mCallbacks.onUiSelection(selection, resultReceiver);
+ mCallbacks.onUiSelection(selection);
}
break;
case UserSelectionDialogResult.RESULT_CODE_DIALOG_USER_CANCELED:
mStatus = UiStatus.TERMINATED;
- mCallbacks.onUiCancellation(/* isUserCancellation= */ true,
- resultData.getParcelable(EXTRA_FINAL_RESPONSE_RECEIVER,
- ResultReceiver.class));
+ mCallbacks.onUiCancellation(/* isUserCancellation= */ true);
break;
case UserSelectionDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS:
mStatus = UiStatus.TERMINATED;
- mCallbacks.onUiCancellation(/* isUserCancellation= */ false,
- resultData.getParcelable(EXTRA_FINAL_RESPONSE_RECEIVER,
- ResultReceiver.class));
+ mCallbacks.onUiCancellation(/* isUserCancellation= */ false);
break;
case UserSelectionDialogResult.RESULT_CODE_DATA_PARSING_FAILURE:
mStatus = UiStatus.TERMINATED;
@@ -124,10 +115,10 @@
*/
public interface CredentialManagerUiCallback {
/** Called when the user makes a selection. */
- void onUiSelection(UserSelectionDialogResult selection, ResultReceiver resultReceiver);
+ void onUiSelection(UserSelectionDialogResult selection);
/** Called when the UI is canceled without a successful provider result. */
- void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver);
+ void onUiCancellation(boolean isUserCancellation);
/** Called when the selector UI fails to come up (mostly due to parsing issue today). */
void onUiSelectorInvocationFailure();
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index 69d32a0..b1673e2 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -26,6 +26,7 @@
import android.credentials.CredentialProviderInfo;
import android.credentials.GetCandidateCredentialsException;
import android.credentials.GetCandidateCredentialsResponse;
+import android.credentials.GetCredentialException;
import android.credentials.GetCredentialRequest;
import android.credentials.GetCredentialResponse;
import android.credentials.IGetCandidateCredentialsCallback;
@@ -64,6 +65,8 @@
private final int mAutofillSessionId;
private final int mAutofillRequestId;
+ private final ResultReceiver mAutofillCallback;
+
public GetCandidateRequestSession(
Context context, SessionLifetime sessionCallback,
Object lock, int userId, int callingUid,
@@ -77,6 +80,8 @@
mClientBinder = clientBinder;
mAutofillSessionId = request.getData().getInt(SESSION_ID_KEY, -1);
mAutofillRequestId = request.getData().getInt(REQUEST_ID_KEY, -1);
+ mAutofillCallback = request.getData().getParcelable(
+ CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER, ResultReceiver.class);
if (mClientBinder != null) {
setUpClientCallbackListener(mClientBinder);
}
@@ -155,34 +160,34 @@
public void onFinalErrorReceived(ComponentName componentName, String errorType,
String message) {
Slog.d(TAG, "onFinalErrorReceived");
- respondToFinalReceiverWithFailureAndFinish(this.mFinalResponseReceiver, errorType, message);
+ if (GetCredentialException.TYPE_USER_CANCELED.equals(errorType)) {
+ Slog.d(TAG, "User canceled but session is not being terminated");
+ return;
+ }
+ respondToFinalReceiverWithFailureAndFinish(errorType, message);
}
@Override
- public void onUiCancellation(boolean isUserCancellation,
- @Nullable ResultReceiver finalResponseReceiver) {
- String exception = GetCandidateCredentialsException.TYPE_USER_CANCELED;
- String message = "User cancelled the selector";
- if (!isUserCancellation) {
- exception = GetCandidateCredentialsException.TYPE_INTERRUPTED;
- message = "The UI was interrupted - please try again.";
- }
- mRequestSessionMetric.collectFrameworkException(exception);
- respondToFinalReceiverWithFailureAndFinish(finalResponseReceiver, exception, message);
+ public void onUiCancellation(boolean isUserCancellation) {
+ Slog.d(TAG, "User canceled but session is not being terminated");
}
private void respondToFinalReceiverWithFailureAndFinish(
- ResultReceiver finalResponseReceiver,
String exception, String message
) {
- if (finalResponseReceiver != null) {
+ if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
+ Slog.w(TAG, "Request has already been completed. This is strange.");
+ return;
+ }
+
+ if (mAutofillCallback != null) {
Bundle resultData = new Bundle();
resultData.putStringArray(
CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
new String[] {exception, message});
- finalResponseReceiver.send(Constants.FAILURE_CREDMAN_SELECTOR, resultData);
+ mAutofillCallback.send(Constants.FAILURE_CREDMAN_SELECTOR, resultData);
} else {
- Slog.w(TAG, "onUiCancellation called but finalResponseReceiver not found");
+ Slog.w(TAG, "onUiCancellation called but mAutofillCallback not found");
}
finishSession(/*propagateCancellation=*/false, ApiStatus.FAILURE.getMetricCode());
}
@@ -219,12 +224,25 @@
public void onFinalResponseReceived(ComponentName componentName,
GetCredentialResponse response) {
Slog.d(TAG, "onFinalResponseReceived");
- if (this.mFinalResponseReceiver != null) {
+ if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
+ Slog.w(TAG, "Request has already been completed. This is strange.");
+ return;
+ }
+ respondToFinalReceiverWithResponseAndFinish(response);
+ }
+
+ private void respondToFinalReceiverWithResponseAndFinish(GetCredentialResponse response) {
+ if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
+ Slog.w(TAG, "Request has already been completed. This is strange.");
+ return;
+ }
+
+ if (this.mAutofillCallback != null) {
Slog.d(TAG, "onFinalResponseReceived sending through final receiver");
Bundle resultData = new Bundle();
resultData.putParcelable(
CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response);
- mFinalResponseReceiver.send(Constants.SUCCESS_CREDMAN_SELECTOR, resultData);
+ mAutofillCallback.send(Constants.SUCCESS_CREDMAN_SELECTOR, resultData);
finishSession(/*propagateCancellation=*/ false, ApiStatus.SUCCESS.getMetricCode());
} else {
Slog.w(TAG, "onFinalResponseReceived result receiver not found for pinned entry");
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index c26229b..be36b6c 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -32,7 +32,6 @@
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.PermissionUtils;
import android.util.Slog;
@@ -166,7 +165,7 @@
}
@Override
- public void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver) {
+ public void onUiCancellation(boolean isUserCancellation) {
String exception = GetCredentialException.TYPE_USER_CANCELED;
String message = "User cancelled the selector";
if (!isUserCancellation) {
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 054ba2b..c0bc8e0 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -17,7 +17,6 @@
package com.android.server.credentials;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -35,7 +34,6 @@
import android.os.IInterface;
import android.os.Looper;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.os.UserHandle;
import android.service.credentials.CallingAppInfo;
import android.util.Slog;
@@ -104,9 +102,6 @@
protected PendingIntent mPendingIntent;
- @Nullable
- protected ResultReceiver mFinalResponseReceiver;
-
@NonNull
protected RequestSessionStatus mRequestSessionStatus =
RequestSessionStatus.IN_PROGRESS;
@@ -225,8 +220,7 @@
// UI callbacks
@Override // from CredentialManagerUiCallbacks
- public void onUiSelection(UserSelectionDialogResult selection,
- ResultReceiver finalResponseReceiver) {
+ public void onUiSelection(UserSelectionDialogResult selection) {
if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
Slog.w(TAG, "Request has already been completed. This is strange.");
return;
@@ -242,7 +236,7 @@
Slog.w(TAG, "providerSession not found in onUiSelection. This is strange.");
return;
}
- mFinalResponseReceiver = finalResponseReceiver;
+
ProviderSessionMetric providerSessionMetric = providerSession.mProviderSessionMetric;
int initialAuthMetricsProvider = providerSessionMetric.getBrowsedAuthenticationMetric()
.size();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index cb63757..7e083ba 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -107,6 +107,8 @@
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION;
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_SUSPENSION;
+import static android.app.AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
+import static android.app.AppOpsManager.OP_RUN_IN_BACKGROUND;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
@@ -886,10 +888,6 @@
"enable_permission_based_access";
private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false;
- // TODO(b/266831522) remove the flag after rollout.
- private static final String APPLICATION_EXEMPTIONS_FLAG = "application_exemptions";
- private static final boolean DEFAULT_APPLICATION_EXEMPTIONS_FLAG = true;
-
private static final int RETRY_COPY_ACCOUNT_ATTEMPTS = 3;
/**
@@ -3689,26 +3687,6 @@
mDevicePolicyEngine.handleStartUser(userId);
}
- void pushUserControlDisabledPackagesLocked(int userId) {
- final int targetUserId;
- final ActiveAdmin owner;
- if (getDeviceOwnerUserIdUncheckedLocked() == userId) {
- owner = getDeviceOwnerAdminLocked();
- targetUserId = UserHandle.USER_ALL;
- } else {
- owner = getProfileOwnerAdminLocked(userId);
- targetUserId = userId;
- }
-
- List<String> protectedPackages = (owner == null || owner.protectedPackages == null)
- ? null : owner.protectedPackages;
- mInjector.binderWithCleanCallingIdentity(() ->
- mInjector.getPackageManagerInternal().setOwnerProtectedPackages(
- targetUserId, protectedPackages));
- mUsageStatsManagerInternal.setAdminProtectedPackages(new ArraySet(protectedPackages),
- targetUserId);
- }
-
void handleUnlockUser(int userId) {
startOwnerService(userId, "unlock-user");
mDevicePolicyEngine.handleUnlockUser(userId);
@@ -3957,7 +3935,7 @@
if (policy.mPasswordOwner == oldAdminUid) {
policy.mPasswordOwner = adminToTransfer.getUid();
}
-
+ transferSubscriptionOwnership(outgoingReceiver, incomingReceiver);
saveSettingsLocked(userHandle);
sendAdminCommandLocked(adminToTransfer, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED,
null, null);
@@ -15913,14 +15891,6 @@
}
@Override
- public boolean isApplicationExemptionsFlagEnabled() {
- return DeviceConfig.getBoolean(
- NAMESPACE_DEVICE_POLICY_MANAGER,
- APPLICATION_EXEMPTIONS_FLAG,
- DEFAULT_APPLICATION_EXEMPTIONS_FLAG);
- }
-
- @Override
public List<Bundle> getApplicationRestrictionsPerAdminForUser(
String packageName, @UserIdInt int userId) {
if (UserHandle.getCallingUserId() != userId
@@ -19501,6 +19471,21 @@
.write();
}
+ private void transferSubscriptionOwnership(ComponentName admin, ComponentName target) {
+ if (Flags.esimManagementEnabled()) {
+ SubscriptionManager subscriptionManager = mContext.getSystemService(
+ SubscriptionManager.class);
+ for (int subId : getSubscriptionIdsInternal(admin.getPackageName()).toArray()) {
+ try {
+ subscriptionManager.setGroupOwner(subId, target.getPackageName());
+ } catch (Exception e) {
+ // Shouldn't happen.
+ Slogf.e(LOG_TAG, e, "Error setting group owner for subId: " + subId);
+ }
+ }
+ }
+ }
+
private void prepareTransfer(ComponentName admin, ComponentName target,
PersistableBundle bundle, int callingUserId, String adminType) {
saveTransferOwnershipBundleLocked(bundle, callingUserId);
@@ -20378,34 +20363,47 @@
hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS));
final CallerIdentity caller = getCallerIdentity(callerPackage);
- final ApplicationInfo packageInfo;
- packageInfo = getPackageInfoWithNullCheck(packageName, caller);
+ final AppOpsManager appOpsMgr = mInjector.getAppOpsManager();
+ final ApplicationInfo appInfo = getPackageInfoWithNullCheck(packageName, caller);
+ final int uid = appInfo.uid;
- for (Map.Entry<Integer, String> entry :
- APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.entrySet()) {
- int currentMode = mInjector.getAppOpsManager().unsafeCheckOpNoThrow(
- entry.getValue(), packageInfo.uid, packageInfo.packageName);
- int newMode = ArrayUtils.contains(exemptions, entry.getKey())
- ? MODE_ALLOWED : MODE_DEFAULT;
- mInjector.binderWithCleanCallingIdentity(() -> {
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.forEach((exemption, appOp) -> {
+ int currentMode = appOpsMgr.unsafeCheckOpNoThrow(appOp, uid, packageName);
+ int newMode = ArrayUtils.contains(exemptions, exemption)
+ ? MODE_ALLOWED : MODE_DEFAULT;
if (currentMode != newMode) {
- mInjector.getAppOpsManager()
- .setMode(entry.getValue(),
- packageInfo.uid,
- packageName,
- newMode);
+ appOpsMgr.setMode(appOp, uid, packageName, newMode);
+
+ // If the user has already disabled background usage for the package, it won't
+ // have OP_RUN_ANY_IN_BACKGROUND app op and won't execute in the background. The
+ // code below grants that app op, and once the exemption is in place, the user
+ // won't be able to disable background usage anymore.
+ if (Flags.powerExemptionBgUsageFix()
+ && exemption == EXEMPT_FROM_POWER_RESTRICTIONS
+ && newMode == MODE_ALLOWED) {
+ setBgUsageAppOp(appOpsMgr, appInfo);
+ }
}
});
- }
+ });
+
String[] appOpExemptions = new String[exemptions.length];
for (int i = 0; i < exemptions.length; i++) {
appOpExemptions[i] = APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.get(exemptions[i]);
}
DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.SET_APPLICATION_EXEMPTIONS)
- .setAdmin(caller.getPackageName())
- .setStrings(packageName, appOpExemptions)
- .write();
+ .createEvent(DevicePolicyEnums.SET_APPLICATION_EXEMPTIONS)
+ .setAdmin(caller.getPackageName())
+ .setStrings(packageName, appOpExemptions)
+ .write();
+ }
+
+ static void setBgUsageAppOp(AppOpsManager appOpsMgr, ApplicationInfo appInfo) {
+ appOpsMgr.setMode(OP_RUN_ANY_IN_BACKGROUND, appInfo.uid, appInfo.packageName, MODE_ALLOWED);
+ if (appInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+ appOpsMgr.setMode(OP_RUN_IN_BACKGROUND, appInfo.uid, appInfo.packageName, MODE_ALLOWED);
+ }
}
@Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
index 94c1374..d1830126 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
@@ -215,6 +215,9 @@
} catch (PackageManager.NameNotFoundException e) {
return false;
}
+ if (packageInfo.applicationInfo == null || packageInfo.applicationInfo.metaData == null) {
+ return false;
+ }
final String metadataKey = sActionToMetadataKeyMap.get(provisioningAction);
return packageInfo.applicationInfo.metaData.getBoolean(metadataKey);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 42ac998..d02cfee 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -357,7 +357,8 @@
@Override
boolean shouldWrite() {
- return (mDeviceOwner != null) || (mSystemUpdatePolicy != null)
+ return Flags.alwaysPersistDo()
+ || (mDeviceOwner != null) || (mSystemUpdatePolicy != null)
|| (mSystemUpdateInfo != null);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index e713a82..7a9fa0f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -163,8 +163,7 @@
new NoArgsPolicyKey(
DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY),
new StringSetUnion(),
- (Set<String> value, Context context, Integer userId, PolicyKey policyKey) ->
- PolicyEnforcerCallbacks.setUserControlDisabledPackages(value, userId),
+ PolicyEnforcerCallbacks::setUserControlDisabledPackages,
new StringSetPolicySerializer());
// This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index a7adc5b..a0d9be54 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
+import android.app.AppOpsManager;
import android.app.admin.DevicePolicyCache;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -29,6 +30,7 @@
import android.app.admin.PackagePolicyKey;
import android.app.admin.PolicyKey;
import android.app.admin.UserRestrictionPolicyKey;
+import android.app.admin.flags.Flags;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
@@ -37,6 +39,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -183,15 +186,31 @@
}
static boolean setUserControlDisabledPackages(
- @Nullable Set<String> packages, int userId) {
+ @Nullable Set<String> packages, Context context, int userId, PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
- LocalServices.getService(PackageManagerInternal.class)
- .setOwnerProtectedPackages(
- userId,
- packages == null ? null : packages.stream().toList());
+ PackageManagerInternal pmi =
+ LocalServices.getService(PackageManagerInternal.class);
+ pmi.setOwnerProtectedPackages(userId,
+ packages == null ? null : packages.stream().toList());
LocalServices.getService(UsageStatsManagerInternal.class)
- .setAdminProtectedPackages(
+ .setAdminProtectedPackages(
packages == null ? null : new ArraySet<>(packages), userId);
+
+ if (Flags.disallowUserControlBgUsageFix()) {
+ if (packages == null) {
+ return;
+ }
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ for (var pkg : packages) {
+ final var appInfo = pmi.getApplicationInfo(pkg,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ Process.myUid(), userId);
+ if (appInfo != null) {
+ DevicePolicyManagerService.setBgUsageAppOp(appOpsManager, appInfo);
+ }
+ }
+ }
});
return true;
}
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 3bce9b5..0da17e1 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -84,6 +84,7 @@
],
srcs: [
"src/com/android/server/inputmethod/**/ClientControllerTest.java",
+ "src/com/android/server/inputmethod/**/UserDataRepositoryTest.java",
],
auto_gen_config: true,
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
new file mode 100644
index 0000000..a15b170
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.pm.UserInfo;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.server.pm.UserManagerInternal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+// This test is designed to run on both device and host (Ravenwood) side.
+public final class UserDataRepositoryTest {
+
+ private static final int ANY_USER_ID = 1;
+
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true).build();
+
+ @Mock
+ private UserManagerInternal mMockUserManagerInternal;
+
+ private Handler mHandler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ @Test
+ public void testUserDataRepository_addsNewUserInfoOnUserCreatedEvent() {
+ // Create UserDataRepository and capture the user lifecycle listener
+ final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
+ final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal);
+ verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
+ final var listener = captor.getValue();
+
+ // Assert that UserDataRepository is empty and then call onUserCreated
+ assertThat(collectUserData(repository)).isEmpty();
+ final var userInfo = new UserInfo();
+ userInfo.id = ANY_USER_ID;
+ listener.onUserCreated(userInfo, /* unused token */ new Object());
+ waitForIdle();
+
+ // Assert UserDataRepository contains the expected UserData
+ final var allUserData = collectUserData(repository);
+ assertThat(allUserData).hasSize(1);
+ assertThat(allUserData.get(0).mUserId).isEqualTo(userInfo.id);
+ }
+
+ @Test
+ public void testUserDataRepository_removesUserInfoOnUserRemovedEvent() {
+ // Create UserDataRepository and capture the user lifecycle listener
+ final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
+ final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal);
+ verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
+ final var listener = captor.getValue();
+
+ // Add one UserData ...
+ final var userInfo = new UserInfo();
+ userInfo.id = ANY_USER_ID;
+ listener.onUserCreated(userInfo, /* unused token */ new Object());
+ waitForIdle();
+ // ... and then call onUserRemoved
+ assertThat(collectUserData(repository)).hasSize(1);
+ listener.onUserRemoved(userInfo);
+ waitForIdle();
+
+ // Assert UserDataRepository is now empty
+ assertThat(collectUserData(repository)).isEmpty();
+ }
+
+ @Test
+ public void testGetOrCreate() {
+ final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal);
+
+ synchronized (ImfLock.class) {
+ final var userData = repository.getOrCreate(ANY_USER_ID);
+ assertThat(userData.mUserId).isEqualTo(ANY_USER_ID);
+ }
+
+ final var allUserData = collectUserData(repository);
+ assertThat(allUserData).hasSize(1);
+ assertThat(allUserData.get(0).mUserId).isEqualTo(ANY_USER_ID);
+ }
+
+ private List<UserDataRepository.UserData> collectUserData(UserDataRepository repository) {
+ final var collected = new ArrayList<UserDataRepository.UserData>();
+ synchronized (ImfLock.class) {
+ repository.forAllUserData(userData -> collected.add(userData));
+ }
+ return collected;
+ }
+
+ private void waitForIdle() {
+ final var done = new ConditionVariable();
+ mHandler.post(done::open);
+ done.block();
+ }
+}
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 5897d76..0877146 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -920,6 +920,7 @@
@Test
public void testEvenDimmer() throws IOException {
when(mFlags.isEvenDimmerEnabled()).thenReturn(true);
+ when(mResources.getBoolean(R.bool.config_evenDimmerEnabled)).thenReturn(true);
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ true));
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 b0eee08..1666fef 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -1708,7 +1708,6 @@
* {@link VirtualDisplayConfig.Builder#setSurface(Surface)}
*/
@Test
- @FlakyTest(bugId = 127687569)
public void testCreateVirtualDisplay_setSurface() throws Exception {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
registerDefaultDisplays(displayManager);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
index 0cf0850..88c0daa 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
@@ -19,12 +19,13 @@
import android.content.Context
import android.content.ContextWrapper
import android.hardware.display.BrightnessInfo
-import android.util.SparseBooleanArray
+import android.util.SparseArray
import android.view.Display
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
import com.android.server.display.DisplayDeviceConfig
import com.android.server.display.feature.DisplayManagerFlags
+import com.android.server.display.mode.DisplayModeDirector.DisplayDeviceConfigProvider
import com.android.server.testutils.TestHandler
import com.google.common.truth.Truth.assertThat
import com.google.testing.junit.testparameterinjector.TestParameter
@@ -49,6 +50,7 @@
private val mockInjector = mock<DisplayModeDirector.Injector>()
private val mockFlags = mock<DisplayManagerFlags>()
private val mockDeviceConfig = mock<DisplayDeviceConfig>()
+ private val mockDisplayDeviceConfigProvider = mock<DisplayDeviceConfigProvider>()
private val testHandler = TestHandler(null)
@@ -62,10 +64,11 @@
setUpLowBrightnessZone()
whenever(mockFlags.isVsyncLowLightVoteEnabled).thenReturn(testCase.vsyncLowLightVoteEnabled)
val displayModeDirector = DisplayModeDirector(
- spyContext, testHandler, mockInjector, mockFlags)
- val vrrByDisplay = SparseBooleanArray()
- vrrByDisplay.put(Display.DEFAULT_DISPLAY, testCase.vrrSupported)
- displayModeDirector.injectVrrByDisplay(vrrByDisplay)
+ spyContext, testHandler, mockInjector, mockFlags, mockDisplayDeviceConfigProvider)
+ val ddcByDisplay = SparseArray<DisplayDeviceConfig>()
+ whenever(mockDeviceConfig.isVrrSupportEnabled).thenReturn(testCase.vrrSupported)
+ ddcByDisplay.put(Display.DEFAULT_DISPLAY, mockDeviceConfig)
+ displayModeDirector.injectDisplayDeviceConfigByDisplay(ddcByDisplay)
val brightnessObserver = displayModeDirector.BrightnessObserver(
spyContext, testHandler, mockInjector, mockFlags)
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 0efd046..4591d91 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
@@ -312,6 +312,8 @@
public DisplayManagerInternal mDisplayManagerInternalMock;
@Mock
private DisplayManagerFlags mDisplayManagerFlags;
+ @Mock
+ private DisplayModeDirector.DisplayDeviceConfigProvider mDisplayDeviceConfigProvider;
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule =
@@ -412,7 +414,8 @@
private DisplayModeDirector createDirectorFromModeArray(Display.Mode[] modes,
Display.Mode defaultMode, int[] displayIds) {
DisplayModeDirector director =
- new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+ new DisplayModeDirector(mContext, mHandler, mInjector,
+ mDisplayManagerFlags, mDisplayDeviceConfigProvider);
director.setLoggingEnabled(true);
setupModesForDisplays(director, displayIds , modes, defaultMode);
return director;
@@ -1146,7 +1149,8 @@
@Test
public void testStaleAppRequestSize() {
DisplayModeDirector director =
- new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+ new DisplayModeDirector(mContext, mHandler, mInjector,
+ mDisplayManagerFlags, mDisplayDeviceConfigProvider);
Display.Mode[] modes = new Display.Mode[] {
new Display.Mode(1, 1280, 720, 60),
};
@@ -1397,7 +1401,8 @@
when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
.thenReturn(true);
DisplayModeDirector director =
- new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+ new DisplayModeDirector(mContext, mHandler, mInjector,
+ mDisplayManagerFlags, mDisplayDeviceConfigProvider);
director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
Display.Mode[] modes1 = new Display.Mode[] {
@@ -1808,7 +1813,8 @@
when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
.thenReturn(true);
DisplayModeDirector director =
- new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+ new DisplayModeDirector(mContext, mHandler, mInjector,
+ mDisplayManagerFlags, mDisplayDeviceConfigProvider);
director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
Display.Mode[] modes1 = new Display.Mode[] {
@@ -1888,7 +1894,8 @@
when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
.thenReturn(true);
DisplayModeDirector director =
- new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+ new DisplayModeDirector(mContext, mHandler, mInjector,
+ mDisplayManagerFlags, mDisplayDeviceConfigProvider);
director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
mInjector.mDisplayInfo.supportedModes = new Display.Mode[] {
new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
@@ -1958,7 +1965,8 @@
when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
.thenReturn(true);
DisplayModeDirector director =
- new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+ new DisplayModeDirector(mContext, mHandler, mInjector,
+ mDisplayManagerFlags, mDisplayDeviceConfigProvider);
director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
Display.Mode[] modes1 = new Display.Mode[] {
@@ -2038,7 +2046,8 @@
when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
.thenReturn(true);
DisplayModeDirector director =
- new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+ new DisplayModeDirector(mContext, mHandler, mInjector,
+ mDisplayManagerFlags, mDisplayDeviceConfigProvider);
director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
mInjector.mDisplayInfo.supportedModes = new Display.Mode[] {
new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
@@ -2076,7 +2085,8 @@
when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
.thenReturn(true);
DisplayModeDirector director =
- new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+ new DisplayModeDirector(mContext, mHandler, mInjector,
+ mDisplayManagerFlags, mDisplayDeviceConfigProvider);
director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
Display.Mode[] modes1 = new Display.Mode[] {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
index d0dd921..2d317af 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
@@ -113,6 +113,8 @@
private Resources mResources;
@Mock
private DisplayManagerFlags mDisplayManagerFlags;
+ @Mock
+ private DisplayModeDirector.DisplayDeviceConfigProvider mDisplayDeviceConfigProvider;
private int mExternalDisplayUserPreferredModeId = INVALID_MODE_ID;
private int mInternalDisplayUserPreferredModeId = INVALID_MODE_ID;
private Display mDefaultDisplay;
@@ -446,7 +448,8 @@
when(mInjector.getDisplays()).thenReturn(new Display[] {mDefaultDisplay, mExternalDisplay});
- mDmd = new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+ mDmd = new DisplayModeDirector(mContext, mHandler, mInjector,
+ mDisplayManagerFlags, mDisplayDeviceConfigProvider);
mDmd.start(null);
assertThat(mObserver).isNotNull();
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
index 196a202..3c87261 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
@@ -19,12 +19,14 @@
import android.content.Context
import android.content.ContextWrapper
import android.provider.Settings
-import android.util.SparseBooleanArray
+import android.util.SparseArray
import android.view.Display
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
import com.android.internal.util.test.FakeSettingsProvider
+import com.android.server.display.DisplayDeviceConfig
import com.android.server.display.feature.DisplayManagerFlags
+import com.android.server.display.mode.DisplayModeDirector.DisplayDeviceConfigProvider
import com.android.server.testutils.TestHandler
import com.google.common.truth.Truth.assertThat
import com.google.testing.junit.testparameterinjector.TestParameter
@@ -50,6 +52,8 @@
private lateinit var spyContext: Context
private val mockInjector = mock<DisplayModeDirector.Injector>()
private val mockFlags = mock<DisplayManagerFlags>()
+ private val mockDeviceConfig = mock<DisplayDeviceConfig>()
+ private val mockDisplayDeviceConfigProvider = mock<DisplayDeviceConfigProvider>()
private val testHandler = TestHandler(null)
@@ -68,10 +72,11 @@
spyContext.contentResolver, Settings.Global.LOW_POWER_MODE, lowPowerModeSetting)
val displayModeDirector = DisplayModeDirector(
- spyContext, testHandler, mockInjector, mockFlags)
- val vrrByDisplay = SparseBooleanArray()
- vrrByDisplay.put(Display.DEFAULT_DISPLAY, testCase.vrrSupported)
- displayModeDirector.injectVrrByDisplay(vrrByDisplay)
+ spyContext, testHandler, mockInjector, mockFlags, mockDisplayDeviceConfigProvider)
+ val ddcByDisplay = SparseArray<DisplayDeviceConfig>()
+ whenever(mockDeviceConfig.isVrrSupportEnabled).thenReturn(testCase.vrrSupported)
+ ddcByDisplay.put(Display.DEFAULT_DISPLAY, mockDeviceConfig)
+ displayModeDirector.injectDisplayDeviceConfigByDisplay(ddcByDisplay)
val settingsObserver = displayModeDirector.SettingsObserver(
spyContext, testHandler, mockFlags)
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
index edee8cd..124ae20 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
@@ -282,6 +282,14 @@
.getActiveNotifications();
}
+ private void setupNullNotifications() {
+ // Setup Notification Values
+ StatusBarNotification[] mNotifications = new StatusBarNotification[] { null, null};
+ doReturn(mNotifications)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+ }
+
private MediaProjectionInfo createMediaProjectionInfo() {
return new MediaProjectionInfo(SCREEN_RECORDER_PACKAGE, Process.myUserHandle(), null);
}
@@ -502,6 +510,17 @@
}
@Test
+ public void nlsOnListenerConnected_nullNotifications_noBlockedPackages() {
+ setupNullNotifications();
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
public void nlsOnListenerConnected_nullRankingMap_noBlockedPackages() {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index 7d3a110..c1f4fee 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -321,8 +321,7 @@
app1PackageName); // packageName
ServiceRecord service = ServiceRecord.newEmptyInstanceForTest(mAms);
- mAppStartInfoTracker.handleProcessServiceStart(appStartTimestampService, app, service,
- false);
+ mAppStartInfoTracker.handleProcessServiceStart(appStartTimestampService, app, service);
list.clear();
mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, 0, 0, list);
assertEquals(list.size(), 2);
@@ -336,7 +335,7 @@
app1ProcessName, // processName
ApplicationStartInfo.START_REASON_SERVICE, // reason
ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
- ApplicationStartInfo.START_TYPE_WARM, // state type
+ ApplicationStartInfo.START_TYPE_COLD, // state type
ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
// Case 5: Create an instance of app1 with a different user started for a broadcast
@@ -350,7 +349,7 @@
app1PackageName); // packageName
mAppStartInfoTracker.handleProcessBroadcastStart(appStartTimestampBroadcast, app,
- null, true /* isColdStart */);
+ buildIntent(COMPONENT), false /* isAlarm */);
list.clear();
mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
assertEquals(list.size(), 1);
@@ -395,7 +394,7 @@
app2PackageName); // packageName
mAppStartInfoTracker.handleProcessContentProviderStart(appStartTimestampRContentProvider,
- app, false);
+ app);
list.clear();
mAppStartInfoTracker.getStartInfo(app2PackageName, app2UidUser2, app2PidUser2, 0, list);
assertEquals(list.size(), 1);
@@ -409,7 +408,7 @@
app2ProcessName, // processName
ApplicationStartInfo.START_REASON_CONTENT_PROVIDER, // reason
ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
- ApplicationStartInfo.START_TYPE_WARM, // state type
+ ApplicationStartInfo.START_TYPE_COLD, // state type
ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
// Case 8: Save and load again
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index ce281da..5861917 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -112,6 +112,9 @@
@Mock
ProcessList mProcessList;
+ @Mock
+ AppStartInfoTracker mAppStartInfoTracker;
+
Context mContext;
ActivityManagerService mAms;
BroadcastConstants mConstants;
@@ -172,6 +175,8 @@
mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
+
+ doReturn(mAppStartInfoTracker).when(mProcessList).getAppStartInfoTracker();
}
public void tearDown() throws Exception {
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 97ae0bd..13ba1e5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -2393,6 +2393,20 @@
assertNull(mAms.getProcessRecordLocked(PACKAGE_BLUE, getUidForPackage(PACKAGE_BLUE)));
}
+ @Test
+ public void testBroadcastAppStartInfoReported() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+ final Intent timezone = new Intent(Intent.ACTION_TIME_TICK);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
+
+ waitForIdle();
+
+ verify(mAppStartInfoTracker, times(1)).handleProcessBroadcastStart(anyLong(), any(), any(),
+ anyBoolean());
+ }
+
private long getReceiverScheduledTime(@NonNull BroadcastRecord r, @NonNull Object receiver) {
for (int i = 0; i < r.receivers.size(); ++i) {
if (isReceiverEquals(receiver, r.receivers.get(i))) {
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 4c7a8fe..9590783 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -205,8 +205,6 @@
new ProcessStatsService(sService, new File(sContext.getFilesDir(), "procstats")));
setFieldValue(ActivityManagerService.class, sService, "mBackupTargets",
mock(SparseArray.class));
- setFieldValue(ActivityManagerService.class, sService, "mOomAdjProfiler",
- mock(OomAdjProfiler.class));
setFieldValue(ActivityManagerService.class, sService, "mUserController",
mock(UserController.class));
setFieldValue(ActivityManagerService.class, sService, "mAppProfiler", profiler);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
index 783971a..89b48ba 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
@@ -16,9 +16,18 @@
package com.android.server.am;
+import static android.os.Process.INVALID_UID;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_NULL;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_ONE_SHOT_SENT;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANCELED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_USER_STOPPED;
+import static com.android.server.am.PendingIntentRecord.cancelReasonToString;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -34,6 +43,7 @@
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.os.Looper;
+import android.os.UserHandle;
import androidx.test.runner.AndroidJUnit4;
@@ -54,6 +64,7 @@
private static final String TEST_PACKAGE_NAME = "test-package-1";
private static final String TEST_FEATURE_ID = "test-feature-1";
private static final int TEST_CALLING_UID = android.os.Process.myUid();
+ private static final int TEST_USER_ID = 0;
private static final Intent[] TEST_INTENTS = new Intent[]{new Intent("com.test.intent")};
@Mock
@@ -92,7 +103,7 @@
private PendingIntentRecord createPendingIntentRecord(int flags) {
return mPendingIntentController.getIntentSender(ActivityManager.INTENT_SENDER_BROADCAST,
- TEST_PACKAGE_NAME, TEST_FEATURE_ID, TEST_CALLING_UID, 0, null, null, 0,
+ TEST_PACKAGE_NAME, TEST_FEATURE_ID, TEST_CALLING_UID, TEST_USER_ID, null, null, 0,
TEST_INTENTS, null, flags, null);
}
@@ -126,6 +137,54 @@
piCaptor.getValue().getTarget());
}
+ @Test
+ public void testCancellationReason() {
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ assertCancelReason(CANCEL_REASON_NULL, pir.cancelReason);
+ }
+
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ mPendingIntentController.cancelIntentSender(pir);
+ assertCancelReason(CANCEL_REASON_OWNER_CANCELED, pir.cancelReason);
+ }
+
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ createPendingIntentRecord(PendingIntent.FLAG_CANCEL_CURRENT);
+ assertCancelReason(CANCEL_REASON_SUPERSEDED, pir.cancelReason);
+ }
+
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(PendingIntent.FLAG_ONE_SHOT);
+ pir.send(0, null, null, null, null, null, null);
+ assertCancelReason(CANCEL_REASON_ONE_SHOT_SENT, pir.cancelReason);
+ }
+
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ mPendingIntentController.removePendingIntentsForPackage(TEST_PACKAGE_NAME,
+ TEST_USER_ID, UserHandle.getAppId(TEST_CALLING_UID), true,
+ CANCEL_REASON_OWNER_FORCE_STOPPED);
+ assertCancelReason(CANCEL_REASON_OWNER_FORCE_STOPPED, pir.cancelReason);
+ }
+
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ mPendingIntentController.removePendingIntentsForPackage(null,
+ TEST_USER_ID, INVALID_UID, true,
+ CANCEL_REASON_USER_STOPPED);
+ assertCancelReason(CANCEL_REASON_USER_STOPPED, pir.cancelReason);
+ }
+ }
+
+ private void assertCancelReason(int expectedReason, int actualReason) {
+ final String errMsg = "Expected: " + cancelReasonToString(expectedReason)
+ + "; Actual: " + cancelReasonToString(actualReason);
+ assertEquals(errMsg, expectedReason, actualReason);
+ }
+
@After
public void tearDown() {
if (mMockingSession != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 6df4907..584fd62 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -24,6 +24,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.Flags.FLAG_COUNT_QUOTA_FIX;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
@@ -75,6 +76,9 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.SparseBooleanArray;
@@ -98,6 +102,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.ArgumentCaptor;
@@ -154,6 +159,10 @@
@Mock
private UsageStatsManagerInternal mUsageStatsManager;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private JobStore mJobStore;
@Before
@@ -1978,7 +1987,7 @@
}
@Test
- public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
+ public void testIsWithinQuotaLocked_UnderDuration_OverJobCountRateLimitWindow() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -2021,7 +2030,7 @@
}
@Test
- public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
+ public void testIsWithinQuotaLocked_OverDuration_OverJobCountRateLimitWindow() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -2167,6 +2176,74 @@
}
@Test
+ @RequiresFlagsEnabled(FLAG_COUNT_QUOTA_FIX)
+ public void testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow() {
+ setDischarging();
+
+ JobStatus jobRunning = createJobStatus(
+ "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 1);
+ JobStatus jobPending = createJobStatus(
+ "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 2);
+ setStandbyBucket(WORKING_INDEX, jobRunning, jobPending);
+
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10);
+
+ long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 9), false);
+
+ final ExecutionStats stats;
+ synchronized (mQuotaController.mLock) {
+ stats = mQuotaController.getExecutionStatsLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
+ assertTrue(mQuotaController
+ .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
+ assertEquals(10, stats.jobCountLimit);
+ assertEquals(9, stats.bgJobCountInWindow);
+ }
+
+ when(mJobSchedulerService.isCurrentlyRunningLocked(jobRunning)).thenReturn(true);
+ when(mJobSchedulerService.isCurrentlyRunningLocked(jobPending)).thenReturn(false);
+
+ InOrder inOrder = inOrder(mJobSchedulerService);
+ trackJobs(jobRunning, jobPending);
+ // UID in the background.
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ // Start the job.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobRunning);
+ }
+
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ // Wait for some extra time to allow for job processing.
+ ArraySet<JobStatus> expected = new ArraySet<>();
+ expected.add(jobPending);
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(eq(expected));
+
+ synchronized (mQuotaController.mLock) {
+ assertTrue(mQuotaController.isWithinQuotaLocked(jobRunning));
+ assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobRunning.isReady());
+ assertFalse(mQuotaController.isWithinQuotaLocked(jobPending));
+ assertFalse(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobPending.isReady());
+ assertEquals(10, stats.bgJobCountInWindow);
+ }
+
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobRunning, null);
+ }
+
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController
+ .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
+ assertEquals(10, stats.bgJobCountInWindow);
+ }
+ }
+
+ @Test
public void testIsWithinQuotaLocked_TimingSession() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -4651,7 +4728,7 @@
// Handler is told to check when the quota will be consumed, not when the initial
// remaining time is over.
verify(handler, atLeast(1)).sendMessageDelayed(
- argThat(msg -> msg.what == QuotaController.MSG_REACHED_QUOTA),
+ argThat(msg -> msg.what == QuotaController.MSG_REACHED_TIME_QUOTA),
eq(10 * SECOND_IN_MILLIS));
verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
@@ -6618,7 +6695,7 @@
// Handler is told to check when the quota will be consumed, not when the initial
// remaining time is over.
verify(handler, atLeast(1)).sendMessageDelayed(
- argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_QUOTA),
+ argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_TIME_QUOTA),
eq(10 * SECOND_IN_MILLIS));
verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
}
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 4535ece..8d0b279 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -191,6 +191,8 @@
when(mContext.checkCallingOrSelfPermission(
eq(Manifest.permission.REQUEST_DELETE_PACKAGES))).thenReturn(
PackageManager.PERMISSION_DENIED);
+ when(mContext.createPackageContextAsUser(
+ eq(INSTALLER_PACKAGE), anyInt(), eq(UserHandle.CURRENT))).thenReturn(mContext);
when(mAppOpsManager.checkOp(
eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED),
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
index ad29392..d51828e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
@@ -67,12 +67,11 @@
private static final int UID_2 = 99;
private final MockClock mMockClock = new MockClock();
private final HandlerThread mHandlerThread = new HandlerThread("test");
+ private final PowerStatsUidResolver mUidResolver = new PowerStatsUidResolver();
private Handler mHandler;
private PowerStats mCollectedStats;
private PowerProfile mPowerProfile = new PowerProfile();
@Mock
- private PowerStatsUidResolver mUidResolver;
- @Mock
private CpuPowerStatsCollector.KernelCpuStatsReader mMockKernelCpuStatsReader;
@Mock
private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
@@ -144,15 +143,8 @@
mHandlerThread.start();
mHandler = mHandlerThread.getThreadHandler();
when(mMockKernelCpuStatsReader.isSupportedFeature()).thenReturn(true);
- when(mUidResolver.mapUid(anyInt())).thenAnswer(invocation -> {
- int uid = invocation.getArgument(0);
- if (uid == ISOLATED_UID) {
- return UID_2;
- } else {
- return uid;
- }
- });
when(mConsumedEnergyRetriever.getEnergyConsumerIds(anyInt())).thenReturn(new int[0]);
+ mUidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID_2);
}
@Test
@@ -268,8 +260,7 @@
mockEnergyConsumers();
CpuPowerStatsCollector collector = createCollector(8, 0);
- CpuPowerStatsLayout layout =
- new CpuPowerStatsLayout();
+ CpuPowerStatsLayout layout = new CpuPowerStatsLayout();
layout.fromExtras(collector.getPowerStatsDescriptor().extras);
mockKernelCpuStats(new long[]{1111, 2222, 3333},
@@ -333,6 +324,45 @@
.isEqualTo(78);
}
+ @Test
+ public void isolatedUidReuse() {
+ mockCpuScalingPolicies(1);
+ mockPowerProfile();
+ mockEnergyConsumers();
+
+ CpuPowerStatsCollector collector = createCollector(8, 0);
+ CpuPowerStatsLayout layout = new CpuPowerStatsLayout();
+ layout.fromExtras(collector.getPowerStatsDescriptor().extras);
+
+ mockKernelCpuStats(new long[]{1111, 2222, 3333},
+ new SparseArray<>() {{
+ put(UID_2, new long[]{100, 150});
+ put(ISOLATED_UID, new long[]{10000, 20000});
+ }}, 0, 1234);
+
+ mMockClock.uptime = 1000;
+ collector.forceSchedule();
+ waitForIdle();
+
+ mUidResolver.noteIsolatedUidRemoved(ISOLATED_UID, UID_2);
+ mUidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID_2);
+
+ mockKernelCpuStats(new long[]{5555, 4444, 3333},
+ new SparseArray<>() {{
+ put(UID_2, new long[]{100, 150});
+ put(ISOLATED_UID, new long[]{245, 528});
+ }}, 1234, 3421);
+
+ mMockClock.uptime = 2000;
+ collector.forceSchedule();
+ waitForIdle();
+
+ assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 0))
+ .isEqualTo(245);
+ assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 1))
+ .isEqualTo(528);
+ }
+
private void mockCpuScalingPolicies(int clusterCount) {
SparseArray<int[]> cpus = new SparseArray<>();
SparseArray<int[]> freqs = new SparseArray<>();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
index df1200b..89d6c1c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
@@ -66,8 +66,7 @@
public void setup() {
mHandlerThread.start();
mHandler = mHandlerThread.getThreadHandler();
- mCollector = new PowerStatsCollector(mHandler,
- 60000,
+ mCollector = new PowerStatsCollector(mHandler, 60000, mock(PowerStatsUidResolver.class),
mMockClock) {
@Override
protected PowerStats collectStats() {
diff --git a/services/tests/selinux/Android.bp b/services/tests/selinux/Android.bp
index f387238..12a7038 100644
--- a/services/tests/selinux/Android.bp
+++ b/services/tests/selinux/Android.bp
@@ -52,6 +52,7 @@
"androidx.test.ext.junit",
"androidx.test.ext.truth",
"androidx.test.runner",
+ "compatibility-device-util-axt",
"services.core",
],
test_suites: [
diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
index b36c9bd..e86108d 100644
--- a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
+++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
@@ -15,98 +15,144 @@
*/
package com.android.server.selinux;
-import static com.android.server.selinux.SelinuxAuditLogBuilder.PATH_MATCHER;
-import static com.android.server.selinux.SelinuxAuditLogBuilder.SCONTEXT_MATCHER;
-import static com.android.server.selinux.SelinuxAuditLogBuilder.TCONTEXT_MATCHER;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.android.server.selinux.SelinuxAuditLogBuilder.toCategories;
import static com.google.common.truth.Truth.assertThat;
+import android.provider.DeviceConfig;
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.regex.Matcher;
+
@RunWith(AndroidJUnit4.class)
public class SelinuxAuditLogsBuilderTest {
- private final SelinuxAuditLogBuilder mAuditLogBuilder = new SelinuxAuditLogBuilder();
+ private static final String TEST_DOMAIN = "test_domain";
+
+ private SelinuxAuditLogBuilder mAuditLogBuilder;
+ private Matcher mScontextMatcher;
+ private Matcher mTcontextMatcher;
+ private Matcher mPathMatcher;
+
+ @Before
+ public void setUp() {
+ runWithShellPermissionIdentity(
+ () ->
+ DeviceConfig.setLocalOverride(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN,
+ TEST_DOMAIN));
+
+ mAuditLogBuilder = new SelinuxAuditLogBuilder();
+ mScontextMatcher = mAuditLogBuilder.mScontextMatcher;
+ mTcontextMatcher = mAuditLogBuilder.mTcontextMatcher;
+ mPathMatcher = mAuditLogBuilder.mPathMatcher;
+ }
+
+ @After
+ public void tearDown() {
+ runWithShellPermissionIdentity(() -> DeviceConfig.clearAllLocalOverrides());
+ }
@Test
public void testMatcher_scontext() {
- assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0").matches()).isTrue();
- assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit");
- assertThat(SCONTEXT_MATCHER.group("scategories")).isNull();
+ assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0").matches()).isTrue();
+ assertThat(mScontextMatcher.group("stype")).isEqualTo(TEST_DOMAIN);
+ assertThat(mScontextMatcher.group("scategories")).isNull();
- assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:c123,c456").matches()).isTrue();
- assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit");
- assertThat(toCategories(SCONTEXT_MATCHER.group("scategories")))
+ assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:c123,c456").matches())
+ .isTrue();
+ assertThat(mScontextMatcher.group("stype")).isEqualTo(TEST_DOMAIN);
+ assertThat(toCategories(mScontextMatcher.group("scategories")))
.isEqualTo(new int[] {123, 456});
- assertThat(SCONTEXT_MATCHER.reset("u:r:not_sdk_sandbox:s0").matches()).isFalse();
- assertThat(SCONTEXT_MATCHER.reset("u:object_r:sdk_sandbox_audit:s0").matches()).isFalse();
- assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:p123").matches()).isFalse();
+ assertThat(mScontextMatcher.reset("u:r:wrong_domain:s0").matches()).isFalse();
+ assertThat(mScontextMatcher.reset("u:object_r:" + TEST_DOMAIN + ":s0").matches()).isFalse();
+ assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:p123").matches()).isFalse();
}
@Test
public void testMatcher_tcontext() {
- assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type:s0").matches()).isTrue();
- assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type");
- assertThat(TCONTEXT_MATCHER.group("tcategories")).isNull();
+ assertThat(mTcontextMatcher.reset("u:object_r:target_type:s0").matches()).isTrue();
+ assertThat(mTcontextMatcher.group("ttype")).isEqualTo("target_type");
+ assertThat(mTcontextMatcher.group("tcategories")).isNull();
- assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type2:s0:c666").matches()).isTrue();
- assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type2");
- assertThat(toCategories(TCONTEXT_MATCHER.group("tcategories"))).isEqualTo(new int[] {666});
+ assertThat(mTcontextMatcher.reset("u:object_r:target_type2:s0:c666").matches()).isTrue();
+ assertThat(mTcontextMatcher.group("ttype")).isEqualTo("target_type2");
+ assertThat(toCategories(mTcontextMatcher.group("tcategories"))).isEqualTo(new int[] {666});
- assertThat(TCONTEXT_MATCHER.reset("u:r:target_type:s0").matches()).isFalse();
- assertThat(TCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:x456").matches()).isFalse();
+ assertThat(mTcontextMatcher.reset("u:r:target_type:s0").matches()).isFalse();
+ assertThat(mTcontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:x456").matches()).isFalse();
}
@Test
public void testMatcher_path() {
- assertThat(PATH_MATCHER.reset("\"/data\"").matches()).isTrue();
- assertThat(PATH_MATCHER.group("path")).isEqualTo("/data");
- assertThat(PATH_MATCHER.reset("\"/data/local\"").matches()).isTrue();
- assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local");
- assertThat(PATH_MATCHER.reset("\"/data/local/tmp\"").matches()).isTrue();
- assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local");
+ assertThat(mPathMatcher.reset("\"/data\"").matches()).isTrue();
+ assertThat(mPathMatcher.group("path")).isEqualTo("/data");
+ assertThat(mPathMatcher.reset("\"/data/local\"").matches()).isTrue();
+ assertThat(mPathMatcher.group("path")).isEqualTo("/data/local");
+ assertThat(mPathMatcher.reset("\"/data/local/tmp\"").matches()).isTrue();
+ assertThat(mPathMatcher.group("path")).isEqualTo("/data/local");
- assertThat(PATH_MATCHER.reset("\"/data/local").matches()).isFalse();
- assertThat(PATH_MATCHER.reset("\"_data_local\"").matches()).isFalse();
+ assertThat(mPathMatcher.reset("\"/data/local").matches()).isFalse();
+ assertThat(mPathMatcher.reset("\"_data_local\"").matches()).isFalse();
+ }
+
+ @Test
+ public void testMatcher_scontextDefaultConfig() {
+ runWithShellPermissionIdentity(
+ () ->
+ DeviceConfig.clearLocalOverride(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN));
+
+ Matcher scontexMatcher = new SelinuxAuditLogBuilder().mScontextMatcher;
+
+ assertThat(scontexMatcher.reset("u:r:" + TEST_DOMAIN + ":s0").matches()).isFalse();
+ assertThat(scontexMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:c123,c456").matches())
+ .isFalse();
+ assertThat(scontexMatcher.reset("u:r:wrong_domain:s0").matches()).isFalse();
}
@Test
public void testSelinuxAuditLogsBuilder_noOptionals() {
mAuditLogBuilder.reset(
- "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0"
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 tcontext=u:object_r:t:s0"
+ " tclass=c");
- assertAuditLog(
- mAuditLogBuilder.build(), true, new String[] {"p"}, "sdk_sandbox_audit", "t", "c");
+ assertAuditLog(mAuditLogBuilder.build(), true, new String[] {"p"}, TEST_DOMAIN, "t", "c");
mAuditLogBuilder.reset(
"tclass=c2 granted { p2 } tcontext=u:object_r:t2:s0"
- + " scontext=u:r:sdk_sandbox_audit:s0");
+ + " scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0");
assertAuditLog(
- mAuditLogBuilder.build(),
- true,
- new String[] {"p2"},
- "sdk_sandbox_audit",
- "t2",
- "c2");
+ mAuditLogBuilder.build(), true, new String[] {"p2"}, TEST_DOMAIN, "t2", "c2");
}
@Test
public void testSelinuxAuditLogsBuilder_withCategories() {
mAuditLogBuilder.reset(
- "granted { p } scontext=u:r:sdk_sandbox_audit:s0:c123"
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0:c123"
+ " tcontext=u:object_r:t:s0:c456,c666 tclass=c");
assertAuditLog(
mAuditLogBuilder.build(),
true,
new String[] {"p"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123},
"t",
new int[] {456, 666},
@@ -118,13 +164,15 @@
@Test
public void testSelinuxAuditLogsBuilder_withPath() {
mAuditLogBuilder.reset(
- "granted { p } scontext=u:r:sdk_sandbox_audit:s0 path=\"/very/long/path\""
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 path=\"/very/long/path\""
+ " tcontext=u:object_r:t:s0 tclass=c");
assertAuditLog(
mAuditLogBuilder.build(),
true,
new String[] {"p"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"t",
null,
@@ -136,13 +184,15 @@
@Test
public void testSelinuxAuditLogsBuilder_withPermissive() {
mAuditLogBuilder.reset(
- "granted { p } scontext=u:r:sdk_sandbox_audit:s0 permissive=0"
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 permissive=0"
+ " tcontext=u:object_r:t:s0 tclass=c");
assertAuditLog(
mAuditLogBuilder.build(),
true,
new String[] {"p"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"t",
null,
@@ -151,13 +201,15 @@
false);
mAuditLogBuilder.reset(
- "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0 tclass=c"
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 tcontext=u:object_r:t:s0 tclass=c"
+ " permissive=1");
assertAuditLog(
mAuditLogBuilder.build(),
true,
new String[] {"p"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"t",
null,
@@ -166,6 +218,40 @@
true);
}
+ @Test
+ public void testSelinuxAuditLogsBuilder_wrongConfig() {
+ String notARegexDomain = "not]a[regex";
+ runWithShellPermissionIdentity(
+ () ->
+ DeviceConfig.setLocalOverride(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN,
+ notARegexDomain));
+ SelinuxAuditLogBuilder noOpBuilder = new SelinuxAuditLogBuilder();
+
+ noOpBuilder.reset(
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 tcontext=u:object_r:t:s0 tclass=c");
+ assertThat(noOpBuilder.build()).isNull();
+ noOpBuilder.reset(
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0:c123 tcontext=u:object_r:t:s0:c456,c666 tclass=c");
+ assertThat(noOpBuilder.build()).isNull();
+ noOpBuilder.reset(
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 path=\"/very/long/path\""
+ + " tcontext=u:object_r:t:s0 tclass=c");
+ assertThat(noOpBuilder.build()).isNull();
+ noOpBuilder.reset(
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 permissive=0 tcontext=u:object_r:t:s0 tclass=c");
+ assertThat(noOpBuilder.build()).isNull();
+ }
+
private void assertAuditLog(
SelinuxAuditLog auditLog,
boolean granted,
diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
index 4a70ad3..b6ccf5e 100644
--- a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
+++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.selinux;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -27,6 +28,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
+import android.provider.DeviceConfig;
import android.util.EventLog;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -50,6 +52,7 @@
// Fake tag to use for testing
private static final int ANSWER_TAG = 42;
+ private static final String TEST_DOMAIN = "test_domain";
private final MockClock mClock = new MockClock();
@@ -64,6 +67,14 @@
@Before
public void setUp() {
+ runWithShellPermissionIdentity(
+ () ->
+ DeviceConfig.setLocalOverride(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN,
+ TEST_DOMAIN));
+
+ mSelinuxAutidLogsCollector.setStopRequested(false);
// move the clock forward for the limiters.
mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
// Ignore what was written in the event logs by previous tests.
@@ -74,13 +85,14 @@
@After
public void tearDown() {
+ runWithShellPermissionIdentity(() -> DeviceConfig.clearAllLocalOverrides());
mMockitoSession.finishMocking();
}
@Test
- public void testWriteSdkSandboxAuditLogs() {
- writeTestLog("granted", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm1", "sdk_sandbox_audit", "ttype1", "tclass1");
+ public void testWriteAuditLogs() {
+ writeTestLog("granted", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm1", TEST_DOMAIN, "ttype1", "tclass1");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -91,7 +103,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
true,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -104,7 +116,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm1"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype1",
null,
@@ -114,9 +126,9 @@
}
@Test
- public void testWriteSdkSandboxAuditLogs_multiplePerms() {
- writeTestLog("denied", "perm1 perm2", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm3 perm4", "sdk_sandbox_audit", "ttype", "tclass");
+ public void testWriteAuditLogs_multiplePerms() {
+ writeTestLog("denied", "perm1 perm2", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm3 perm4", TEST_DOMAIN, "ttype", "tclass");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -127,7 +139,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm1", "perm2"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -140,7 +152,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm3", "perm4"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -150,11 +162,11 @@
}
@Test
- public void testWriteSdkSandboxAuditLogs_withPaths() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/good/path");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/very/long/path");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/short_path");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "not_a_path");
+ public void testWriteAuditLogs_withPaths() {
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/good/path");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/very/long/path");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/short_path");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "not_a_path");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -165,7 +177,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -178,7 +190,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -191,7 +203,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -204,7 +216,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -214,23 +226,14 @@
}
@Test
- public void testWriteSdkSandboxAuditLogs_withCategories() {
- writeTestLog(
- "denied", "perm", "sdk_sandbox_audit", new int[] {123}, "ttype", null, "tclass");
+ public void testWriteAuditLogs_withCategories() {
+ writeTestLog("denied", "perm", TEST_DOMAIN, new int[] {123}, "ttype", null, "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, new int[] {123, 456}, "ttype", null, "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, null, "ttype", new int[] {666}, "tclass");
writeTestLog(
"denied",
"perm",
- "sdk_sandbox_audit",
- new int[] {123, 456},
- "ttype",
- null,
- "tclass");
- writeTestLog(
- "denied", "perm", "sdk_sandbox_audit", null, "ttype", new int[] {666}, "tclass");
- writeTestLog(
- "denied",
- "perm",
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123, 456},
"ttype",
new int[] {666, 777},
@@ -245,7 +248,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123},
"ttype",
null,
@@ -258,7 +261,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123, 456},
"ttype",
null,
@@ -271,7 +274,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
new int[] {666},
@@ -284,7 +287,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123, 456},
"ttype",
new int[] {666, 777},
@@ -294,11 +297,11 @@
}
@Test
- public void testWriteSdkSandboxAuditLogs_withPathAndCategories() {
+ public void testWriteAuditLogs_withPathAndCategories() {
writeTestLog(
"denied",
"perm",
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123},
"ttype",
new int[] {666},
@@ -314,7 +317,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123},
"ttype",
new int[] {666},
@@ -324,10 +327,10 @@
}
@Test
- public void testWriteSdkSandboxAuditLogs_permissive() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", true);
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", false);
+ public void testWriteAuditLogs_permissive() {
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", true);
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", false);
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -338,7 +341,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -352,7 +355,7 @@
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -362,7 +365,7 @@
}
@Test
- public void testNotWriteAuditLogs_notSdkSandbox() {
+ public void testNotWriteAuditLogs_notTestDomain() {
writeTestLog("denied", "perm", "stype", "ttype", "tclass");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -385,15 +388,15 @@
}
@Test
- public void testWriteSdkSandboxAuditLogs_upToQuota() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ public void testWriteAuditLogs_upToQuota() {
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
// These are not pushed.
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -415,14 +418,14 @@
}
@Test
- public void testWriteSdkSandboxAuditLogs_resetQuota() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ public void testWriteAuditLogs_resetQuota() {
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
assertThat(done).isTrue();
@@ -441,11 +444,11 @@
anyBoolean()),
times(5));
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
// move the clock forward to reset the quota limiter.
mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -468,16 +471,16 @@
@Test
public void testNotWriteAuditLogs_stopRequested() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
// These are not pushed.
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
- mSelinuxAutidLogsCollector.mStopRequested.set(true);
+ mSelinuxAutidLogsCollector.setStopRequested(true);
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
assertThat(done).isFalse();
verify(
@@ -495,7 +498,7 @@
anyBoolean()),
never());
- mSelinuxAutidLogsCollector.mStopRequested.set(false);
+ mSelinuxAutidLogsCollector.setStopRequested(false);
done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
assertThat(done).isTrue();
verify(
@@ -516,8 +519,8 @@
@Test
public void testAuditLogs_resumeJobDoesNotExceedLimit() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- mSelinuxAutidLogsCollector.mStopRequested.set(true);
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ mSelinuxAutidLogsCollector.setStopRequested(true);
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java
new file mode 100644
index 0000000..2aea8a0
--- /dev/null
+++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+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.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidJUnit4.class)
+public class SelinuxAuditLogsJobTest {
+
+ private final JobService mJobService = mock(JobService.class);
+ private final SelinuxAuditLogsCollector mAuditLogsCollector =
+ mock(SelinuxAuditLogsCollector.class);
+ private final JobParameters mParams = createJobParameters(666);
+ private final SelinuxAuditLogsJob mAuditLogsJob = new SelinuxAuditLogsJob(mAuditLogsCollector);
+
+ @Before
+ public void setUp() {
+ mAuditLogsCollector.mStopRequested = new AtomicBoolean();
+ }
+
+ @Test
+ public void testFinishSuccessfully() {
+ when(mAuditLogsCollector.collect(anyInt())).thenReturn(true);
+
+ mAuditLogsJob.start(mJobService, mParams);
+
+ verify(mJobService).jobFinished(mParams, /* wantsReschedule= */ false);
+ assertThat(mAuditLogsJob.isRunning()).isFalse();
+ }
+
+ @Test
+ public void testInterrupt() {
+ when(mAuditLogsCollector.collect(anyInt())).thenReturn(false);
+
+ mAuditLogsJob.start(mJobService, mParams);
+
+ verify(mJobService, never()).jobFinished(any(), anyBoolean());
+ assertThat(mAuditLogsJob.isRunning()).isFalse();
+ }
+
+ @Test
+ public void testInterruptAndResume() {
+ when(mAuditLogsCollector.collect(anyInt())).thenReturn(false);
+ mAuditLogsJob.start(mJobService, mParams);
+ verify(mJobService, never()).jobFinished(any(), anyBoolean());
+
+ when(mAuditLogsCollector.collect(anyInt())).thenReturn(true);
+ mAuditLogsJob.start(mJobService, mParams);
+ verify(mJobService).jobFinished(mParams, /* wantsReschedule= */ false);
+ assertThat(mAuditLogsJob.isRunning()).isFalse();
+ }
+
+ @Test
+ public void testRequestStop() throws InterruptedException {
+ Semaphore isRunning = new Semaphore(0);
+ Semaphore stopRequested = new Semaphore(0);
+ AtomicReference<Throwable> uncaughtException = new AtomicReference<>();
+
+ // Set up a logs collector that runs in a worker thread until a stop is requested.
+ when(mAuditLogsCollector.collect(anyInt()))
+ .thenAnswer(
+ invocation -> {
+ assertThat(mAuditLogsCollector.mStopRequested.get()).isFalse();
+ isRunning.release();
+ stopRequested.acquire();
+ assertThat(mAuditLogsCollector.mStopRequested.get()).isTrue();
+ return true;
+ });
+ Thread jobThread =
+ new Thread(
+ () -> {
+ mAuditLogsJob.start(mJobService, mParams);
+ });
+ jobThread.setUncaughtExceptionHandler(
+ (thread, exception) -> uncaughtException.set(exception));
+ assertThat(mAuditLogsJob.isRunning()).isFalse();
+ jobThread.start();
+
+ // Wait until the worker thread is running.
+ isRunning.acquire();
+ assertThat(mAuditLogsJob.isRunning()).isTrue();
+
+ // Request for the worker thread to stop, and wait to verify.
+ mAuditLogsJob.requestStop();
+ stopRequested.release();
+ jobThread.join();
+ assertThat(uncaughtException.get()).isNull();
+ assertThat(mAuditLogsJob.isRunning()).isFalse();
+ }
+
+ private static JobParameters createJobParameters(int jobId) {
+ JobParameters jobParameters = mock(JobParameters.class);
+ when(jobParameters.getJobId()).thenReturn(jobId);
+ return jobParameters;
+ }
+}
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 5a17851..cb4fc75 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -26,6 +26,7 @@
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.server.accessibility.AccessibilityManagerService.ACTION_LAUNCH_HEARING_DEVICES_DIALOG;
+import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
import static com.google.common.truth.Truth.assertThat;
@@ -68,7 +69,10 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.LocaleList;
+import android.os.PermissionEnforcer;
+import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.test.FakePermissionEnforcer;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -110,6 +114,7 @@
import com.android.server.wm.WindowManagerInternal;
import org.junit.After;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -197,31 +202,34 @@
private AccessibilityManagerService mA11yms;
private TestableLooper mTestableLooper;
private Handler mHandler;
+ private FakePermissionEnforcer mFakePermissionEnforcer;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
mHandler = new Handler(mTestableLooper.getLooper());
-
+ mFakePermissionEnforcer = new FakePermissionEnforcer();
LocalServices.removeServiceForTest(WindowManagerInternal.class);
LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+ LocalServices.removeServiceForTest(PermissionEnforcer.class);
LocalServices.addService(
WindowManagerInternal.class, mMockWindowManagerService);
LocalServices.addService(
ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal);
LocalServices.addService(
UserManagerInternal.class, mMockUserManagerInternal);
- LocalServices.addService(
- StatusBarManagerInternal.class, mStatusBarManagerInternal);
+ LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal);
mInputFilter = Mockito.mock(FakeInputFilter.class);
when(mMockMagnificationController.getMagnificationConnectionManager()).thenReturn(
mMockMagnificationConnectionManager);
when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn(
mMockFullScreenMagnificationController);
+ when(mMockMagnificationController.isFullScreenMagnificationControllerInitialized())
+ .thenReturn(true);
when(mMockMagnificationController.supportWindowMagnification()).thenReturn(true);
when(mMockWindowManagerService.getAccessibilityController()).thenReturn(
mMockA11yController);
@@ -248,7 +256,8 @@
mMockA11yDisplayListener,
mMockMagnificationController,
mInputFilter,
- mProxyManager);
+ mProxyManager,
+ mFakePermissionEnforcer);
final AccessibilityUserState userState = new AccessibilityUserState(
mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
@@ -305,9 +314,7 @@
@SmallTest
@Test
public void testRegisterSystemActionWithoutPermission() throws Exception {
- doThrow(SecurityException.class).when(mMockSecurityPolicy)
- .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
-
+ mFakePermissionEnforcer.revoke(Manifest.permission.MANAGE_ACCESSIBILITY);
assertThrows(SecurityException.class,
() -> mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID));
verify(mMockSystemActionPerformer, never()).registerSystemAction(ACTION_ID, TEST_ACTION);
@@ -316,15 +323,14 @@
@SmallTest
@Test
public void testRegisterSystemAction() throws Exception {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
verify(mMockSystemActionPerformer).registerSystemAction(ACTION_ID, TEST_ACTION);
}
@Test
public void testUnregisterSystemActionWithoutPermission() throws Exception {
- doThrow(SecurityException.class).when(mMockSecurityPolicy)
- .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
-
+ mFakePermissionEnforcer.revoke(Manifest.permission.MANAGE_ACCESSIBILITY);
assertThrows(SecurityException.class,
() -> mA11yms.unregisterSystemAction(ACTION_ID));
verify(mMockSystemActionPerformer, never()).unregisterSystemAction(ACTION_ID);
@@ -333,6 +339,7 @@
@SmallTest
@Test
public void testUnregisterSystemAction() throws Exception {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.unregisterSystemAction(ACTION_ID);
verify(mMockSystemActionPerformer).unregisterSystemAction(ACTION_ID);
}
@@ -353,6 +360,7 @@
@SmallTest
@Test
public void testRegisterProxy() throws Exception {
+ mFakePermissionEnforcer.grant(Manifest.permission.CREATE_VIRTUAL_DEVICE);
when(mProxyManager.displayBelongsToCaller(anyInt(), anyInt())).thenReturn(true);
mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
verify(mProxyManager).registerProxy(eq(mMockServiceClient), eq(TEST_DISPLAY), anyInt(),
@@ -364,6 +372,7 @@
@SmallTest
@Test
public void testRegisterProxyWithoutA11yPermissionOrRole() throws Exception {
+ mFakePermissionEnforcer.grant(Manifest.permission.CREATE_VIRTUAL_DEVICE);
doThrow(SecurityException.class).when(mMockSecurityPolicy)
.checkForAccessibilityPermissionOrRole();
@@ -376,9 +385,7 @@
@SmallTest
@Test
public void testRegisterProxyWithoutDevicePermission() throws Exception {
- doThrow(SecurityException.class).when(mMockSecurityPolicy)
- .enforceCallingOrSelfPermission(Manifest.permission.CREATE_VIRTUAL_DEVICE);
-
+ mFakePermissionEnforcer.revoke(Manifest.permission.CREATE_VIRTUAL_DEVICE);
assertThrows(SecurityException.class,
() -> mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY));
verify(mProxyManager, never()).registerProxy(any(), anyInt(), anyInt(), any(),
@@ -397,6 +404,7 @@
@SmallTest
@Test
public void testRegisterProxyForInvalidDisplay() throws Exception {
+ mFakePermissionEnforcer.grant(Manifest.permission.CREATE_VIRTUAL_DEVICE);
assertThrows(IllegalArgumentException.class,
() -> mA11yms.registerProxyForDisplay(mMockServiceClient, Display.INVALID_DISPLAY));
verify(mProxyManager, never()).registerProxy(any(), anyInt(), anyInt(), any(),
@@ -406,6 +414,7 @@
@SmallTest
@Test
public void testUnRegisterProxyWithPermission() throws Exception {
+ mFakePermissionEnforcer.grant(Manifest.permission.CREATE_VIRTUAL_DEVICE);
when(mProxyManager.displayBelongsToCaller(anyInt(), anyInt())).thenReturn(true);
mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
mA11yms.unregisterProxyForDisplay(TEST_DISPLAY);
@@ -427,9 +436,7 @@
@SmallTest
@Test
public void testUnRegisterProxyWithoutDevicePermission() {
- doThrow(SecurityException.class).when(mMockSecurityPolicy)
- .enforceCallingOrSelfPermission(Manifest.permission.CREATE_VIRTUAL_DEVICE);
-
+ mFakePermissionEnforcer.revoke(Manifest.permission.CREATE_VIRTUAL_DEVICE);
assertThrows(SecurityException.class,
() -> mA11yms.unregisterProxyForDisplay(TEST_DISPLAY));
verify(mProxyManager, never()).unregisterProxy(TEST_DISPLAY);
@@ -568,6 +575,17 @@
verify(mMockMagnificationController).setAlwaysOnMagnificationEnabled(eq(true));
}
+ @Test
+ @EnableFlags(FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
+ public void testSetConnectionNull_borderFlagEnabled_unregisterFullScreenMagnification()
+ throws RemoteException {
+ mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
+ mA11yms.setMagnificationConnection(null);
+
+ verify(mMockFullScreenMagnificationController, atLeastOnce()).reset(
+ /* displayId= */ anyInt(), /* animate= */ anyBoolean());
+ }
+
@SmallTest
@Test
public void testOnClientChange_magnificationEnabledAndCapabilityAll_requestConnection() {
@@ -774,7 +792,7 @@
public void testPerformAccessibilityShortcut_hearingAids_startActivityWithExpectedComponent() {
final AccessibilityUserState userState = mA11yms.mUserStates.get(
mA11yms.getCurrentUserIdLocked());
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
userState.mAccessibilityShortcutKeyTargets.add(
ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
@@ -792,7 +810,7 @@
public void testPerformAccessibilityShortcut_hearingAids_sendExpectedBroadcast() {
final AccessibilityUserState userState = mA11yms.mUserStates.get(
mA11yms.getCurrentUserIdLocked());
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
userState.mAccessibilityShortcutKeyTargets.add(
ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
@@ -906,7 +924,7 @@
@Test
public void testIsAccessibilityServiceWarningRequired_requiredByDefault() {
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
final AccessibilityServiceInfo info = mockAccessibilityServiceInfo(COMPONENT_NAME);
assertThat(mA11yms.isAccessibilityServiceWarningRequired(info)).isTrue();
@@ -914,7 +932,7 @@
@Test
public void testIsAccessibilityServiceWarningRequired_notRequiredIfAlreadyEnabled() {
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(COMPONENT_NAME);
final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo(
new ComponentName("package_b", "class_b"));
@@ -928,7 +946,7 @@
@Test
public void testIsAccessibilityServiceWarningRequired_notRequiredIfExistingShortcut() {
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(
new ComponentName("package_a", "class_a"));
final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo(
@@ -949,7 +967,7 @@
@Test
@EnableFlags(FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES)
public void testIsAccessibilityServiceWarningRequired_notRequiredIfAllowlisted() {
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(
new ComponentName("package_a", "class_a"),
/* isSystemApp= */ true, /* isAlwaysOnService= */ false);
@@ -989,7 +1007,10 @@
@Test
public void enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn()
throws Exception {
- mockManageAccessibilityGranted(mTestableContext);
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
@@ -1008,13 +1029,16 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HARDWARE_SHORTCUT_DISABLES_WARNING)
public void enableHardwareShortcutsForTargets_shortcutDialogSetting_isShown() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
Settings.Secure.putInt(
mTestableContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
AccessibilityShortcutController.DialogStatus.NOT_SHOWN
);
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
@@ -1035,6 +1059,9 @@
@Test
public void enableShortcutsForTargets_disableSoftwareShortcut_shortcutTurnedOff()
throws Exception {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn();
@@ -1052,7 +1079,10 @@
@Test
public void enableShortcutsForTargets_enableSoftwareShortcutWithMagnification_menuSizeIncreased() {
- mockManageAccessibilityGranted(mTestableContext);
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.enableShortcutsForTargets(
/* enable= */ true,
@@ -1071,7 +1101,7 @@
@Test
public void enableShortcutsForTargets_enableSoftwareShortcutWithMagnification_userConfigureSmallMenuSize_menuSizeNotChanged() {
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
Settings.Secure.putInt(
mTestableContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
@@ -1095,7 +1125,10 @@
@Test
public void enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService()
throws Exception {
- mockManageAccessibilityGranted(mTestableContext);
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
mA11yms.enableShortcutsForTargets(
@@ -1115,6 +1148,9 @@
@Test
public void enableShortcutsForTargets_disableAlwaysOnServiceSoftwareShortcut_turnsOffAlwaysOnService()
throws Exception {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService();
mA11yms.enableShortcutsForTargets(
@@ -1134,7 +1170,7 @@
@Test
public void enableShortcutsForTargets_enableStandardServiceSoftwareShortcut_wontTurnOnService()
throws Exception {
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
mA11yms.enableShortcutsForTargets(
@@ -1154,6 +1190,9 @@
@Test
public void enableShortcutsForTargets_disableStandardServiceSoftwareShortcutWithServiceOn_wontTurnOffService()
throws Exception {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableStandardServiceSoftwareShortcut_wontTurnOnService();
AccessibilityUtils.setAccessibilityServiceState(
mTestableContext, TARGET_STANDARD_A11Y_SERVICE, /* enabled= */ true);
@@ -1174,7 +1213,10 @@
@Test
public void enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated() {
- mockManageAccessibilityGranted(mTestableContext);
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.enableShortcutsForTargets(
/* enable= */ true,
@@ -1193,6 +1235,9 @@
@Test
public void enableShortcutsForTargets_disableTripleTapShortcut_settingUpdated() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated();
mA11yms.enableShortcutsForTargets(
@@ -1211,7 +1256,10 @@
@Test
public void enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated() {
- mockManageAccessibilityGranted(mTestableContext);
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
mA11yms.enableShortcutsForTargets(
/* enable= */ true,
@@ -1230,6 +1278,9 @@
@Test
public void enableShortcutsForTargets_disableMultiFingerMultiTapsShortcut_settingUpdated() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated();
mA11yms.enableShortcutsForTargets(
@@ -1249,7 +1300,10 @@
@Test
public void enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet() {
- mockManageAccessibilityGranted(mTestableContext);
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
mA11yms.enableShortcutsForTargets(
@@ -1268,6 +1322,9 @@
@Test
public void enableShortcutsForTargets_disableVolumeKeysShortcut_shortcutNotSet() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet();
mA11yms.enableShortcutsForTargets(
@@ -1278,15 +1335,19 @@
mTestableLooper.processAllMessages();
assertThat(
- ShortcutUtils.isComponentIdExistingInSettings(
- mTestableContext, ShortcutConstants.UserShortcutType.HARDWARE,
- TARGET_STANDARD_A11Y_SERVICE.flattenToString())
- ).isFalse();
+ ShortcutUtils.isComponentIdExistingInSettings(
+ mTestableContext,
+ ShortcutConstants.UserShortcutType.HARDWARE,
+ TARGET_STANDARD_A11Y_SERVICE.flattenToString()))
+ .isFalse();
}
@Test
public void enableShortcutsForTargets_enableQuickSettings_shortcutSet() {
- mockManageAccessibilityGranted(mTestableContext);
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
mA11yms.enableShortcutsForTargets(
@@ -1311,6 +1372,9 @@
@Test
public void enableShortcutsForTargets_disableQuickSettings_shortcutNotSet() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableQuickSettings_shortcutSet();
mA11yms.enableShortcutsForTargets(
@@ -1343,7 +1407,7 @@
@Test
public void getA11yFeatureToTileMap() {
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
Bundle bundle = mA11yms.getA11yFeatureToTileMap(mA11yms.getCurrentUserIdLocked());
@@ -1368,9 +1432,8 @@
@Test
@EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_statusBarServiceNotGranted_throwsException() {
- mTestableContext.getTestablePermissions().setPermission(
- Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.revoke(Manifest.permission.STATUS_BAR_SERVICE);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
assertThrows(SecurityException.class,
() -> mA11yms.notifyQuickSettingsTilesChanged(
@@ -1382,7 +1445,7 @@
@Test
@EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_manageAccessibilityNotGranted_throwsException() {
- mockStatusBarServiceGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
mTestableContext.getTestablePermissions().setPermission(
Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);
@@ -1396,8 +1459,8 @@
@Test
@EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel() {
- mockStatusBarServiceGranted(mTestableContext);
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
List<ComponentName> tiles = List.of(
AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
@@ -1433,8 +1496,8 @@
@Test
@EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_serviceWarningRequired_qsShortcutRemainDisabled() {
- mockStatusBarServiceGranted(mTestableContext);
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
ComponentName tile = new ComponentName(
TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
@@ -1451,8 +1514,8 @@
@Test
@EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_serviceWarningNotRequired_qsShortcutEnabled() {
- mockStatusBarServiceGranted(mTestableContext);
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
setupShortcutTargetServices();
final AccessibilityUserState userState = mA11yms.getCurrentUserState();
userState.mAccessibilityButtonTargets.clear();
@@ -1473,8 +1536,8 @@
@Test
@EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled() {
- mockStatusBarServiceGranted(mTestableContext);
- mockManageAccessibilityGranted(mTestableContext);
+ mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
List<ComponentName> tiles = List.of(
AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
@@ -1601,16 +1664,6 @@
return lockState;
}
- private void mockManageAccessibilityGranted(TestableContext context) {
- context.getTestablePermissions().setPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
- PackageManager.PERMISSION_GRANTED);
- }
-
- private void mockStatusBarServiceGranted(TestableContext context) {
- context.getTestablePermissions().setPermission(Manifest.permission.STATUS_BAR_SERVICE,
- PackageManager.PERMISSION_GRANTED);
- }
-
private void assertStartActivityWithExpectedComponentName(Context mockContext,
String componentName) {
verify(mockContext).startActivityAsUser(mIntentArgumentCaptor.capture(),
@@ -1695,4 +1748,8 @@
return mBroadcastReceivers;
}
}
+
+ private static boolean isSameCurrentUser(AccessibilityManagerService service, Context context) {
+ return service.getCurrentUserIdLocked() == context.getUserId();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index f3cd0d6..7b71f85 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -20,6 +20,7 @@
import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY;
+import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -139,6 +140,8 @@
private final TimeAnimator mMockTimeAnimator = mock(TimeAnimator.class);
+ private boolean mMockMagnificationConnectionState;
+
FullScreenMagnificationController mFullScreenMagnificationController;
public DisplayManagerInternal mDisplayManagerInternalMock = mock(DisplayManagerInternal.class);
@@ -175,6 +178,8 @@
mScaleProvider = new MagnificationScaleProvider(mMockContext);
+ // Assume the connection is established by default
+ mMockMagnificationConnectionState = true;
mFullScreenMagnificationController =
new FullScreenMagnificationController(
mMockControllerCtx,
@@ -184,7 +189,8 @@
() -> mMockThumbnail,
ConcurrentUtils.DIRECT_EXECUTOR,
() -> mMockScroller,
- () -> mMockTimeAnimator);
+ () -> mMockTimeAnimator,
+ () -> mMockMagnificationConnectionState);
}
@After
@@ -196,7 +202,6 @@
CURRENT_USER_ID);
}
-
@Test
public void testRegister_WindowManagerAndContextRegisterListeners() {
register(DISPLAY_0);
@@ -291,6 +296,21 @@
}
@Test
+ @RequiresFlagsEnabled(FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
+ public void testSetScale_noConnection_doNothing() {
+ register(TEST_DISPLAY);
+
+ // Assume that the connection does not exist.
+ mMockMagnificationConnectionState = false;
+
+ final float scale = 2.0f;
+ final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+ assertFalse(mFullScreenMagnificationController
+ .setScale(TEST_DISPLAY, scale, center.x, center.y, false, SERVICE_ID_1));
+ assertFalse(mFullScreenMagnificationController.isActivated(TEST_DISPLAY));
+ }
+
+ @Test
public void testSetScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState() {
for (int i = 0; i < DISPLAY_COUNT; i++) {
setScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState(i);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 7fbd521..f482ddc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -197,6 +197,8 @@
private final Scroller mMockScroller = spy(new Scroller(mContext));
+ private boolean mMockMagnificationConnectionState;
+
private OffsettableClock mClock;
private FullScreenMagnificationGestureHandler mMgh;
private TestHandler mHandler;
@@ -229,6 +231,7 @@
Settings.Secure.putFloatForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
UserHandle.USER_SYSTEM);
+ mMockMagnificationConnectionState = true;
mFullScreenMagnificationController =
new FullScreenMagnificationController(
mockController,
@@ -238,7 +241,8 @@
() -> null,
ConcurrentUtils.DIRECT_EXECUTOR,
() -> mMockScroller,
- TimeAnimator::new) {
+ TimeAnimator::new,
+ () -> mMockMagnificationConnectionState) {
@Override
public boolean magnificationRegionContains(int displayId, float x, float y) {
return true;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 58567ca..2528177 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -220,7 +220,8 @@
() -> null,
ConcurrentUtils.DIRECT_EXECUTOR,
() -> mMockScroller,
- () -> mTimeAnimator));
+ () -> mTimeAnimator,
+ () -> true));
mScreenMagnificationController.register(TEST_DISPLAY);
mMagnificationConnectionManager = spy(
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 7c0dbf4..c6f3eb3 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -34,6 +34,7 @@
import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG;
import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG;
import static com.android.server.am.UserController.REPORT_USER_SWITCH_MSG;
+import static com.android.server.am.UserController.SCHEDULED_STOP_BACKGROUND_USER_MSG;
import static com.android.server.am.UserController.USER_COMPLETED_EVENT_MSG;
import static com.android.server.am.UserController.USER_CURRENT_MSG;
import static com.android.server.am.UserController.USER_START_MSG;
@@ -323,7 +324,8 @@
@Test
public void testStartUserUIDisabled() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
verify(mInjector, never()).showUserSwitchingDialog(
@@ -393,7 +395,8 @@
@Test
public void testFailedStartUserInForeground() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mUserController.startUserInForeground(NONEXIST_USER_ID);
verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
@@ -470,7 +473,8 @@
@Test
public void testContinueUserSwitch() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -483,7 +487,7 @@
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
verify(mInjector, times(0)).dismissKeyguard(any());
verify(mInjector, times(1)).dismissUserSwitchingDialog(any());
- continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
+ continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false);
verifySystemUserVisibilityChangesNeverNotified();
}
@@ -491,7 +495,8 @@
public void testContinueUserSwitchDismissKeyguard() {
when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(false);
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -504,14 +509,15 @@
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
verify(mInjector, times(1)).dismissKeyguard(any());
verify(mInjector, times(1)).dismissUserSwitchingDialog(any());
- continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
+ continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false);
verifySystemUserVisibilityChangesNeverNotified();
}
@Test
public void testContinueUserSwitchUIDisabled() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
@@ -524,11 +530,11 @@
// Verify that continueUserSwitch worked as expected
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
verify(mInjector, never()).dismissUserSwitchingDialog(any());
- continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
+ continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false);
}
private void continueUserSwitchAssertions(int expectedOldUserId, int expectedNewUserId,
- boolean backgroundUserStopping) {
+ boolean backgroundUserStopping, boolean expectScheduleBackgroundUserStopping) {
Set<Integer> expectedCodes = new LinkedHashSet<>();
expectedCodes.add(COMPLETE_USER_SWITCH_MSG);
expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG);
@@ -536,6 +542,9 @@
expectedCodes.add(CLEAR_USER_JOURNEY_SESSION_MSG);
expectedCodes.add(0); // this is for directly posting in stopping.
}
+ if (expectScheduleBackgroundUserStopping) {
+ expectedCodes.add(SCHEDULED_STOP_BACKGROUND_USER_MSG);
+ }
Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes();
assertEquals("Unexpected message sent", expectedCodes, actualCodes);
Message msg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_COMPLETE_MSG);
@@ -571,6 +580,112 @@
).collect(Collectors.toList()), Collections.emptySet());
}
+ /** Test scheduling stopping of background users after a user-switch. */
+ @Test
+ public void testScheduleStopOfBackgroundUser_switch() {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER);
+
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ 2);
+
+ setUpUser(TEST_USER_ID1, NO_USERINFO_FLAGS);
+
+ // Switch to TEST_USER_ID from user 0
+ int numberOfUserSwitches = 0;
+ addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
+ ++numberOfUserSwitches,
+ /* expectOldUserStopping= */false,
+ /* expectScheduleBackgroundUserStopping= */ false);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID),
+ mUserController.getRunningUsersLU());
+
+ // Allow the post-switch processing to complete (there should be no scheduled stopping).
+ assertAndProcessScheduledStopBackgroundUser(false, null);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID),
+ mUserController.getRunningUsersLU());
+
+ // Switch to TEST_USER_ID1 from TEST_USER_ID
+ addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
+ ++numberOfUserSwitches,
+ /* expectOldUserStopping= */false,
+ /* expectScheduleBackgroundUserStopping= */ true);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_ID1),
+ mUserController.getRunningUsersLU());
+
+ // Switch back to TEST_USER_ID from TEST_USER_ID1
+ addForegroundUserAndContinueUserSwitch(TEST_USER_ID, TEST_USER_ID1,
+ ++numberOfUserSwitches,
+ /* expectOldUserStopping= */false,
+ /* expectScheduleBackgroundUserStopping= */ true);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID1, TEST_USER_ID),
+ mUserController.getRunningUsersLU());
+
+ // Allow the post-switch processing to complete.
+ assertAndProcessScheduledStopBackgroundUser(false, TEST_USER_ID);
+ assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID1);
+ assertAndProcessScheduledStopBackgroundUser(false, null);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID),
+ mUserController.getRunningUsersLU());
+ }
+
+ /** Test scheduling stopping of background users that were started in the background. */
+ @Test
+ public void testScheduleStopOfBackgroundUser_startInBackground() throws Exception {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER);
+
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ 2);
+
+ // Start two full background users (which should both get scheduled for stopping)
+ // and one profile (which should not).
+ setUpAndStartUserInBackground(TEST_USER_ID);
+ setUpAndStartUserInBackground(TEST_USER_ID1);
+ setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED);
+
+ assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_ID1, TEST_USER_ID2),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID);
+ assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID1, TEST_USER_ID2),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID1);
+ assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID2),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ assertAndProcessScheduledStopBackgroundUser(false, TEST_USER_ID2);
+ assertAndProcessScheduledStopBackgroundUser(false, null);
+
+ // Now that we've processed the stops, let's make sure that a subsequent one will work too.
+ setUpAndStartUserInBackground(TEST_USER_ID3);
+ assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID2, TEST_USER_ID3),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+ assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID3);
+ assertAndProcessScheduledStopBackgroundUser(false, null);
+ assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID2),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+ }
+
+ /**
+ * Process queued SCHEDULED_STOP_BACKGROUND_USER_MSG message, if expected.
+ * @param userId the user we are checking to see whether it is scheduled.
+ * Can be null, when expectScheduled is false, to indicate no user should be
+ * scheduled.
+ */
+ private void assertAndProcessScheduledStopBackgroundUser(
+ boolean expectScheduled, @Nullable Integer userId) {
+ TestHandler handler = mInjector.mHandler;
+ if (expectScheduled) {
+ assertTrue(handler.hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId));
+ handler.removeMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId);
+ mUserController.processScheduledStopOfBackgroundUser(userId);
+ } else {
+ assertFalse(handler.hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId));
+ }
+ }
+
@Test
public void testExplicitSystemUserStartInBackground() {
setUpUser(UserHandle.USER_SYSTEM, 0);
@@ -587,13 +702,14 @@
public void testUserLockingFromUserSwitchingForMultipleUsersNonDelayedLocking()
throws InterruptedException, RemoteException {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
setUpUser(TEST_USER_ID1, 0);
setUpUser(TEST_USER_ID2, 0);
int numberOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
// running: user 0, USER_ID
assertTrue(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}),
@@ -601,7 +717,7 @@
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
// running: user 0, USER_ID, USER_ID1
assertFalse(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID, TEST_USER_ID1}),
@@ -609,7 +725,7 @@
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
UserState ussUser2 = mUserStates.get(TEST_USER_ID2);
// skip middle step and call this directly.
mUserController.finishUserSwitch(ussUser2);
@@ -631,13 +747,14 @@
public void testUserLockingFromUserSwitchingForMultipleUsersDelayedLockingMode()
throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
setUpUser(TEST_USER_ID1, 0);
setUpUser(TEST_USER_ID2, 0);
int numberOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
// running: user 0, USER_ID
assertTrue(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}),
@@ -645,7 +762,7 @@
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
- numberOfUserSwitches, true);
+ numberOfUserSwitches, true, false);
// running: user 0, USER_ID1
// stopped + unlocked: USER_ID
numberOfUserSwitches++;
@@ -663,7 +780,7 @@
.lockCeStorage(anyInt());
addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1,
- numberOfUserSwitches, true);
+ numberOfUserSwitches, true, false);
// running: user 0, USER_ID2
// stopped + unlocked: USER_ID1
// stopped + locked: USER_ID
@@ -686,7 +803,8 @@
public void testStoppingExcessRunningUsersAfterSwitch_currentProfileNotStopped()
throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
final int PARENT_ID = 200;
final int PROFILE1_ID = 201;
@@ -707,7 +825,7 @@
int numberOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(PARENT_ID, UserHandle.USER_SYSTEM,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
mUserController.finishUserSwitch(mUserStates.get(PARENT_ID));
waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
assertTrue(mUserController.canStartMoreUsers());
@@ -722,7 +840,7 @@
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(FG_USER_ID, PARENT_ID,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
mUserController.finishUserSwitch(mUserStates.get(FG_USER_ID));
waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
assertTrue(mUserController.canStartMoreUsers());
@@ -747,7 +865,7 @@
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(PARENT_ID, FG_USER_ID,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
mUserController.finishUserSwitch(mUserStates.get(PARENT_ID));
waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
// We've now done a user switch and should notice that we've exceeded the maximum number of
@@ -766,7 +884,8 @@
@Test
public void testRunningUsersListOrder_parentAfterProfile() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
final int PARENT_ID = 200;
final int PROFILE1_ID = 201;
@@ -787,7 +906,7 @@
int numberOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(PARENT_ID, UserHandle.USER_SYSTEM,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
assertEquals(Arrays.asList(
new Integer[] {SYSTEM_USER_ID, PARENT_ID}),
mUserController.getRunningUsersLU());
@@ -799,7 +918,7 @@
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(FG_USER_ID, PARENT_ID,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
assertEquals(Arrays.asList(
new Integer[] {SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID, FG_USER_ID}),
mUserController.getRunningUsersLU());
@@ -827,7 +946,8 @@
@Test
public void testRunningUsersListOrder_currentAtEnd() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
final int CURRENT_ID = 200;
final int PROFILE_ID = 201;
@@ -842,7 +962,7 @@
new Integer[] {SYSTEM_USER_ID}),
mUserController.getRunningUsersLU());
- addForegroundUserAndContinueUserSwitch(CURRENT_ID, UserHandle.USER_SYSTEM, 1, false);
+ addForegroundUserAndContinueUserSwitch(CURRENT_ID, UserHandle.USER_SYSTEM, 1, false, false);
assertEquals(Arrays.asList(
new Integer[] {SYSTEM_USER_ID, CURRENT_ID}),
mUserController.getRunningUsersLU());
@@ -864,7 +984,8 @@
@Test
public void testUserLockingWithStopUserForNonDelayedLockingMode() throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
setUpAndStartUserInBackground(TEST_USER_ID);
assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* allowDelayedLocking= */ true,
@@ -922,7 +1043,8 @@
@Test
public void testUserLockingForDelayedLockingMode() throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
// allowDelayedLocking set and no KeyEvictedCallback, so it should not lock.
setUpAndStartUserInBackground(TEST_USER_ID);
@@ -973,7 +1095,8 @@
@Test
public void testStopProfile_doesNotStopItsParent() throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
final Range<Integer> RUNNING_RANGE =
Range.closed(UserState.STATE_BOOTING, UserState.STATE_RUNNING_UNLOCKED);
@@ -1053,7 +1176,8 @@
@Test
public void testStopPrivateProfile() throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
@@ -1071,7 +1195,8 @@
@Test
public void testStopPrivateProfileWithDelayedLocking() throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
@@ -1083,7 +1208,8 @@
@Test
public void testStopPrivateProfileWithDelayedLocking_flagDisabled() throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.disableFlags(
@@ -1113,7 +1239,8 @@
public void testStopPrivateProfileWithDelayedLocking_imperviousToNumberOfRunningUsers()
throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
- /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
@@ -1130,7 +1257,8 @@
@Test
public void testStopManagedProfileWithDelayedLocking() throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
@@ -1285,7 +1413,8 @@
public void testStallUserSwitchUntilTheKeyguardIsShown() throws Exception {
// enable user switch ui, because keyguard is only shown then
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
// mock the device to be secure in order to expect the keyguard to be shown
when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
@@ -1365,7 +1494,8 @@
}
private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId,
- int expectedNumberOfCalls, boolean expectOldUserStopping) {
+ int expectedNumberOfCalls, boolean expectOldUserStopping,
+ boolean expectScheduleBackgroundUserStopping) {
// Start user -- this will update state of mUserController
mUserController.startUser(newUserId, USER_START_MODE_FOREGROUND);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -1378,8 +1508,12 @@
mInjector.mHandler.clearAllRecordedMessages();
// Verify that continueUserSwitch worked as expected
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
+ assertEquals(mInjector.mHandler
+ .hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, expectedOldUserId),
+ expectScheduleBackgroundUserStopping);
verify(mInjector, times(expectedNumberOfCalls)).dismissUserSwitchingDialog(any());
- continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping);
+ continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping,
+ expectScheduleBackgroundUserStopping);
}
private UserInfo setUpUser(@UserIdInt int userId, @UserInfoFlag int flags) {
diff --git a/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java
index 472a82c..d5638e9 100644
--- a/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java
@@ -57,8 +57,9 @@
private ResolveInfo mResolveInfo1 = new ResolveInfo();
private ResolveInfo mResolveInfo2 = new ResolveInfo();
- private final String mTestPkg1 = "testPkg1", mTestPkg2 = "testPkg2", mTestPkg3 = "testPkg3";
- private final String mMusicFxPkgName = "com.android.musicfx";
+ private final String mTestPkg1 = new String("testPkg1"), mTestPkg2 = new String("testPkg2"),
+ mTestPkg3 = new String("testPkg3"), mTestPkg1Equivalent = new String("testPkg1");
+ private final String mMusicFxPkgName = new String("com.android.musicfx");
private final int mTestUid1 = 1, mTestUid2 = 2, mTestUid3 = 3, mMusicFxUid = 78;
private final int mTestSession1 = 11, mTestSession2 = 22, mTestSession3 = 33;
@@ -191,7 +192,8 @@
public void testCloseBroadcastIntent() {
Log.i(TAG, "running testCloseBroadcastIntent");
- closeSessionWithResList(null, 0, 0, null, mTestSession1, mTestUid1);
+ closeSessionWithResList(null, 0 /* unbind */, 0 /* broadcast */, null /* packageName */,
+ mTestSession1, mTestUid1);
}
/**
@@ -225,8 +227,10 @@
public void testBroadcastIntentWithNoPackageAndNoBroadcastReceiver() {
Log.i(TAG, "running testBroadcastIntentWithNoPackageAndNoBroadcastReceiver");
- openSessionWithResList(mEmptyList, 0, 0, null, mTestSession1, mTestUid1);
- closeSessionWithResList(mEmptyList, 0, 0, null, mTestSession1, mTestUid1);
+ openSessionWithResList(mEmptyList, 0 /* bind */, 0 /* broadcast */, null /* packageName */,
+ mTestSession1, mTestUid1);
+ closeSessionWithResList(mEmptyList, 0 /* unbind */, 0 /* broadcast */,
+ null /* packageName */, mTestSession1, mTestUid1);
}
/**
@@ -236,26 +240,10 @@
public void testBroadcastIntentWithNoPackageAndOneBroadcastReceiver() {
Log.i(TAG, "running testBroadcastIntentWithNoPackageAndOneBroadcastReceiver");
- int broadcasts = 1, bind = 1, unbind = 1;
- openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession1, mTestUid1);
- broadcasts = broadcasts + 1;
- closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession1, mTestUid1);
-
- // repeat with different session ID
- broadcasts = broadcasts + 1;
- bind = bind + 1;
- unbind = unbind + 1;
- openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession2, mTestUid1);
- broadcasts = broadcasts + 1;
- closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession2, mTestUid1);
-
- // repeat with different UID
- broadcasts = broadcasts + 1;
- bind = bind + 1;
- unbind = unbind + 1;
- openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession1, mTestUid2);
- broadcasts = broadcasts + 1;
- closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession1, mTestUid2);
+ openSessionWithResList(mSingleList, 0 /* bind */, 0 /* broadcast */,
+ null /* packageName */, mTestSession1, mTestUid1);
+ closeSessionWithResList(mSingleList, 0 /* unbind */, 0 /* broadcast */,
+ null /* packageName */, mTestSession1, mTestUid1);
}
/**
@@ -265,8 +253,50 @@
public void testBroadcastIntentWithNoPackageAndTwoBroadcastReceivers() {
Log.i(TAG, "running testBroadcastIntentWithNoPackageAndTwoBroadcastReceivers");
- openSessionWithResList(mDoubleList, 1, 1, null, mTestSession1, mTestUid1);
- closeSessionWithResList(mDoubleList, 1, 2, null, mTestSession1, mTestUid1);
+ openSessionWithResList(mDoubleList, 0 /* bind */, 0 /* broadcast */,
+ null /* packageName */, mTestSession1, mTestUid1);
+ closeSessionWithResList(mDoubleList, 0 /* bind */, 0 /* broadcast */,
+ null /* packageName */, mTestSession1, mTestUid1);
+ }
+
+ @Test
+ public void testBroadcastIntentWithPackageAndOneBroadcastReceiver() {
+ Log.i(TAG, "running testBroadcastIntentWithPackageAndOneBroadcastReceiver");
+
+ int broadcasts = 1, bind = 1, unbind = 1;
+ openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1);
+
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg1, mTestSession1,
+ mTestUid1);
+
+ // repeat with different session ID
+ broadcasts = broadcasts + 1;
+ bind = bind + 1;
+ unbind = unbind + 1;
+ openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid1);
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg2, mTestSession2,
+ mTestUid1);
+
+ // repeat with different UID
+ broadcasts = broadcasts + 1;
+ bind = bind + 1;
+ unbind = unbind + 1;
+ openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg3, mTestSession1, mTestUid2);
+ broadcasts = broadcasts + 1;
+ closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg3, mTestSession1,
+ mTestUid2);
+ }
+
+ @Test
+ public void testBroadcastIntentWithPackageAndTwoBroadcastReceivers() {
+ Log.i(TAG, "running testBroadcastIntentWithPackageAndTwoBroadcastReceivers");
+
+ openSessionWithResList(mDoubleList, 1 /* bind */, 1 /* broadcast */,
+ mTestPkg1 /* packageName */, mTestSession1, mTestUid1);
+ closeSessionWithResList(mDoubleList, 1 /* unbind */, 2 /* broadcast */,
+ mTestPkg1 /* packageName */, mTestSession1, mTestUid1);
}
/**
@@ -639,4 +669,18 @@
unbind = unbind + 1;
sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid3, unbind, broadcasts);
}
+
+ /**
+ * Test audio session open/close with same package name value but different String object.
+ */
+ @Test
+ public void testSessionOpenCloseWithSamePackageNameValueButDiffObject() {
+ Log.i(TAG, "running testSessionOpenCloseWithSamePackageNameValueButDiffObject");
+ int broadcasts = 1;
+ openSessionWithResList(mSingleList, 1 /* bind */, broadcasts, mTestPkg1, mTestSession1,
+ mTestUid1);
+ closeSessionWithResList(mSingleList, 1 /* unbind */, broadcasts + 1, mTestPkg1Equivalent,
+ mTestSession1, mTestUid1);
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 7e04277..90b131a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -16,10 +16,10 @@
package com.android.server.biometrics;
-import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_SUCCESS;
@@ -65,8 +65,6 @@
import android.os.UserHandle;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
-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.platform.test.flag.junit.SetFlagsRule;
@@ -204,43 +202,7 @@
}
@Test
- @RequiresFlagsDisabled(com.android.server.biometrics.Flags.FLAG_DE_HIDL)
- public void testRegisterAuthenticator_registerAuthenticators() throws Exception {
- final int fingerprintId = 0;
- final int fingerprintStrength = 15;
-
- final int faceId = 1;
- final int faceStrength = 15;
-
- final String[] config = {
- // ID0:Fingerprint:Strong
- String.format("%d:2:%d", fingerprintId, fingerprintStrength),
- // ID2:Face:Convenience
- String.format("%d:8:%d", faceId, faceStrength)
- };
-
- when(mInjector.getConfiguration(any())).thenReturn(config);
-
- mAuthService = new AuthService(mContext, mInjector);
- mAuthService.onStart();
-
- verify(mFingerprintService).registerAuthenticators(mFingerprintPropsCaptor.capture());
- final FingerprintSensorPropertiesInternal fingerprintProp =
- mFingerprintPropsCaptor.getValue().get(0);
- assertEquals(fingerprintProp.sensorId, fingerprintId);
- assertEquals(fingerprintProp.sensorStrength,
- Utils.authenticatorStrengthToPropertyStrength(fingerprintStrength));
-
- verify(mFaceService).registerAuthenticators(mFacePropsCaptor.capture());
- final FaceSensorPropertiesInternal faceProp = mFacePropsCaptor.getValue().get(0);
- assertEquals(faceProp.sensorId, faceId);
- assertEquals(faceProp.sensorStrength,
- Utils.authenticatorStrengthToPropertyStrength(faceStrength));
- }
-
- @Test
- @RequiresFlagsEnabled(com.android.server.biometrics.Flags.FLAG_DE_HIDL)
- public void testRegisterAuthenticator_registerAuthenticatorsLegacy() throws RemoteException {
+ public void testRegisterAuthenticator_registerAuthenticators() throws RemoteException {
final int fingerprintId = 0;
final int fingerprintStrength = 15;
@@ -265,7 +227,7 @@
mFingerprintLooper.dispatchAll();
mFaceLooper.dispatchAll();
- verify(mFingerprintService).registerAuthenticatorsLegacy(
+ verify(mFingerprintService).registerAuthenticators(
mFingerprintSensorConfigurationsCaptor.capture());
final SensorProps[] fingerprintProp = mFingerprintSensorConfigurationsCaptor.getValue()
@@ -275,8 +237,7 @@
assertEquals(fingerprintProp[0].commonProps.sensorStrength,
Utils.authenticatorStrengthToPropertyStrength(fingerprintStrength));
- verify(mFaceService).registerAuthenticatorsLegacy(
- mFaceSensorConfigurationsCaptor.capture());
+ verify(mFaceService).registerAuthenticators(mFaceSensorConfigurationsCaptor.capture());
final android.hardware.biometrics.face.SensorProps[] faceProp =
mFaceSensorConfigurationsCaptor.getValue()
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 5fd29c2..503ab8e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -76,7 +76,6 @@
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
@@ -90,7 +89,6 @@
import android.view.DisplayInfo;
import android.view.WindowManager;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
@@ -246,14 +244,8 @@
when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService);
when(mInjector.getNotificationLogger()).thenReturn(mNotificationLogger);
when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L);
-
- if (com.android.server.biometrics.Flags.deHidl()) {
- when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
- new Handler(TestableLooper.get(this).getLooper()));
- } else {
- when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
- new Handler(Looper.getMainLooper()));
- }
+ when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+ new Handler(TestableLooper.get(this).getLooper()));
final String[] config = {
"0:2:15", // ID0:Fingerprint:Strong
@@ -2037,11 +2029,7 @@
}
private void waitForIdle() {
- if (com.android.server.biometrics.Flags.deHidl()) {
- TestableLooper.get(this).processAllMessages();
- } else {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- }
+ TestableLooper.get(this).processAllMessages();
}
private byte[] generateRandomHAT() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
index c7300bb..960357f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -87,8 +87,12 @@
private ISessionListener mSessionListener;
@Mock
private WindowManager mWindowManager;
+ @Mock
+ private Consumer<OperationContext> mStartHalConsumer;
- private OperationContextExt mOpContext = new OperationContextExt(true);
+ private final FingerprintAuthenticateOptions mAuthenticateOptions =
+ new FingerprintAuthenticateOptions.Builder().build();
+ private final OperationContextExt mOpContext = new OperationContextExt(true);
private IBiometricContextListener mListener;
private BiometricContextProvider mProvider;
@@ -200,11 +204,11 @@
final List<Integer> actual = new ArrayList<>();
final List<Integer> expected = List.of(FoldState.FULLY_CLOSED, FoldState.FULLY_OPENED,
FoldState.UNKNOWN, FoldState.HALF_OPENED);
- mProvider.subscribe(mOpContext, ctx -> {
+ mProvider.subscribe(mOpContext, mStartHalConsumer, ctx -> {
assertThat(ctx).isSameInstanceAs(mOpContext.toAidlContext());
assertThat(mProvider.getFoldState()).isEqualTo(ctx.foldState);
actual.add(ctx.foldState);
- });
+ }, mAuthenticateOptions);
for (int v : expected) {
mListener.onFoldChanged(v);
@@ -228,11 +232,11 @@
AuthenticateOptions.DISPLAY_STATE_NO_UI,
AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN);
- mProvider.subscribe(mOpContext, ctx -> {
+ mProvider.subscribe(mOpContext, mStartHalConsumer, ctx -> {
assertThat(ctx).isSameInstanceAs(mOpContext.toAidlContext());
assertThat(mProvider.getDisplayState()).isEqualTo(ctx.displayState);
actual.add(ctx.displayState);
- });
+ }, mAuthenticateOptions);
for (int v : expected) {
mListener.onDisplayStateChanged(v);
@@ -250,11 +254,11 @@
public void testSubscribesToAod() throws RemoteException {
final List<Boolean> actual = new ArrayList<>();
- mProvider.subscribe(mOpContext, ctx -> {
+ mProvider.subscribe(mOpContext, mStartHalConsumer, ctx -> {
assertThat(ctx).isSameInstanceAs(mOpContext.toAidlContext());
assertThat(mProvider.isAod()).isEqualTo(ctx.isAod);
actual.add(ctx.isAod);
- });
+ }, mAuthenticateOptions);
for (int v : List.of(
AuthenticateOptions.DISPLAY_STATE_AOD,
@@ -273,10 +277,10 @@
public void testSubscribesToAwake() throws RemoteException {
final List<Boolean> actual = new ArrayList<>();
- mProvider.subscribe(mOpContext, ctx -> {
+ mProvider.subscribe(mOpContext, mStartHalConsumer, ctx -> {
assertThat(ctx).isSameInstanceAs(mOpContext.toAidlContext());
actual.add(mProvider.isAwake());
- });
+ }, mAuthenticateOptions);
for (int v : List.of(
AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN,
@@ -295,14 +299,15 @@
public void testSubscribesWithDifferentState() throws RemoteException {
final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class);
mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_AOD);
- mProvider.subscribe(mOpContext, nonEmptyConsumer);
- verify(nonEmptyConsumer).accept(same(mOpContext.toAidlContext()));
+ mProvider.subscribe(mOpContext, mStartHalConsumer, nonEmptyConsumer, mAuthenticateOptions);
+
+ assertThat(mOpContext.getDisplayState()).isEqualTo(AuthenticateOptions.DISPLAY_STATE_AOD);
}
@Test
public void testUnsubscribes() throws RemoteException {
final Consumer<OperationContext> emptyConsumer = mock(Consumer.class);
- mProvider.subscribe(mOpContext, emptyConsumer);
+ mProvider.subscribe(mOpContext, mStartHalConsumer, emptyConsumer, mAuthenticateOptions);
mProvider.unsubscribe(mOpContext);
mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_AOD);
@@ -311,7 +316,7 @@
mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_UNKNOWN);
final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class);
- mProvider.subscribe(mOpContext, nonEmptyConsumer);
+ mProvider.subscribe(mOpContext, mStartHalConsumer, nonEmptyConsumer, mAuthenticateOptions);
mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN);
mProvider.unsubscribe(mOpContext);
mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_NO_UI);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 971323a..fc573d2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -52,13 +52,12 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
+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.AndroidTestingRunner;
import android.testing.TestableContext;
-import android.testing.TestableLooper;
import android.util.Slog;
import androidx.annotation.NonNull;
@@ -66,7 +65,6 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-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.nano.BiometricSchedulerProto;
@@ -79,7 +77,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.HashMap;
@@ -90,7 +89,6 @@
@Presubmit
@SmallTest
@RunWith(AndroidTestingRunner.class)
[email protected](setAsMainLooper = true)
public class BiometricSchedulerTest {
private static final String TAG = "BiometricSchedulerTest";
@@ -105,6 +103,9 @@
@Rule
public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
private BiometricScheduler<IFingerprint, ISession> mScheduler;
private IBinder mToken;
private int mCurrentUserId = UserHandle.USER_SYSTEM;
@@ -121,8 +122,10 @@
mUsersStoppedCount++;
mCurrentUserId = UserHandle.USER_NULL;
};
+ private TestLooper mLooper;
private boolean mStartOperationsFinish = true;
private int mStartUserClientCount = 0;
+
@Mock
private IBiometricService mBiometricService;
@Mock
@@ -140,44 +143,39 @@
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
mToken = new Binder();
+ mLooper = new TestLooper();
+
when(mAuthSessionCoordinator.getLockoutStateFor(anyInt(), anyInt())).thenReturn(
BIOMETRIC_SUCCESS);
when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
- if (Flags.deHidl()) {
- mScheduler = new BiometricScheduler<>(
- new Handler(TestableLooper.get(this).getLooper()),
- BiometricScheduler.SENSOR_TYPE_UNKNOWN,
- null /* gestureAvailabilityDispatcher */,
- mBiometricService,
- LOG_NUM_RECENT_OPERATIONS,
- () -> mCurrentUserId,
- new UserSwitchProvider<IFingerprint, ISession>() {
- @NonNull
- @Override
- public StopUserClient<ISession> getStopUserClient(int userId) {
- return new TestStopUserClient(mContext, () -> mSession, mToken, userId,
- TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
- mUserStoppedCallback, () -> mShouldFailStopUser);
- }
- @NonNull
- @Override
- public StartUserClient<IFingerprint, ISession> getStartUserClient(
- int newUserId) {
- mStartUserClientCount++;
- return new TestStartUserClient(mContext, () -> mFingerprint, mToken,
- newUserId, TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
- mUserStartedCallback, mStartOperationsFinish);
- }
- });
- } else {
- mScheduler = new BiometricScheduler<>(
- new Handler(TestableLooper.get(this).getLooper()),
- BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */,
- mBiometricService, LOG_NUM_RECENT_OPERATIONS);
- }
+ mScheduler = new BiometricScheduler<>(
+ new Handler(mLooper.getLooper()),
+ BiometricScheduler.SENSOR_TYPE_UNKNOWN,
+ null /* gestureAvailabilityDispatcher */,
+ mBiometricService,
+ LOG_NUM_RECENT_OPERATIONS,
+ () -> mCurrentUserId,
+ new UserSwitchProvider<IFingerprint, ISession>() {
+ @NonNull
+ @Override
+ public StopUserClient<ISession> getStopUserClient(int userId) {
+ return new TestStopUserClient(mContext, () -> mSession, mToken, userId,
+ TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
+ mUserStoppedCallback, () -> mShouldFailStopUser);
+ }
+
+ @NonNull
+ @Override
+ public StartUserClient<IFingerprint, ISession> getStartUserClient(
+ int newUserId) {
+ mStartUserClientCount++;
+ return new TestStartUserClient(mContext, () -> mFingerprint, mToken,
+ newUserId, TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
+ mUserStartedCallback, mStartOperationsFinish);
+ }
+ });
}
@Test
@@ -657,7 +655,6 @@
@Test
public void testClearBiometricQueue_clearsHungAuthOperation() {
// Creating a hung client
- final TestableLooper looper = TestableLooper.get(this);
final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
@@ -676,9 +673,9 @@
assertNotNull(mScheduler.mCurrentOperation);
assertEquals(0, mScheduler.getCurrentPendingCount());
- looper.moveTimeForward(10000);
+ mLooper.moveTimeForward(10000);
waitForIdle();
- looper.moveTimeForward(3000);
+ mLooper.moveTimeForward(3000);
waitForIdle();
// The hung client did not honor this operation, verify onError and authenticated
@@ -693,7 +690,6 @@
@Test
public void testAuthWorks_afterClearBiometricQueue() {
// Creating a hung client
- final TestableLooper looper = TestableLooper.get(this);
final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
@@ -714,10 +710,10 @@
waitForIdle();
// The watchdog should kick off the cancellation
- looper.moveTimeForward(10000);
+ mLooper.moveTimeForward(10000);
waitForIdle();
// After 10 seconds the HAL has 3 seconds to respond to a cancel
- looper.moveTimeForward(3000);
+ mLooper.moveTimeForward(3000);
waitForIdle();
// The hung client did not honor this operation, verify onError and authenticated
@@ -752,10 +748,10 @@
client2.getCallback().onClientFinished(client2, true);
waitForIdle();
- looper.moveTimeForward(10000);
+ mLooper.moveTimeForward(10000);
waitForIdle();
// After 10 seconds the HAL has 3 seconds to respond to a cancel
- looper.moveTimeForward(3000);
+ mLooper.moveTimeForward(3000);
waitForIdle();
//Asserting auth client passes
@@ -766,7 +762,6 @@
@Test
public void testClearBiometricQueue_doesNotClearOperationsWhenQueueNotStuck() {
//Creating clients
- final TestableLooper looper = TestableLooper.get(this);
final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
@@ -793,10 +788,10 @@
waitForIdle();
// The watchdog should kick off the cancellation
- looper.moveTimeForward(10000);
+ mLooper.moveTimeForward(10000);
waitForIdle();
// After 10 seconds the HAL has 3 seconds to respond to a cancel
- looper.moveTimeForward(3000);
+ mLooper.moveTimeForward(3000);
waitForIdle();
//Watchdog does not clear pending operations
@@ -855,7 +850,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testScheduleOperation_whenNoUser() {
mCurrentUserId = UserHandle.USER_NULL;
@@ -871,7 +865,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testScheduleOperation_whenNoUser_notStarted() {
mCurrentUserId = UserHandle.USER_NULL;
mStartOperationsFinish = false;
@@ -896,7 +889,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testScheduleOperation_whenNoUser_notStarted_andReset() {
mCurrentUserId = UserHandle.USER_NULL;
mStartOperationsFinish = false;
@@ -923,7 +915,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testScheduleOperation_whenSameUser() {
mCurrentUserId = 10;
@@ -940,7 +931,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testScheduleOperation_whenDifferentUser() {
mCurrentUserId = 10;
@@ -961,7 +951,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testStartUser_alwaysStartsNextOperation() {
mCurrentUserId = UserHandle.USER_NULL;
@@ -990,7 +979,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testStartUser_failsClearsStopUserClient() {
mCurrentUserId = UserHandle.USER_NULL;
@@ -1033,7 +1021,7 @@
}
private void waitForIdle() {
- TestableLooper.get(this).processAllMessages();
+ mLooper.dispatchAll();
}
private static class TestAuthenticateOptions implements AuthenticateOptions {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
deleted file mode 100644
index dd5d826..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
+++ /dev/null
@@ -1,393 +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.biometrics.sensors;
-
-import static android.testing.TestableLooper.RunWithLooper;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-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.IBiometricService;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.UserHandle;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.filters.SmallTest;
-
-import com.android.server.biometrics.Flags;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-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.function.Supplier;
-
-@Presubmit
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class UserAwareBiometricSchedulerTest {
-
- private static final String TAG = "UserAwareBiometricSchedulerTest";
- private static final int TEST_SENSOR_ID = 0;
-
- @Rule
- public final MockitoRule mockito = MockitoJUnit.rule();
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
- private Handler mHandler;
- private UserAwareBiometricScheduler mScheduler;
- private final IBinder mToken = new Binder();
-
- @Mock
- private Context mContext;
- @Mock
- private IBiometricService mBiometricService;
- @Mock
- private BiometricLogger mBiometricLogger;
- @Mock
- private BiometricContext mBiometricContext;
-
- private boolean mShouldFailStopUser = false;
- private final StopUserClientShouldFail mStopUserClientShouldFail =
- () -> {
- return mShouldFailStopUser;
- };
- private final TestUserStartedCallback mUserStartedCallback = new TestUserStartedCallback();
- private final TestUserStoppedCallback mUserStoppedCallback = new TestUserStoppedCallback();
- private int mCurrentUserId = UserHandle.USER_NULL;
- private boolean mStartOperationsFinish = true;
- private int mStartUserClientCount = 0;
-
- @Before
- public void setUp() {
- mShouldFailStopUser = false;
- mHandler = new Handler(TestableLooper.get(this).getLooper());
- mScheduler = new UserAwareBiometricScheduler(TAG,
- mHandler,
- BiometricScheduler.SENSOR_TYPE_UNKNOWN,
- null /* gestureAvailabilityDispatcher */,
- mBiometricService,
- () -> mCurrentUserId,
- new UserAwareBiometricScheduler.UserSwitchCallback() {
- @NonNull
- @Override
- public StopUserClient<?> getStopUserClient(int userId) {
- return new TestStopUserClient(mContext, Object::new, mToken, userId,
- TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
- mUserStoppedCallback, mStopUserClientShouldFail);
- }
-
- @NonNull
- @Override
- public StartUserClient<?, ?> getStartUserClient(int newUserId) {
- mStartUserClientCount++;
- return new TestStartUserClient(mContext, Object::new, mToken, newUserId,
- TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
- mUserStartedCallback, mStartOperationsFinish);
- }
- });
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void testScheduleOperation_whenNoUser() {
- mCurrentUserId = UserHandle.USER_NULL;
-
- final BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
- when(nextClient.getTargetUserId()).thenReturn(0);
-
- mScheduler.scheduleClientMonitor(nextClient);
- waitForIdle();
-
- assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(0);
- assertThat(mUserStartedCallback.mStartedUsers).containsExactly(0);
- verify(nextClient).start(any());
- }
-
- @Test
- public void testScheduleOperation_whenNoUser_notStarted() {
- mCurrentUserId = UserHandle.USER_NULL;
- mStartOperationsFinish = false;
-
- final BaseClientMonitor[] nextClients = new BaseClientMonitor[]{
- mock(BaseClientMonitor.class),
- mock(BaseClientMonitor.class),
- mock(BaseClientMonitor.class)
- };
- for (BaseClientMonitor client : nextClients) {
- when(client.getTargetUserId()).thenReturn(5);
- mScheduler.scheduleClientMonitor(client);
- waitForIdle();
- }
-
- assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(0);
- assertThat(mUserStartedCallback.mStartedUsers).isEmpty();
- assertThat(mStartUserClientCount).isEqualTo(1);
- for (BaseClientMonitor client : nextClients) {
- verify(client, never()).start(any());
- }
- }
-
- @Test
- public void testScheduleOperation_whenNoUser_notStarted_andReset() {
- mCurrentUserId = UserHandle.USER_NULL;
- mStartOperationsFinish = false;
-
- final BaseClientMonitor client = mock(BaseClientMonitor.class);
- when(client.getTargetUserId()).thenReturn(5);
- mScheduler.scheduleClientMonitor(client);
- waitForIdle();
-
- final TestStartUserClient startUserClient =
- (TestStartUserClient) mScheduler.mCurrentOperation.getClientMonitor();
- mScheduler.reset();
- assertThat(mScheduler.mCurrentOperation).isNull();
-
- final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation(
- mock(BaseClientMonitor.class), new ClientMonitorCallback() {});
- mScheduler.mCurrentOperation = fakeOperation;
- startUserClient.mCallback.onClientFinished(startUserClient, true);
- assertThat(fakeOperation).isSameInstanceAs(mScheduler.mCurrentOperation);
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void testScheduleOperation_whenSameUser() {
- mCurrentUserId = 10;
-
- BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
- when(nextClient.getTargetUserId()).thenReturn(mCurrentUserId);
-
- mScheduler.scheduleClientMonitor(nextClient);
-
- waitForIdle();
-
- verify(nextClient).start(any());
- assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(0);
- assertThat(mUserStartedCallback.mStartedUsers).isEmpty();
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void testScheduleOperation_whenDifferentUser() {
- mCurrentUserId = 10;
-
- final int nextUserId = 11;
- BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
- when(nextClient.getTargetUserId()).thenReturn(nextUserId);
-
- mScheduler.scheduleClientMonitor(nextClient);
-
- waitForIdle();
- assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(1);
-
- waitForIdle();
- assertThat(mUserStartedCallback.mStartedUsers).containsExactly(nextUserId);
-
- waitForIdle();
- verify(nextClient).start(any());
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void testStartUser_alwaysStartsNextOperation() {
- BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
- when(nextClient.getTargetUserId()).thenReturn(10);
-
- mScheduler.scheduleClientMonitor(nextClient);
-
- waitForIdle();
- verify(nextClient).start(any());
-
- // finish first operation
- mScheduler.getInternalCallback().onClientFinished(nextClient, true /* success */);
- waitForIdle();
-
- // schedule second operation but swap out the current operation
- // before it runs so that it's not current when it's completion callback runs
- nextClient = mock(BaseClientMonitor.class);
- when(nextClient.getTargetUserId()).thenReturn(11);
- mUserStartedCallback.mAfterStart = () -> mScheduler.mCurrentOperation = null;
- mScheduler.scheduleClientMonitor(nextClient);
-
- waitForIdle();
- verify(nextClient).start(any());
- assertThat(mUserStartedCallback.mStartedUsers).containsExactly(10, 11).inOrder();
- assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(1);
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void testStartUser_failsClearsStopUserClient() {
- // When a stop user client fails, check that mStopUserClient
- // is set to null to prevent the scheduler from getting stuck.
- BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
- when(nextClient.getTargetUserId()).thenReturn(10);
-
- mScheduler.scheduleClientMonitor(nextClient);
-
- waitForIdle();
- verify(nextClient).start(any());
-
- // finish first operation
- mScheduler.getInternalCallback().onClientFinished(nextClient, true /* success */);
- waitForIdle();
-
- // schedule second operation but swap out the current operation
- // before it runs so that it's not current when it's completion callback runs
- nextClient = mock(BaseClientMonitor.class);
- when(nextClient.getTargetUserId()).thenReturn(11);
- mUserStartedCallback.mAfterStart = () -> mScheduler.mCurrentOperation = null;
- mShouldFailStopUser = true;
- mScheduler.scheduleClientMonitor(nextClient);
-
- waitForIdle();
- assertThat(mUserStartedCallback.mStartedUsers).containsExactly(10, 11).inOrder();
- assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(0);
- assertThat(mScheduler.getStopUserClient()).isEqualTo(null);
- }
-
- private void waitForIdle() {
- TestableLooper.get(this).processAllMessages();
- }
-
- private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback {
- int mNumInvocations;
-
- @Override
- public void onUserStopped() {
- mNumInvocations++;
- mCurrentUserId = UserHandle.USER_NULL;
- }
- }
-
- private class TestUserStartedCallback implements StartUserClient.UserStartedCallback<Object> {
- final List<Integer> mStartedUsers = new ArrayList<>();
- Runnable mAfterStart = null;
-
- @Override
- public void onUserStarted(int newUserId, Object newObject, int halInterfaceVersion) {
- mStartedUsers.add(newUserId);
- mCurrentUserId = newUserId;
- if (mAfterStart != null) {
- mAfterStart.run();
- }
- }
- }
-
- private interface StopUserClientShouldFail {
- boolean shouldFail();
- }
-
- private class TestStopUserClient extends StopUserClient<Object> {
- private StopUserClientShouldFail mShouldFailClient;
- public TestStopUserClient(@NonNull Context context,
- @NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId,
- int sensorId, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext,
- @NonNull UserStoppedCallback callback, StopUserClientShouldFail shouldFail) {
- super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
- mShouldFailClient = shouldFail;
- }
-
- @Override
- protected void startHalOperation() {
-
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
- if (mShouldFailClient.shouldFail()) {
- getCallback().onClientFinished(this, false /* success */);
- // When the above fails, it means that the HAL has died, in this case we
- // need to ensure the UserSwitchCallback correctly returns the NULL user handle.
- mCurrentUserId = UserHandle.USER_NULL;
- } else {
- onUserStopped();
- }
- }
-
- @Override
- public void unableToStart() {
-
- }
- }
-
- private static class TestStartUserClient extends StartUserClient<Object, Object> {
- private final boolean mShouldFinish;
-
- ClientMonitorCallback mCallback;
-
- public TestStartUserClient(@NonNull Context context,
- @NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId,
- int sensorId, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext,
- @NonNull UserStartedCallback<Object> callback, boolean shouldFinish) {
- super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
- mShouldFinish = shouldFinish;
- }
-
- @Override
- protected void startHalOperation() {
-
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
-
- mCallback = callback;
- if (mShouldFinish) {
- mUserStartedCallback.onUserStarted(
- getTargetUserId(), new Object(), 1 /* halInterfaceVersion */);
- callback.onClientFinished(this, true /* success */);
- }
- }
-
- @Override
- public void unableToStart() {
-
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index e4c56a7..1ca36a3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -147,11 +147,10 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
- public void registerAuthenticatorsLegacy_defaultOnly() throws Exception {
+ public void registerAuthenticators_defaultOnly() throws Exception {
initService();
- mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT),
@@ -161,13 +160,13 @@
}
@Test
- @RequiresFlagsEnabled({Flags.FLAG_DE_HIDL, Flags.FLAG_FACE_VHAL_FEATURE})
+ @RequiresFlagsEnabled(Flags.FLAG_FACE_VHAL_FEATURE)
public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
initService();
Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
- mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
@@ -176,13 +175,13 @@
}
@Test
- @RequiresFlagsEnabled({Flags.FLAG_DE_HIDL, Flags.FLAG_FACE_VHAL_FEATURE})
- public void registerAuthenticatorsLegacy_virtualFaceOnly() throws Exception {
+ @RequiresFlagsEnabled(Flags.FLAG_FACE_VHAL_FEATURE)
+ public void registerAuthenticators_virtualFaceOnly() throws Exception {
initService();
Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
Settings.Secure.BIOMETRIC_FACE_VIRTUAL_ENABLED, 1);
- mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
@@ -191,13 +190,12 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
- public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
+ public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
mFaceSensorConfigurations = new FaceSensorConfigurations(false);
mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_VIRTUAL});
initService();
- mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
@@ -210,7 +208,7 @@
FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
.build();
initService();
- mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+ mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
waitForRegistration();
final long operationId = 5;
@@ -230,7 +228,7 @@
R.string.config_keyguardComponent,
OP_PACKAGE_NAME);
initService();
- mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+ mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
waitForRegistration();
mFaceService.mServiceWrapper.detectFace(mToken, mFaceServiceReceiver,
faceAuthenticateOptions);
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 84c3684..a556f52 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
@@ -50,8 +50,6 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-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.platform.test.flag.junit.SetFlagsRule;
@@ -60,7 +58,6 @@
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.OperationContextExt;
@@ -137,6 +134,8 @@
private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
@Captor
private ArgumentCaptor<Consumer<OperationContext>> mStartHalConsumerCaptor;
+ @Captor
+ private ArgumentCaptor<FaceAuthenticateOptions> mFaceAuthenticateOptionsCaptor;
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -159,21 +158,26 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
public void authWithContext_v2() throws RemoteException {
final FaceAuthenticationClient client = createClient(2);
client.start(mCallback);
+ verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+ mStartHalConsumerCaptor.capture(), mContextInjector.capture(),
+ mFaceAuthenticateOptionsCaptor.capture());
+
+ mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor.getValue().toAidlContext(
+ mFaceAuthenticateOptionsCaptor.getValue()));
InOrder order = inOrder(mHal, mBiometricContext);
order.verify(mBiometricContext).updateContext(
mOperationContextCaptor.capture(), anyBoolean());
final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
+
order.verify(mHal).authenticateWithContext(eq(OP_ID), same(aidlContext));
assertThat(aidlContext.wakeReason).isEqualTo(WAKE_REASON);
assertThat(aidlContext.authenticateReason.getFaceAuthenticateReason())
.isEqualTo(AUTH_REASON);
-
verify(mHal, never()).authenticate(anyLong());
}
@@ -200,30 +204,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void notifyHalWhenContextChanges() throws RemoteException {
- final FaceAuthenticationClient client = createClient();
- client.start(mCallback);
-
- final ArgumentCaptor<OperationContext> captor =
- ArgumentCaptor.forClass(OperationContext.class);
- verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
- OperationContext opContext = captor.getValue();
-
- // fake an update to the context
- verify(mBiometricContext).subscribe(
- mOperationContextCaptor.capture(), mContextInjector.capture());
- assertThat(opContext).isSameInstanceAs(
- mOperationContextCaptor.getValue().toAidlContext());
- mContextInjector.getValue().accept(opContext);
- verify(mHal).onContextChanged(same(opContext));
-
- client.stopHalOperation();
- verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void subscribeContextAndStartHal() throws RemoteException {
final FaceAuthenticationClient client = createClient();
client.start(mCallback);
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 e626f73..fd3f054 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
@@ -20,7 +20,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
@@ -37,8 +36,6 @@
import android.os.RemoteException;
import android.os.Vibrator;
import android.platform.test.annotations.Presubmit;
-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.TestableContext;
@@ -46,7 +43,6 @@
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.OperationContextExt;
@@ -58,7 +54,6 @@
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -124,49 +119,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void detectWithContext_v2() throws RemoteException {
- final FaceDetectClient client = createClient(2);
- client.start(mCallback);
-
- InOrder order = inOrder(mHal, mBiometricContext);
- order.verify(mBiometricContext).updateContext(
- mOperationContextCaptor.capture(), anyBoolean());
-
- final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
- order.verify(mHal).detectInteractionWithContext(same(aidlContext));
- assertThat(aidlContext.wakeReason).isEqualTo(WAKE_REASON);
- assertThat(aidlContext.authenticateReason.getFaceAuthenticateReason())
- .isEqualTo(AUTH_REASON);
-
- verify(mHal, never()).detectInteraction();
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void notifyHalWhenContextChanges() throws RemoteException {
- final FaceDetectClient client = createClient();
- client.start(mCallback);
-
- final ArgumentCaptor<OperationContext> captor =
- ArgumentCaptor.forClass(OperationContext.class);
- verify(mHal).detectInteractionWithContext(captor.capture());
- OperationContext opContext = captor.getValue();
-
- // fake an update to the context
- verify(mBiometricContext).subscribe(
- mOperationContextCaptor.capture(), mContextInjector.capture());
- assertThat(opContext).isSameInstanceAs(
- mOperationContextCaptor.getValue().toAidlContext());
- mContextInjector.getValue().accept(opContext);
- verify(mHal).onContextChanged(same(opContext));
-
- client.stopHalOperation();
- verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void subscribeContextAndStartHal() throws RemoteException {
final FaceDetectClient client = createClient();
client.start(mCallback);
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 02363cd..d6b5789 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
@@ -24,7 +24,6 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
@@ -38,8 +37,6 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-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.TestableContext;
@@ -47,7 +44,6 @@
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.OperationContextExt;
@@ -60,7 +56,6 @@
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -124,45 +119,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void enrollWithContext_v2() throws RemoteException {
- final FaceEnrollClient client = createClient(2);
- client.start(mCallback);
-
- InOrder order = inOrder(mHal, mBiometricContext);
- order.verify(mBiometricContext).updateContext(
- mOperationContextCaptor.capture(), anyBoolean());
-
- final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
- order.verify(mHal).enrollWithContext(any(), anyByte(), any(), any(), same(aidlContext));
- verify(mHal, never()).enroll(any(), anyByte(), any(), any());
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void notifyHalWhenContextChanges() throws RemoteException {
- final FaceEnrollClient client = createClient(3);
- client.start(mCallback);
-
- final ArgumentCaptor<OperationContext> captor =
- ArgumentCaptor.forClass(OperationContext.class);
- verify(mHal).enrollWithContext(any(), anyByte(), any(), any(), captor.capture());
- OperationContext opContext = captor.getValue();
-
- // fake an update to the context
- verify(mBiometricContext).subscribe(
- mOperationContextCaptor.capture(), mContextInjector.capture());
- assertThat(opContext).isSameInstanceAs(
- mOperationContextCaptor.getValue().toAidlContext());
- mContextInjector.getValue().accept(opContext);
- verify(mHal).onContextChanged(same(opContext));
-
- client.stopHalOperation();
- verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void subscribeContextAndStartHal() throws RemoteException {
final FaceEnrollClient client = createClient(3);
client.start(mCallback);
@@ -192,16 +148,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void enrollWithFaceOptions() throws RemoteException {
- final FaceEnrollClient client = createClient(4);
- client.start(mCallback);
-
- verify(mHal).enrollWithOptions(any());
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void enrollWithFaceOptionsAfterSubscribingContext() throws RemoteException {
final FaceEnrollClient client = createClient(4);
client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 9eca93e..c4e51f8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -44,22 +44,18 @@
import android.hardware.face.HidlFaceSensorConfig;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserManager;
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 androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.server.biometrics.BiometricHandlerProvider;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationStateListeners;
@@ -134,13 +130,8 @@
when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
mBiometricCallbackHandler);
when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
- if (Flags.deHidl()) {
- when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
- mLooper.getLooper()));
- } else {
- when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
- Looper.getMainLooper()));
- }
+ when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
+ mLooper.getLooper()));
final SensorProps sensor1 = new SensorProps();
sensor1.commonProps = new CommonProps();
@@ -176,7 +167,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testAddingHidlSensors() {
when(mResources.getIntArray(anyInt())).thenReturn(new int[]{});
when(mResources.getBoolean(anyInt())).thenReturn(false);
@@ -247,7 +237,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testAuthenticateCallbackHandler() {
waitForIdle();
@@ -295,10 +284,6 @@
}
private void waitForIdle() {
- if (Flags.deHidl()) {
- mLooper.dispatchAll();
- } else {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- }
+ mLooper.dispatchAll();
}
}
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 fe9cd43..6780e60 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
@@ -41,7 +41,6 @@
import androidx.test.filters.SmallTest;
-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.sensors.AuthSessionCoordinator;
@@ -50,7 +49,6 @@
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.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.UserSwitchProvider;
import org.junit.Before;
@@ -75,12 +73,8 @@
@Mock
private ISession mSession;
@Mock
- private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
- @Mock
private UserSwitchProvider<IFace, ISession> mUserSwitchProvider;
@Mock
- private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
- @Mock
private LockoutResetDispatcher mLockoutResetDispatcher;
@Mock
private BiometricLogger mBiometricLogger;
@@ -94,6 +88,8 @@
private BaseClientMonitor mClientMonitor;
@Mock
private AidlSession mCurrentSession;
+ @Mock
+ private AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
@@ -108,27 +104,17 @@
when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
- if (Flags.deHidl()) {
- mScheduler = new BiometricScheduler<>(
- new Handler(mLooper.getLooper()),
- BiometricScheduler.SENSOR_TYPE_FACE,
- null /* gestureAvailabilityDispatcher */,
- mBiometricService,
- 2 /* recentOperationsLimit */,
- () -> USER_ID,
- mUserSwitchProvider);
- } else {
- mScheduler = new UserAwareBiometricScheduler<>(TAG,
- new Handler(mLooper.getLooper()),
- BiometricScheduler.SENSOR_TYPE_FACE,
- null /* gestureAvailabilityDispatcher */,
- mBiometricService,
- () -> USER_ID,
- mUserSwitchCallback);
- }
+ mScheduler = new BiometricScheduler<>(
+ new Handler(mLooper.getLooper()),
+ BiometricScheduler.SENSOR_TYPE_FACE,
+ null /* gestureAvailabilityDispatcher */,
+ mBiometricService,
+ 2 /* recentOperationsLimit */,
+ () -> USER_ID,
+ mUserSwitchProvider);
mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- mHardwareUnavailableCallback);
+ mAidlResponseHandlerCallback);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
deleted file mode 100644
index 949d6ee..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ /dev/null
@@ -1,220 +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.biometrics.sensors.face.hidl;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.isA;
-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.content.res.Resources;
-import android.hardware.biometrics.ComponentInfoInternal;
-import android.hardware.biometrics.SensorProperties;
-import android.hardware.face.FaceSensorProperties;
-import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.face.IFaceServiceReceiver;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.UserManager;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.R;
-import com.android.server.biometrics.Flags;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.time.Clock;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.IntStream;
-
-@Presubmit
-@SmallTest
-public class Face10Test {
-
- private static final String TAG = "Face10Test";
- private static final int SENSOR_ID = 1;
- private static final int USER_ID = 20;
- private static final float FRR_THRESHOLD = 0.2f;
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Mock
- private Context mContext;
- @Mock
- private UserManager mUserManager;
- @Mock
- private Resources mResources;
- @Mock
- private BiometricScheduler mScheduler;
- @Mock
- private BiometricContext mBiometricContext;
- @Mock
- private BiometricStateCallback mBiometricStateCallback;
- @Mock
- private AuthenticationStateListeners mAuthenticationStateListeners;
-
- private final Handler mHandler = new Handler(Looper.getMainLooper());
- private LockoutResetDispatcher mLockoutResetDispatcher;
- private com.android.server.biometrics.sensors.face.hidl.Face10 mFace10;
- private IBinder mBinder;
-
- private static void waitForIdle() {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- }
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
- when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
-
- when(mContext.getResources()).thenReturn(mResources);
- when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
- .thenReturn(FRR_THRESHOLD);
-
- mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
-
- final int maxEnrollmentsPerUser = 1;
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- final boolean supportsFaceDetection = false;
- final boolean supportsSelfIllumination = false;
- final boolean resetLockoutRequiresChallenge = false;
- final FaceSensorPropertiesInternal sensorProps = new FaceSensorPropertiesInternal(SENSOR_ID,
- SensorProperties.STRENGTH_STRONG, maxEnrollmentsPerUser, componentInfo,
- FaceSensorProperties.TYPE_UNKNOWN, supportsFaceDetection, supportsSelfIllumination,
- resetLockoutRequiresChallenge);
-
- Face10.sSystemClock = Clock.fixed(
- Instant.ofEpochMilli(100), ZoneId.of("America/Los_Angeles"));
- mFace10 = new Face10(mContext, mBiometricStateCallback, mAuthenticationStateListeners,
- sensorProps, mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
- mBinder = new Binder();
- }
-
- private void tick(long seconds) {
- waitForIdle();
- Face10.sSystemClock = Clock.fixed(Instant.ofEpochSecond(
- Face10.sSystemClock.instant().getEpochSecond() + seconds),
- ZoneId.of("America/Los_Angeles"));
- }
-
- @Test
- public void getAuthenticatorId_doesNotCrashWhenIdNotFound() {
- assertEquals(0, mFace10.getAuthenticatorId(0 /* sensorId */, 111 /* userId */));
- waitForIdle();
- }
-
- @Test
- public void scheduleRevokeChallenge_doesNotCrash() {
- mFace10.scheduleRevokeChallenge(0 /* sensorId */, 0 /* userId */, mBinder, TAG,
- 0 /* challenge */);
- waitForIdle();
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void scheduleGenerateChallenge_cachesResult() {
- final IFaceServiceReceiver[] mocks = IntStream.range(0, 3)
- .mapToObj(i -> mock(IFaceServiceReceiver.class))
- .toArray(IFaceServiceReceiver[]::new);
- for (IFaceServiceReceiver mock : mocks) {
- mFace10.scheduleGenerateChallenge(SENSOR_ID, USER_ID, mBinder, mock, TAG);
- tick(10);
- }
- tick(120);
- mFace10.scheduleGenerateChallenge(
- SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
- waitForIdle();
-
- verify(mScheduler, times(2))
- .scheduleClientMonitor(isA(FaceGenerateChallengeClient.class), any());
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void scheduleRevokeChallenge_waitsUntilEmpty() {
- final long challenge = 22;
- final IFaceServiceReceiver[] mocks = IntStream.range(0, 3)
- .mapToObj(i -> mock(IFaceServiceReceiver.class))
- .toArray(IFaceServiceReceiver[]::new);
- for (IFaceServiceReceiver mock : mocks) {
- mFace10.scheduleGenerateChallenge(SENSOR_ID, USER_ID, mBinder, mock, TAG);
- tick(10);
- }
- for (IFaceServiceReceiver mock : mocks) {
- mFace10.scheduleRevokeChallenge(SENSOR_ID, USER_ID, mBinder, TAG, challenge);
- tick(10);
- }
- waitForIdle();
-
- verify(mScheduler).scheduleClientMonitor(isA(FaceRevokeChallengeClient.class), any());
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void scheduleRevokeChallenge_doesNotWaitForever() {
- mFace10.scheduleGenerateChallenge(
- SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
- mFace10.scheduleGenerateChallenge(
- SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
- tick(10000);
- mFace10.scheduleGenerateChallenge(
- SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
- mFace10.scheduleRevokeChallenge(
- SENSOR_ID, USER_ID, mBinder, TAG, 8 /* challenge */);
- waitForIdle();
-
- verify(mScheduler).scheduleClientMonitor(isA(FaceRevokeChallengeClient.class), any());
- }
-
- @Test
- public void halServiceDied_resetsScheduler() {
- // It's difficult to test the linkToDeath --> serviceDied path, so let's just invoke
- // serviceDied directly.
- mFace10.serviceDied(0 /* cookie */);
- waitForIdle();
- verify(mScheduler).reset();
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
deleted file mode 100644
index ec08329..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
+++ /dev/null
@@ -1,111 +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.biometrics.sensors.face.hidl;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-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.V1_0.IBiometricsFace;
-import android.hardware.biometrics.face.V1_0.OptionalUint64;
-import android.hardware.face.IFaceServiceReceiver;
-import android.os.Binder;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.core.app.ApplicationProvider;
-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 com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@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 START_TIME = 5000;
- private static final long CHALLENGE = 200;
-
- private final Context mContext = ApplicationProvider.getApplicationContext();
-
- @Mock
- private IBiometricsFace mIBiometricsFace;
- @Mock
- private IFaceServiceReceiver mClientReceiver;
- @Mock
- private IFaceServiceReceiver mOtherReceiver;
- @Mock
- private ClientMonitorCallback mMonitorCallback;
- @Mock
- private BiometricLogger mBiometricLogger;
- @Mock
- private BiometricContext mBiometricContext;
-
- private FaceGenerateChallengeClient mClient;
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- final OptionalUint64 challenge = new OptionalUint64();
- challenge.value = CHALLENGE;
- when(mIBiometricsFace.generateChallenge(anyInt())).thenReturn(challenge);
-
- mClient = new FaceGenerateChallengeClient(mContext, () -> mIBiometricsFace, new Binder(),
- new ClientMonitorCallbackConverter(mClientReceiver), USER_ID,
- TAG, SENSOR_ID, mBiometricLogger, mBiometricContext , START_TIME);
- }
-
- @Test
- public void getCreatedAt() {
- assertEquals(START_TIME, mClient.getCreatedAt());
- }
-
- @Test
- public void reuseResult_whenNotReady() throws Exception {
- mClient.reuseResult(mOtherReceiver);
- verify(mOtherReceiver, never()).onChallengeGenerated(anyInt(), anyInt(), anyInt());
- }
-
- @Test
- public void reuseResult_whenReady() throws Exception {
- mClient.start(mMonitorCallback);
- mClient.reuseResult(mOtherReceiver);
- verify(mOtherReceiver).onChallengeGenerated(eq(SENSOR_ID), eq(USER_ID), eq(CHALLENGE));
- }
-
- @Test
- public void reuseResult_whenReallyReady() throws Exception {
- mClient.reuseResult(mOtherReceiver);
- mClient.start(mMonitorCallback);
- verify(mOtherReceiver).onChallengeGenerated(eq(SENSOR_ID), eq(USER_ID), eq(CHALLENGE));
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
index b5d73d2..44da431 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
@@ -16,8 +16,8 @@
package com.android.server.biometrics.sensors.face.hidl;
-import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC;
import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC;
+import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.CHALLENGE_TIMEOUT_SEC;
import static com.google.common.truth.Truth.assertThat;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index 9a8cd48..6126af5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -51,7 +51,6 @@
import android.os.Binder;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
@@ -64,7 +63,6 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
@@ -90,8 +88,6 @@
private static final int ID_VIRTUAL = 6;
private static final String NAME_DEFAULT = "default";
private static final String NAME_VIRTUAL = "virtual";
- private static final List<FingerprintSensorPropertiesInternal> HIDL_AUTHENTICATORS =
- List.of();
private static final String OP_PACKAGE_NAME = "FingerprintServiceTest/SystemUi";
@Rule
@@ -185,7 +181,7 @@
private void initServiceWithAndWait(String... aidlInstances) throws Exception {
initServiceWith(aidlInstances);
- mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
+ mService.mServiceWrapper.registerAuthenticators(mFingerprintSensorConfigurations);
waitForRegistration();
}
@@ -193,18 +189,7 @@
public void registerAuthenticators_defaultOnly() throws Exception {
initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
- mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
- waitForRegistration();
-
- verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
- public void registerAuthenticatorsLegacy_defaultOnly() throws Exception {
- initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
-
- mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ mService.mServiceWrapper.registerAuthenticators(mFingerprintSensorConfigurations);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
@@ -216,7 +201,7 @@
Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
- mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
+ mService.mServiceWrapper.registerAuthenticators(mFingerprintSensorConfigurations);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
@@ -228,20 +213,7 @@
Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
Settings.Secure.BIOMETRIC_FINGERPRINT_VIRTUAL_ENABLED, 1);
- mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
- waitForRegistration();
-
- verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
- public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
- initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
- Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
- Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
-
- mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ mService.mServiceWrapper.registerAuthenticators(mFingerprintSensorConfigurations);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
@@ -249,23 +221,12 @@
@Test
public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
- initServiceWith(NAME_VIRTUAL);
-
- mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
- waitForRegistration();
-
- verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
- public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
mFingerprintSensorConfigurations =
new FingerprintSensorConfigurations(true);
mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_VIRTUAL});
initServiceWith(NAME_VIRTUAL);
- mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ mService.mServiceWrapper.registerAuthenticators(mFingerprintSensorConfigurations);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
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 7a77392..db9fe7f 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
@@ -30,7 +30,6 @@
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.same;
@@ -42,7 +41,6 @@
import android.app.ActivityTaskManager;
import android.content.ComponentName;
import android.hardware.biometrics.BiometricManager;
-import android.hardware.biometrics.common.AuthenticateReason;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.ISession;
@@ -52,13 +50,10 @@
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
-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.platform.test.flag.junit.SetFlagsRule;
@@ -67,7 +62,6 @@
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;
@@ -84,7 +78,6 @@
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -180,36 +173,18 @@
public void authNoContext_v1() throws RemoteException {
final FingerprintAuthenticationClient client = createClient(1);
client.start(mCallback);
- if (Flags.deHidl()) {
- verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
- mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
- mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
- .getValue().toAidlContext());
- }
+
+ verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+ mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+
+ mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+ .getValue().toAidlContext());
verify(mHal).authenticate(eq(OP_ID));
verify(mHal, never()).authenticateWithContext(anyLong(), any());
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void authWithContext_v2() throws RemoteException {
- final FingerprintAuthenticationClient client = createClient(2);
- client.start(mCallback);
-
- InOrder order = inOrder(mHal, mBiometricContext);
- order.verify(mBiometricContext).updateContext(
- mOperationContextCaptor.capture(), anyBoolean());
-
- final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
- order.verify(mHal).authenticateWithContext(eq(OP_ID), same(aidlContext));
- assertThat(aidlContext.authenticateReason.getFingerprintAuthenticateReason())
- .isEqualTo(AuthenticateReason.Fingerprint.UNKNOWN);
-
- verify(mHal, never()).authenticate(anyLong());
- }
-
- @Test
public void pointerUp_v1() throws RemoteException {
final FingerprintAuthenticationClient client = createClient(1);
client.start(mCallback);
@@ -277,21 +252,18 @@
final FingerprintAuthenticationClient client = createClient();
client.start(mCallback);
- if (Flags.deHidl()) {
- verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
- mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
- mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
- .getValue().toAidlContext());
- }
+
+ verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+ mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+
+ mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+ .getValue().toAidlContext());
final ArgumentCaptor<OperationContext> captor =
ArgumentCaptor.forClass(OperationContext.class);
verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
OperationContext opContext = captor.getValue();
- if (!Flags.deHidl()) {
- verify(mBiometricContext).subscribe(
- mOperationContextCaptor.capture(), mContextInjector.capture());
- }
+
assertThat(mOperationContextCaptor.getValue().toAidlContext())
.isSameInstanceAs(opContext);
@@ -326,12 +298,12 @@
when(mBiometricContext.isAod()).thenReturn(false);
final FingerprintAuthenticationClient client = createClient();
client.start(mCallback);
- if (Flags.deHidl()) {
- verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
- mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
- mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
- .getValue().toAidlContext());
- }
+
+ verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+ mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+
+ mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+ .getValue().toAidlContext());
verify(mLuxProbe, isAwake ? times(1) : never()).enable();
}
@@ -342,21 +314,18 @@
when(mBiometricContext.isAod()).thenReturn(true);
final FingerprintAuthenticationClient client = createClient();
client.start(mCallback);
- if (Flags.deHidl()) {
- verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
- mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
- mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
- .getValue().toAidlContext());
- }
+
+ verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+ mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+
+ mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+ .getValue().toAidlContext());
final ArgumentCaptor<OperationContext> captor =
ArgumentCaptor.forClass(OperationContext.class);
verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
OperationContext opContext = captor.getValue();
- if (!Flags.deHidl()) {
- verify(mBiometricContext).subscribe(
- mOperationContextCaptor.capture(), mContextInjector.capture());
- }
+
assertThat(opContext).isSameInstanceAs(
mOperationContextCaptor.getValue().toAidlContext());
@@ -380,30 +349,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void notifyHalWhenContextChanges() throws RemoteException {
- final FingerprintAuthenticationClient client = createClient();
- client.start(mCallback);
-
- final ArgumentCaptor<OperationContext> captor =
- ArgumentCaptor.forClass(OperationContext.class);
- verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
- OperationContext opContext = captor.getValue();
-
- // fake an update to the context
- verify(mBiometricContext).subscribe(
- mOperationContextCaptor.capture(), mContextInjector.capture());
- assertThat(opContext).isSameInstanceAs(
- mOperationContextCaptor.getValue().toAidlContext());
- mContextInjector.getValue().accept(opContext);
- verify(mHal).onContextChanged(same(opContext));
-
- client.stopHalOperation();
- verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void subscribeContextAndStartHal() throws RemoteException {
final FingerprintAuthenticationClient client = createClient();
client.start(mCallback);
@@ -603,7 +548,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testLockoutTracker_authFailed() throws RemoteException {
final FingerprintAuthenticationClient client = createClient(1 /* version */,
true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter,
@@ -658,8 +602,7 @@
null /* taskStackListener */,
mUdfpsOverlayController, mSideFpsController, mAuthenticationStateListeners,
allowBackgroundAuthentication,
- mSensorProps,
- new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock,
+ mSensorProps, 0 /* biometricStrength */,
lockoutTracker) {
@Override
protected ActivityTaskManager getActivityTaskManager() {
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 9edb8dd..6b8c3cd 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
@@ -21,13 +21,11 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.hardware.biometrics.common.AuthenticateReason;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
@@ -35,8 +33,6 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-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.TestableContext;
@@ -44,7 +40,6 @@
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.OperationContextExt;
@@ -56,7 +51,6 @@
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -119,49 +113,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void detectNoContext_v2() throws RemoteException {
- final FingerprintDetectClient client = createClient(2);
-
- client.start(mCallback);
-
- InOrder order = inOrder(mHal, mBiometricContext);
- order.verify(mBiometricContext).updateContext(
- mOperationContextCaptor.capture(), anyBoolean());
-
- final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
- order.verify(mHal).detectInteractionWithContext(same(aidlContext));
- assertThat(aidlContext.authenticateReason.getFingerprintAuthenticateReason())
- .isEqualTo(AuthenticateReason.Fingerprint.UNKNOWN);
-
- verify(mHal, never()).detectInteraction();
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void notifyHalWhenContextChanges() throws RemoteException {
- final FingerprintDetectClient client = createClient();
- client.start(mCallback);
-
- final ArgumentCaptor<OperationContext> captor =
- ArgumentCaptor.forClass(OperationContext.class);
- verify(mHal).detectInteractionWithContext(captor.capture());
- OperationContext opContext = captor.getValue();
-
- // fake an update to the context
- verify(mBiometricContext).subscribe(
- mOperationContextCaptor.capture(), mContextInjector.capture());
- assertThat(opContext).isSameInstanceAs(
- mOperationContextCaptor.getValue().toAidlContext());
- mContextInjector.getValue().accept(opContext);
- verify(mHal).onContextChanged(same(opContext));
-
- client.stopHalOperation();
- verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void subscribeContextAndStartHal() throws RemoteException {
final FingerprintDetectClient client = createClient();
client.start(mCallback);
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 916f696..d2e1c3c 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
@@ -24,7 +24,6 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.times;
@@ -44,8 +43,6 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-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.platform.test.flag.junit.SetFlagsRule;
@@ -54,7 +51,6 @@
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;
@@ -70,7 +66,6 @@
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -156,21 +151,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void enrollWithContext_v2() throws RemoteException {
- final FingerprintEnrollClient client = createClient(2);
-
- client.start(mCallback);
-
- InOrder order = inOrder(mHal, mBiometricContext);
- order.verify(mBiometricContext).updateContext(
- mOperationContextCaptor.capture(), anyBoolean());
- order.verify(mHal).enrollWithContext(any(),
- same(mOperationContextCaptor.getValue().toAidlContext()));
- verify(mHal, never()).enroll(any());
- }
-
- @Test
public void pointerUp_v1() throws RemoteException {
final FingerprintEnrollClient client = createClient(1);
client.start(mCallback);
@@ -253,29 +233,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void notifyHalWhenContextChanges() throws RemoteException {
- final FingerprintEnrollClient client = createClient();
- client.start(mCallback);
-
- final ArgumentCaptor<OperationContext> captor =
- ArgumentCaptor.forClass(OperationContext.class);
- verify(mHal).enrollWithContext(any(), captor.capture());
- OperationContext opContext = captor.getValue();
-
- // fake an update to the context
- verify(mBiometricContext).subscribe(
- mOperationContextCaptor.capture(), mContextInjector.capture());
- mContextInjector.getValue().accept(
- mOperationContextCaptor.getValue().toAidlContext());
- verify(mHal).onContextChanged(same(opContext));
-
- client.stopHalOperation();
- verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void subscribeContextAndStartHal() throws RemoteException {
final FingerprintEnrollClient client = createClient();
client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 0a35037..1f288b2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -47,21 +47,17 @@
import android.hardware.fingerprint.HidlFingerprintSensorConfig;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserManager;
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 androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.BiometricHandlerProvider;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationStateListeners;
@@ -136,13 +132,8 @@
when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
mBiometricCallbackHandler);
- if (Flags.deHidl()) {
- when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
- new Handler(mLooper.getLooper()));
- } else {
- when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
- new Handler(Looper.getMainLooper()));
- }
+ when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
+ new Handler(mLooper.getLooper()));
final SensorProps sensor1 = new SensorProps();
sensor1.commonProps = new CommonProps();
@@ -176,7 +167,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testAddingHidlSensors() {
when(mResources.getIntArray(anyInt())).thenReturn(new int[]{});
when(mResources.getBoolean(anyInt())).thenReturn(false);
@@ -252,7 +242,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testScheduleAuthenticate() {
waitForIdle();
@@ -302,10 +291,6 @@
}
private void waitForIdle() {
- if (Flags.deHidl()) {
- mLooper.dispatchAll();
- } else {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- }
+ mLooper.dispatchAll();
}
}
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 b4c2ee8..698db2e 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
@@ -42,7 +42,6 @@
import androidx.test.filters.SmallTest;
-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.sensors.AuthSessionCoordinator;
@@ -51,7 +50,6 @@
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.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.UserSwitchProvider;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
@@ -77,12 +75,8 @@
@Mock
private ISession mSession;
@Mock
- private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
- @Mock
private UserSwitchProvider<IFingerprint, ISession> mUserSwitchProvider;
@Mock
- private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
- @Mock
private LockoutResetDispatcher mLockoutResetDispatcher;
@Mock
private BiometricLogger mLogger;
@@ -100,6 +94,8 @@
private BaseClientMonitor mClientMonitor;
@Mock
private HandlerThread mThread;
+ @Mock
+ AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
@@ -115,27 +111,17 @@
when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
when(mThread.getLooper()).thenReturn(mLooper.getLooper());
- if (Flags.deHidl()) {
- mScheduler = new BiometricScheduler<>(
- new Handler(mLooper.getLooper()),
- BiometricScheduler.SENSOR_TYPE_FP_OTHER,
- null /* gestureAvailabilityDispatcher */,
- mBiometricService,
- 2 /* recentOperationsLimit */,
- () -> USER_ID,
- mUserSwitchProvider);
- } else {
- mScheduler = new UserAwareBiometricScheduler<>(TAG,
- new Handler(mLooper.getLooper()),
- BiometricScheduler.SENSOR_TYPE_FP_OTHER,
- null /* gestureAvailabilityDispatcher */,
- mBiometricService,
- () -> USER_ID,
- mUserSwitchCallback);
- }
+ mScheduler = new BiometricScheduler<>(
+ new Handler(mLooper.getLooper()),
+ BiometricScheduler.SENSOR_TYPE_FP_OTHER,
+ null /* gestureAvailabilityDispatcher */,
+ mBiometricService,
+ 2 /* recentOperationsLimit */,
+ () -> USER_ID,
+ mUserSwitchProvider);
mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- mHardwareUnavailableCallback);
+ mAidlResponseHandlerCallback);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
deleted file mode 100644
index 0d3f192..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
+++ /dev/null
@@ -1,148 +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.biometrics.sensors.fingerprint.hidl;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.biometrics.ComponentInfoInternal;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserManager;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.annotation.NonNull;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.R;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@Presubmit
-@SmallTest
-public class Fingerprint21Test {
-
- private static final String TAG = "Fingerprint21Test";
- private static final int SENSOR_ID = 1;
-
- @Mock
- private Context mContext;
- @Mock
- private Resources mResources;
- @Mock
- private UserManager mUserManager;
- @Mock
- Fingerprint21.HalResultController mHalResultController;
- @Mock
- private BiometricScheduler mScheduler;
- @Mock
- private AuthenticationStateListeners mAuthenticationStateListeners;
- @Mock
- private BiometricStateCallback mBiometricStateCallback;
- @Mock
- private BiometricContext mBiometricContext;
-
- private LockoutResetDispatcher mLockoutResetDispatcher;
- private Fingerprint21 mFingerprint21;
-
- private static void waitForIdle() {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- }
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
- when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
- when(mContext.getResources()).thenReturn(mResources);
- when(mResources.getInteger(eq(R.integer.config_fingerprintMaxTemplatesPerUser)))
- .thenReturn(5);
-
- mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
-
- final int maxEnrollmentsPerUser = 1;
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- final boolean resetLockoutRequiresHardwareAuthToken = false;
- final FingerprintSensorPropertiesInternal sensorProps =
- new FingerprintSensorPropertiesInternal(SENSOR_ID,
- FingerprintSensorProperties.STRENGTH_WEAK, maxEnrollmentsPerUser,
- componentInfo, FingerprintSensorProperties.TYPE_UNKNOWN,
- resetLockoutRequiresHardwareAuthToken);
-
- mFingerprint21 = new TestableFingerprint21(mContext, mBiometricStateCallback,
- mAuthenticationStateListeners, sensorProps, mScheduler,
- new Handler(Looper.getMainLooper()), mLockoutResetDispatcher, mHalResultController,
- mBiometricContext);
- }
-
- @Test
- public void getAuthenticatorId_doesNotCrashWhenIdNotFound() {
- assertEquals(0, mFingerprint21.getAuthenticatorId(0 /* sensorId */, 111 /* userId */));
- waitForIdle();
- }
-
- @Test
- public void halServiceDied_resetsScheduler() {
- // It's difficult to test the linkToDeath --> serviceDied path, so let's just invoke
- // serviceDied directly.
- mFingerprint21.serviceDied(0 /* cookie */);
- waitForIdle();
- verify(mScheduler).reset();
- }
-
- private static class TestableFingerprint21 extends Fingerprint21 {
-
- TestableFingerprint21(@NonNull Context context,
- @NonNull BiometricStateCallback biometricStateCallback,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- @NonNull FingerprintSensorPropertiesInternal sensorProps,
- @NonNull BiometricScheduler scheduler, @NonNull Handler handler,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull HalResultController controller,
- @NonNull BiometricContext biometricContext) {
- super(context, biometricStateCallback, authenticationStateListeners, sensorProps,
- scheduler, handler, lockoutResetDispatcher, controller, biometricContext);
- }
-
- @Override
- synchronized IBiometricsFingerprint getDaemon() {
- return mock(IBiometricsFingerprint.class);
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
index 74e854e4..00c8ed1 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
@@ -53,7 +53,7 @@
private IInputDevicesChangedListener mDevicesChangedListener;
private final Map<String /* uniqueId */, Integer /* displayId */> mDisplayIdMapping =
new HashMap<>();
- private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociation =
+ private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociationByPort =
new HashMap<>();
InputManagerMockHelper(TestableLooper testableLooper,
@@ -79,10 +79,11 @@
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
doAnswer(inv -> mDevices.get(inv.getArgument(0)))
.when(mIInputManagerMock).getInputDevice(anyInt());
- doAnswer(inv -> mUniqueIdAssociation.put(inv.getArgument(0), inv.getArgument(1))).when(
- mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
- doAnswer(inv -> mUniqueIdAssociation.remove(inv.getArgument(0))).when(
- mIInputManagerMock).removeUniqueIdAssociation(anyString());
+ doAnswer(inv -> mUniqueIdAssociationByPort.put(inv.getArgument(0),
+ inv.getArgument(1))).when(mIInputManagerMock).addUniqueIdAssociationByPort(
+ anyString(), anyString());
+ doAnswer(inv -> mUniqueIdAssociationByPort.remove(inv.getArgument(0))).when(
+ mIInputManagerMock).removeUniqueIdAssociationByPort(anyString());
// Set a new instance of InputManager for testing that uses the IInputManager mock as the
// interface to the server.
@@ -112,7 +113,7 @@
.setDescriptor(phys)
.setExternal(true)
.setAssociatedDisplayId(
- mDisplayIdMapping.getOrDefault(mUniqueIdAssociation.get(phys),
+ mDisplayIdMapping.getOrDefault(mUniqueIdAssociationByPort.get(phys),
Display.INVALID_DISPLAY))
.build();
diff --git a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
index 861562d..305108e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
@@ -1,31 +1,11 @@
{
"presubmit": [
{
- "name": "FrameworksServicesTests",
- "options": [
- {
- "include-filter": "com.android.server.pm."
- },
- {
- "include-annotation": "android.platform.test.annotations.Presubmit"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "org.junit.Ignore"
- }
- ]
+ "name": "FrameworksServicesTests_pm_presubmit"
}
],
"postsubmit": [
{
- // Presubmit is intentional here while testing with SLO checker.
- // Tests are flaky, waiting to bypass.
- "name": "FrameworksServicesTests_pm_presubmit"
- },
- {
- // Leave postsubmit here when migrating
"name": "FrameworksServicesTests_pm_postsubmit"
}
]
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 515898a..e6cf0c38 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -50,6 +50,7 @@
"SettingsLib",
"libprotobuf-java-lite",
"platformprotoslite",
+ "platform-parametric-runner-lib",
],
libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index ad25d76..770712a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -26,14 +26,18 @@
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import static android.media.AudioAttributes.USAGE_UNKNOWN;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static com.android.server.notification.NotificationChannelExtractor.RESTRICT_AUDIO_ATTRIBUTES;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Flags;
@@ -43,12 +47,14 @@
import android.app.Person;
import android.media.AudioAttributes;
import android.net.Uri;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
+import com.android.internal.compat.IPlatformCompat;
import com.android.server.UiServiceTestCase;
import org.junit.Before;
@@ -60,6 +66,8 @@
public class NotificationChannelExtractorTest extends UiServiceTestCase {
@Mock RankingConfig mConfig;
+ @Mock
+ IPlatformCompat mPlatformCompat;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@@ -73,6 +81,7 @@
mExtractor = new NotificationChannelExtractor();
mExtractor.setConfig(mConfig);
mExtractor.initialize(mContext, null);
+ mExtractor.setCompatChangeLogger(mPlatformCompat);
}
private NotificationRecord getRecord(NotificationChannel channel, Notification n) {
@@ -82,7 +91,7 @@
}
@Test
- public void testExtractsUpdatedConversationChannel() {
+ public void testExtractsUpdatedConversationChannel() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
final Notification n = new Notification.Builder(getContext())
.setContentTitle("foo")
@@ -101,7 +110,7 @@
}
@Test
- public void testInvalidShortcutFlagEnabled_looksUpCorrectNonChannel() {
+ public void testInvalidShortcutFlagEnabled_looksUpCorrectNonChannel() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
final Notification n = new Notification.Builder(getContext())
.setContentTitle("foo")
@@ -122,7 +131,7 @@
}
@Test
- public void testInvalidShortcutFlagDisabled_looksUpCorrectChannel() {
+ public void testInvalidShortcutFlagDisabled_looksUpCorrectChannel() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
final Notification n = new Notification.Builder(getContext())
.setContentTitle("foo")
@@ -143,7 +152,7 @@
@Test
@EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_CALL)
- public void testAudioAttributes_callStyleCanUseCallUsage() {
+ public void testAudioAttributes_callStyleCanUseCallUsage() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
.setUsage(USAGE_NOTIFICATION_RINGTONE)
@@ -162,11 +171,12 @@
assertThat(mExtractor.process(r)).isNull();
assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION_RINGTONE);
assertThat(r.getChannel()).isEqualTo(channel);
+ verify(mPlatformCompat, never()).reportChangeByUid(anyLong(), anyInt());
}
@Test
@EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_CALL)
- public void testAudioAttributes_nonCallStyleCannotUseCallUsage() {
+ public void testAudioAttributes_nonCallStyleCannotUseCallUsage() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
.setUsage(USAGE_NOTIFICATION_RINGTONE)
@@ -180,13 +190,14 @@
assertThat(mExtractor.process(r)).isNull();
// instance updated
assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION);
+ verify(mPlatformCompat).reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, r.getUid());
// in-memory channel unchanged
assertThat(channel.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION_RINGTONE);
}
@Test
@EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_ALARM)
- public void testAudioAttributes_alarmCategoryCanUseAlarmUsage() {
+ public void testAudioAttributes_alarmCategoryCanUseAlarmUsage() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
.setUsage(USAGE_ALARM)
@@ -201,11 +212,12 @@
assertThat(mExtractor.process(r)).isNull();
assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_ALARM);
assertThat(r.getChannel()).isEqualTo(channel);
+ verify(mPlatformCompat, never()).reportChangeByUid(anyLong(), anyInt());
}
@Test
@EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_ALARM)
- public void testAudioAttributes_nonAlarmCategoryCannotUseAlarmUsage() {
+ public void testAudioAttributes_nonAlarmCategoryCannotUseAlarmUsage() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
.setUsage(USAGE_ALARM)
@@ -219,13 +231,14 @@
assertThat(mExtractor.process(r)).isNull();
// instance updated
assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION);
+ verify(mPlatformCompat).reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, r.getUid());
// in-memory channel unchanged
assertThat(channel.getAudioAttributes().getUsage()).isEqualTo(USAGE_ALARM);
}
@Test
@EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_MEDIA)
- public void testAudioAttributes_noMediaUsage() {
+ public void testAudioAttributes_noMediaUsage() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
.setUsage(USAGE_MEDIA)
@@ -239,13 +252,14 @@
assertThat(mExtractor.process(r)).isNull();
// instance updated
assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION);
+ verify(mPlatformCompat).reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, r.getUid());
// in-memory channel unchanged
assertThat(channel.getAudioAttributes().getUsage()).isEqualTo(USAGE_MEDIA);
}
@Test
@EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_MEDIA)
- public void testAudioAttributes_noUnknownUsage() {
+ public void testAudioAttributes_noUnknownUsage() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
.setUsage(USAGE_UNKNOWN)
@@ -259,6 +273,7 @@
assertThat(mExtractor.process(r)).isNull();
// instance updated
assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION);
+ verify(mPlatformCompat).reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, r.getUid());
// in-memory channel unchanged
assertThat(channel.getAudioAttributes().getUsage()).isEqualTo(USAGE_UNKNOWN);
}
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 4a61d32..011f2e3 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -106,22 +106,23 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
-
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
+import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION;
import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
import static com.android.server.notification.NotificationManagerService.NOTIFICATION_TTL;
+import static com.android.server.notification.NotificationManagerService.TAG;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
-
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -130,7 +131,6 @@
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
-
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.isNull;
@@ -138,10 +138,24 @@
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
-
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+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.Manifest;
import android.annotation.Nullable;
@@ -168,6 +182,8 @@
import android.app.RemoteInputHistoryItem;
import android.app.StatsManager;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.job.JobScheduler;
+import android.app.role.RoleManager;
import android.app.usage.UsageStatsManagerInternal;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
@@ -184,9 +200,11 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
+import android.content.pm.ModuleInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
+import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.UserInfo;
@@ -217,6 +235,7 @@
import android.permission.PermissionManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.rule.LimitDevicesRule;
import android.provider.DeviceConfig;
@@ -235,7 +254,8 @@
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
import android.telecom.TelecomManager;
-import android.testing.AndroidTestingRunner;
+import android.testing.TestWithLooperRule;
+import android.testing.TestableContentResolver;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.TestablePermissions;
@@ -245,13 +265,13 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.Log;
import android.util.Pair;
import android.util.Xml;
+import android.view.accessibility.AccessibilityManager;
import android.widget.RemoteViews;
-
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-
import com.android.internal.R;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.config.sysui.TestableFlagResolver;
@@ -259,6 +279,7 @@
import com.android.internal.logging.InstanceIdSequenceFake;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.internal.widget.LockPatternUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.DeviceIdleInternal;
@@ -282,13 +303,10 @@
import com.android.server.utils.quota.MultiRateLimiter;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
-
import com.google.android.collect.Lists;
import com.google.common.collect.ImmutableList;
-
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -306,6 +324,8 @@
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -322,9 +342,9 @@
import java.util.function.Consumer;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
+@RunWith(ParameterizedAndroidJunit4.class)
@RunWithLooper
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
public class NotificationManagerServiceTest extends UiServiceTestCase {
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
private static final String TEST_PACKAGE = "The.name.is.Package.Test.Package";
@@ -369,6 +389,8 @@
@Mock
private PermissionHelper mPermissionHelper;
private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake();
+ @Rule(order = Integer.MAX_VALUE)
+ public TestWithLooperRule mlooperRule = new TestWithLooperRule();
private TestableLooper mTestableLooper;
@Mock
private RankingHelper mRankingHelper;
@@ -415,8 +437,8 @@
private final TestPostNotificationTrackerFactory mPostNotificationTrackerFactory =
new TestPostNotificationTrackerFactory();
- @Mock
- IIntentSender pi1;
+ private PendingIntent mActivityIntent;
+ private PendingIntent mActivityIntentImmutable;
private static final int MAX_POST_DELAY = 1000;
@@ -429,6 +451,9 @@
private static final String VALID_CONVO_SHORTCUT_ID = "shortcut";
private static final String SEARCH_SELECTOR_PKG = "searchSelector";
+ private static final String ADSERVICES_MODULE_PKG = "com.android.adservices";
+ private static final String ADSERVICES_APK_PKG = "com.android.adservices.api";
+
@Mock
private NotificationListeners mListeners;
@Mock
@@ -465,6 +490,7 @@
StatsManager mStatsManager;
@Mock
AlarmManager mAlarmManager;
+ @Mock JobScheduler mJobScheduler;
@Mock
MultiRateLimiter mToastRateLimiter;
BroadcastReceiver mPackageIntentReceiver;
@@ -508,6 +534,16 @@
}
}
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_ALL_NOTIFS_NEED_TTL);
+ }
+
+ public NotificationManagerServiceTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
// Shell permisssions will override permissions of our app, so add all necessary permissions
@@ -540,12 +576,22 @@
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
LocalServices.removeServiceForTest(PermissionPolicyInternal.class);
LocalServices.addService(PermissionPolicyInternal.class, mPermissionPolicyInternal);
+ LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
+ LocalServices.addService(ShortcutServiceInternal.class, mShortcutServiceInternal);
mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
mContext.addMockSystemService(NotificationManager.class, mMockNm);
+ mContext.addMockSystemService(RoleManager.class, mock(RoleManager.class));
+ mContext.addMockSystemService(Context.LAUNCHER_APPS_SERVICE, mLauncherApps);
+ mContext.addMockSystemService(Context.USER_SERVICE, mUm);
+ mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE,
+ mock(AccessibilityManager.class));
doNothing().when(mContext).sendBroadcast(any(), anyString());
doNothing().when(mContext).sendBroadcastAsUser(any(), any());
doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
+ TestableContentResolver cr = mock(TestableContentResolver.class);
+ when(mContext.getContentResolver()).thenReturn(cr);
+ doNothing().when(cr).registerContentObserver(any(), anyBoolean(), any(), anyInt());
setDpmAppOppsExemptFromDismissal(false);
@@ -648,11 +694,16 @@
});
// TODO (b/291907312): remove feature flag
- // NOTE: Prefer using the @EnableFlag annotation where possible. Do not add any android.app
+ // NOTE: Prefer using the @EnableFlags annotation where possible. Do not add any android.app
// flags here.
mSetFlagsRule.disableFlags(
Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE);
+ mActivityIntent = spy(PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mPkg), PendingIntent.FLAG_MUTABLE));
+ mActivityIntentImmutable = spy(PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mPkg), FLAG_IMMUTABLE));
+
initNMS();
}
@@ -689,10 +740,15 @@
mPowerManager, mPostNotificationTrackerFactory);
mService.setAttentionHelper(mAttentionHelper);
+ mService.setLockPatternUtils(mock(LockPatternUtils.class));
// Return first true for RoleObserver main-thread check
when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
-
+ ModuleInfo moduleInfo = new ModuleInfo();
+ moduleInfo.setApexModuleName(ADSERVICES_MODULE_PKG);
+ moduleInfo.setApkInApexPackageNames(List.of(ADSERVICES_APK_PKG));
+ when(mPackageManagerClient.getInstalledModules(anyInt()))
+ .thenReturn(List.of(moduleInfo));
if (upToBootPhase >= SystemService.PHASE_SYSTEM_SERVICES_READY) {
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
}
@@ -749,7 +805,10 @@
}
assertNotNull("package intent receiver should exist", mPackageIntentReceiver);
assertNotNull("User-switch receiver should exist", mUserSwitchIntentReceiver);
- assertNotNull("Notification timeout receiver should exist", mNotificationTimeoutReceiver);
+ if (!Flags.allNotifsNeedTtl()) {
+ assertNotNull("Notification timeout receiver should exist",
+ mNotificationTimeoutReceiver);
+ }
// Pretend the shortcut exists
List<ShortcutInfo> shortcutInfos = new ArrayList<>();
@@ -834,9 +893,17 @@
if (mFile != null) mFile.delete();
clearDeviceConfig();
+ if (mActivityIntent != null) {
+ mActivityIntent.cancel();
+ }
+
+ mService.clearNotifications();
+ TestableLooper.get(this).processAllMessages();
+
try {
mService.onDestroy();
} catch (IllegalStateException | IllegalArgumentException e) {
+ Log.e(TAG, "failed to destroy", e);
// can throw if a broadcast receiver was never registered
}
@@ -846,6 +913,11 @@
// could cause issues, for example, messages that remove/cancel shown toasts (this causes
// problematic interactions with mocks when they're no longer working as expected).
mWorkerHandler.removeCallbacksAndMessages(null);
+
+ if (TestableLooper.get(this) != null) {
+ // Must remove static reference to this test object to prevent leak (b/261039202)
+ TestableLooper.remove(this);
+ }
}
private void simulatePackageSuspendBroadcast(boolean suspend, String pkg,
@@ -1005,7 +1077,9 @@
Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
- .addAction(new Notification.Action.Builder(null, "test", null).build());
+ .addAction(new Notification.Action.Builder(null, "test", mActivityIntent).build())
+ .addAction(new Notification.Action.Builder(
+ null, "test", mActivityIntentImmutable).build());
if (extender != null) {
nb.extend(extender);
}
@@ -1045,18 +1119,20 @@
private NotificationRecord generateMessageBubbleNotifRecord(NotificationChannel channel,
String tag) {
- return generateMessageBubbleNotifRecord(true, channel, 1, tag, null, false);
+ return generateMessageBubbleNotifRecord(true, channel, 1, tag, null, false, true);
}
private NotificationRecord generateMessageBubbleNotifRecord(boolean addMetadata,
- NotificationChannel channel, int id, String tag, String groupKey, boolean isSummary) {
+ NotificationChannel channel, int id, String tag, String groupKey, boolean isSummary,
+ boolean mutable) {
if (channel == null) {
channel = mTestNotificationChannel;
}
if (tag == null) {
tag = "tag";
}
- Notification.Builder nb = getMessageStyleNotifBuilder(addMetadata, groupKey, isSummary);
+ Notification.Builder nb = getMessageStyleNotifBuilder(addMetadata, groupKey,
+ isSummary, mutable);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id,
tag, mUid, 0,
nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -1129,18 +1205,15 @@
}
private Notification.Builder getMessageStyleNotifBuilder(boolean addBubbleMetadata,
- String groupKey, boolean isSummary) {
+ String groupKey, boolean isSummary, boolean mutable) {
// Give it a person
Person person = new Person.Builder()
.setName("bubblebot")
.build();
RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0,
- new Intent().setPackage(mContext.getPackageName()),
- PendingIntent.FLAG_MUTABLE);
Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
+ mutable ? mActivityIntent : mActivityIntentImmutable).addRemoteInput(remoteInput)
.build();
// Make it messaging style
Notification.Builder nb = new Notification.Builder(mContext,
@@ -1167,17 +1240,14 @@
}
private Notification.BubbleMetadata getBubbleMetadata() {
- PendingIntent pendingIntent = mock(PendingIntent.class);
- Intent intent = mock(Intent.class);
- when(pendingIntent.getIntent()).thenReturn(intent);
- when(pendingIntent.getTarget()).thenReturn(pi1);
-
ActivityInfo info = new ActivityInfo();
info.resizeMode = RESIZE_MODE_RESIZEABLE;
- when(intent.resolveActivityInfo(any(), anyInt())).thenReturn(info);
+ ResolveInfo ri = new ResolveInfo();
+ ri.activityInfo = info;
+ when(mPackageManagerClient.resolveActivity(any(), anyInt())).thenReturn(ri);
return new Notification.BubbleMetadata.Builder(
- pendingIntent,
+ mActivityIntent,
Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon))
.build();
}
@@ -1189,7 +1259,8 @@
// Notification that has bubble metadata
NotificationRecord nrBubble = generateMessageBubbleNotifRecord(true /* addMetadata */,
- mTestNotificationChannel, 1 /* id */, "tag", groupKey, false /* isSummary */);
+ mTestNotificationChannel, 1 /* id */, "tag", groupKey, false /* isSummary */,
+ true);
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, nrBubble.getSbn().getTag(),
nrBubble.getSbn().getId(), nrBubble.getSbn().getNotification(),
@@ -1203,7 +1274,8 @@
// Notification without bubble metadata
NotificationRecord nrPlain = generateMessageBubbleNotifRecord(false /* addMetadata */,
- mTestNotificationChannel, 2 /* id */, "tag", groupKey, false /* isSummary */);
+ mTestNotificationChannel, 2 /* id */, "tag", groupKey, false /* isSummary */,
+ true);
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, nrPlain.getSbn().getTag(),
nrPlain.getSbn().getId(), nrPlain.getSbn().getNotification(),
@@ -1215,7 +1287,8 @@
// Summary notification for both of those
NotificationRecord nrSummary = generateMessageBubbleNotifRecord(false /* addMetadata */,
- mTestNotificationChannel, 3 /* id */, "tag", groupKey, true /* isSummary */);
+ mTestNotificationChannel, 3 /* id */, "tag", groupKey, true /* isSummary */,
+ true);
if (summaryAutoCancel) {
nrSummary.getNotification().flags |= FLAG_AUTO_CANCEL;
@@ -1232,6 +1305,7 @@
}
@Test
+ @DisableFlags(FLAG_ALL_NOTIFS_NEED_TTL)
public void testLimitTimeOutBroadcast() {
NotificationChannel channel = new NotificationChannel("id", "name",
NotificationManager.IMPORTANCE_HIGH);
@@ -2501,8 +2575,8 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testCancelWithTagDoesNotCancelLifetimeExtended() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final NotificationRecord notif = generateNotificationRecord(null);
notif.getSbn().getNotification().flags =
Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
@@ -2529,19 +2603,11 @@
assertThat(captor.getValue().getNotification().flags
& FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
-
- mSetFlagsRule.disableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
- mBinderService.cancelNotificationWithTag(mPkg, mPkg, sbn.getTag(), sbn.getId(),
- sbn.getUserId());
- waitForIdle();
-
- assertThat(mBinderService.getActiveNotifications(sbn.getPackageName()).length).isEqualTo(0);
- assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testCancelAllDoesNotCancelLifetimeExtended() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
// Adds a lifetime extended notification.
final NotificationRecord notif = generateNotificationRecord(mTestNotificationChannel, 1,
null, false);
@@ -2978,9 +3044,9 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testCancelNotificationsFromListener_clearAll_NoClearLifetimeExt()
throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final NotificationRecord notif = generateNotificationRecord(
mTestNotificationChannel, 1, null, false);
notif.getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
@@ -3211,9 +3277,9 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testCancelNotificationsFromListener_byKey_NoClearLifetimeExt()
throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final NotificationRecord notif = generateNotificationRecord(
mTestNotificationChannel, 3, null, false);
notif.getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
@@ -5695,12 +5761,16 @@
verify(mZenModeHelper).updateZenRulesOnLocaleChange();
}
- private void simulateNotificationTimeoutBroadcast(String notificationKey) {
- final Bundle extras = new Bundle();
- extras.putString(EXTRA_KEY, notificationKey);
- final Intent intent = new Intent(ACTION_NOTIFICATION_TIMEOUT);
- intent.putExtras(extras);
- mNotificationTimeoutReceiver.onReceive(getContext(), intent);
+ private void simulateNotificationTimeout(String notificationKey) {
+ if (Flags.allNotifsNeedTtl()) {
+ mService.mNotificationManagerPrivate.timeoutNotification(notificationKey);
+ } else {
+ final Bundle extras = new Bundle();
+ extras.putString(EXTRA_KEY, notificationKey);
+ final Intent intent = new Intent(ACTION_NOTIFICATION_TIMEOUT);
+ intent.putExtras(extras);
+ mNotificationTimeoutReceiver.onReceive(getContext(), intent);
+ }
}
@Test
@@ -5709,7 +5779,7 @@
mTestNotificationChannel, 1, null, false);
mService.addNotification(notif);
- simulateNotificationTimeoutBroadcast(notif.getKey());
+ simulateNotificationTimeout(notif.getKey());
waitForIdle();
// Check that the notification was cancelled.
@@ -5725,7 +5795,7 @@
notif.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
mService.addNotification(notif);
- simulateNotificationTimeoutBroadcast(notif.getKey());
+ simulateNotificationTimeout(notif.getKey());
waitForIdle();
// Check that the notification was not cancelled.
@@ -5741,7 +5811,7 @@
notif.getSbn().getNotification().flags = Notification.FLAG_USER_INITIATED_JOB;
mService.addNotification(notif);
- simulateNotificationTimeoutBroadcast(notif.getKey());
+ simulateNotificationTimeout(notif.getKey());
waitForIdle();
// Check that the notification was not cancelled.
@@ -5751,15 +5821,15 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testTimeout_NoCancelLifetimeExtensionNotification() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
// Create a notification with FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY
final NotificationRecord notif = generateNotificationRecord(null);
notif.getSbn().getNotification().flags =
Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
mService.addNotification(notif);
- simulateNotificationTimeoutBroadcast(notif.getKey());
+ simulateNotificationTimeout(notif.getKey());
waitForIdle();
// Check that the notification was not cancelled.
@@ -5839,8 +5909,8 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testStats_DirectReplyLifetimeExtendedPostsUpdate() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
mService.addNotification(r);
@@ -6662,6 +6732,7 @@
verify(visitor, times(1)).accept(eq(personIcon.getUri()));
verify(visitor, times(1)).accept(eq(verificationIcon.getUri()));
verify(visitor, times(1)).accept(eq(hangUpUri));
+ hangUpIntent.cancel();
}
@Test
@@ -6691,6 +6762,8 @@
verify(visitor, times(1)).accept(eq(verificationIcon.getUri()));
verify(visitor, times(1)).accept(eq(answerIntent.getIntent().getData()));
verify(visitor, times(1)).accept(eq(declineUri));
+ answerIntent.cancel();
+ declineIntent.cancel();
}
@Test
@@ -6767,6 +6840,9 @@
verify(visitor, times(1)).accept(eq(actionIntentUri));
verify(visitor).accept(eq(wearActionIcon.getUri()));
verify(visitor, times(1)).accept(eq(wearActionIntentUri));
+ displayIntent.cancel();
+ actionIntent.cancel();
+ wearActionIntent.cancel();
}
@Test
@@ -6788,6 +6864,8 @@
verify(visitor, times(1)).accept(eq(contentIntentUri));
verify(visitor, times(1)).accept(eq(deleteIntentUri));
+ contentIntent.cancel();
+ deleteIntent.cancel();
}
@Test
@@ -6813,6 +6891,8 @@
verify(visitor, times(1)).accept(eq(readPendingIntentUri));
verify(visitor, times(1)).accept(eq(replyPendingIntentUri));
+ readPendingIntent.cancel();
+ replyPendingIntent.cancel();
}
@Test
@@ -8587,8 +8667,8 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testOnNotificationSmartReplySent() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final int replyIndex = 2;
final String reply = "Hello";
final boolean modifiedBeforeSending = true;
@@ -8613,8 +8693,8 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testStats_SmartReplyAlreadyLifetimeExtendedPostsUpdate() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final int replyIndex = 2;
final String reply = "Hello";
final boolean modifiedBeforeSending = true;
@@ -8647,8 +8727,7 @@
public void testOnNotificationActionClick() {
final int actionIndex = 2;
final Notification.Action action =
- new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
- mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
+ new Notification.Action.Builder(null, "text", mActivityIntent).build();
final boolean generatedByAssistant = false;
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
@@ -8669,9 +8748,8 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testActionClickLifetimeExtendedCancel() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
-
final Notification.Action action =
new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
@@ -8797,8 +8875,7 @@
public void testOnAssistantNotificationActionClick() {
final int actionIndex = 1;
final Notification.Action action =
- new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
- mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
+ new Notification.Action.Builder(null, "text", mActivityIntent).build();
final boolean generatedByAssistant = true;
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
@@ -9313,7 +9390,7 @@
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
- Notification.Builder nb = getMessageStyleNotifBuilder(true, null, false);
+ Notification.Builder nb = getMessageStyleNotifBuilder(true, null, false, true);
nb.setShortcutId(null);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
null, mUid, 0,
@@ -9357,7 +9434,7 @@
// Messaging notif WITHOUT bubble metadata
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addBubbleMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
"testFlagBubbleNotifs_noFlag_notBubble", mUid, 0,
@@ -10615,7 +10692,7 @@
Notification.BubbleMetadata metadata =
new Notification.BubbleMetadata.Builder(VALID_CONVO_SHORTCUT_ID).build();
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
nb.setBubbleMetadata(metadata);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -10675,7 +10752,7 @@
Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
shortcutId).build();
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(shortcutId);
nb.setBubbleMetadata(metadata);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11068,7 +11145,7 @@
//Create notification record
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
nb.setChannelId(originalChannel.getId());
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11104,7 +11181,7 @@
//Create notification record
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
nb.setChannelId(originalChannel.getId());
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11148,7 +11225,7 @@
//Create notification record
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
nb.setChannelId(originalChannel.getId());
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11193,7 +11270,7 @@
//Create notification record without a shortcutId
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(null);
nb.setChannelId(originalChannel.getId());
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11328,7 +11405,7 @@
@Test
public void testRecordMessages_invalidMsg() throws RemoteException {
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(null);
StatusBarNotification sbn = new StatusBarNotification(PKG_P, PKG_P, 1,
"testRecordMessages_invalidMsg", mUid, 0, nb.build(),
@@ -11369,7 +11446,7 @@
@Test
public void testRecordMessages_validMsg() throws RemoteException {
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(null);
StatusBarNotification sbn = new StatusBarNotification(PKG_P, PKG_P, 1,
"testRecordMessages_validMsg", mUid, 0, nb.build(),
@@ -11405,7 +11482,7 @@
waitForIdle();
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(null);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
"testRecordMessages_invalidMsg_afterValidMsg_2", mUid, 0, nb.build(),
@@ -11625,10 +11702,9 @@
@Test
public void testImmutableBubbleIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(pi1))
- .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
NotificationRecord r = generateMessageBubbleNotifRecord(true,
- mTestNotificationChannel, 7, "testImmutableBubbleIntent", null, false);
+ mTestNotificationChannel, 7, "testImmutableBubbleIntent", null, false, false);
try {
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11642,10 +11718,8 @@
@Test
public void testMutableBubbleIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(pi1))
- .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
NotificationRecord r = generateMessageBubbleNotifRecord(true,
- mTestNotificationChannel, 7, "testMutableBubbleIntent", null, false);
+ mTestNotificationChannel, 7, "testMutableBubbleIntent", null, false, true);
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11658,10 +11732,10 @@
@Test
public void testImmutableDirectReplyActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
NotificationRecord r = generateMessageBubbleNotifRecord(false,
- mTestNotificationChannel, 7, "testImmutableDirectReplyActionIntent", null, false);
+ mTestNotificationChannel, 7, "testImmutableDirectReplyActionIntent", null, false,
+ false);
try {
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11675,10 +11749,9 @@
@Test
public void testMutableDirectReplyActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
NotificationRecord r = generateMessageBubbleNotifRecord(false,
- mTestNotificationChannel, 7, "testMutableDirectReplyActionIntent", null, false);
+ mTestNotificationChannel, 7, "testMutableDirectReplyActionIntent", null, false,
+ true);
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11690,18 +11763,15 @@
@Test
public void testImmutableDirectReplyContextualActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
ArrayList<Notification.Action> extraAction = new ArrayList<>();
RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(),
- PendingIntent.FLAG_IMMUTABLE);
Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
+ mActivityIntentImmutable).addRemoteInput(remoteInput)
.build();
extraAction.add(replyAction);
Bundle signals = new Bundle();
@@ -11722,18 +11792,13 @@
@Test
public void testMutableDirectReplyContextualActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
ArrayList<Notification.Action> extraAction = new ArrayList<>();
RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0,
- new Intent().setPackage(mContext.getPackageName()),
- PendingIntent.FLAG_MUTABLE);
Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
+ mActivityIntent).addRemoteInput(remoteInput)
.build();
extraAction.add(replyAction);
Bundle signals = new Bundle();
@@ -11749,10 +11814,8 @@
@Test
public void testImmutableActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
-
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11764,12 +11827,11 @@
@Test
public void testImmutableContextualActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
ArrayList<Notification.Action> extraAction = new ArrayList<>();
- extraAction.add(new Notification.Action(0, "hello", null));
+ extraAction.add(new Notification.Action(0, "hello", mActivityIntentImmutable));
Bundle signals = new Bundle();
signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction);
Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "",
@@ -12121,8 +12183,6 @@
@Test
public void testCallNotificationsBypassBlock() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
Notification.Builder nb = new Notification.Builder(
@@ -12145,8 +12205,8 @@
.setName("caller")
.build();
nb.setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)));
- nb.setFullScreenIntent(mock(PendingIntent.class), true);
+ person, mActivityIntent));
+ nb.setFullScreenIntent(mActivityIntent, true);
sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
@@ -12214,8 +12274,8 @@
mService.clearNotifications();
reset(mUsageStats);
Person person = new Person.Builder().setName("caller").build();
- nb.setStyle(Notification.CallStyle.forOngoingCall(person, mock(PendingIntent.class)));
- nb.setFullScreenIntent(mock(PendingIntent.class), true);
+ nb.setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent));
+ nb.setFullScreenIntent(mActivityIntent, true);
sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0, nb.build(),
UserHandle.getUserHandleForUid(mUid), null, 0);
r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
@@ -12709,7 +12769,7 @@
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
mService.maybeShowInitialReviewPermissionsNotification();
- verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+ verify(mMockNm, times(1)).notify(eq(TAG),
eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
any(Notification.class));
}
@@ -12744,7 +12804,7 @@
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
mService.maybeShowInitialReviewPermissionsNotification();
- verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+ verify(mMockNm, times(1)).notify(eq(TAG),
eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
any(Notification.class));
}
@@ -12760,7 +12820,7 @@
mInternalService.sendReviewPermissionsNotification();
// Notification should be sent
- verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+ verify(mMockNm, times(1)).notify(eq(TAG),
eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
any(Notification.class));
@@ -12792,7 +12852,7 @@
.thenReturn(permissionState);
Notification n = new Notification.Builder(mContext, "test")
- .setFullScreenIntent(mock(PendingIntent.class), true)
+ .setFullScreenIntent(mActivityIntent, true)
.build();
mService.fixNotification(n, mPkg, "tag", 9, mUserId, mUid, NOT_FOREGROUND_SERVICE, true);
@@ -12860,7 +12920,7 @@
Person person = new Person.Builder().setName("caller").build();
Notification n = new Notification.Builder(mContext, "test")
.setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -12881,7 +12941,7 @@
Notification n = new Notification.Builder(mContext, "test")
.setFlag(FLAG_FOREGROUND_SERVICE, true)
.setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13040,8 +13100,7 @@
Notification n = new Notification.Builder(mContext, "test")
// Without FLAG_FOREGROUND_SERVICE.
//.setFlag(FLAG_FOREGROUND_SERVICE, true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13057,8 +13116,7 @@
Person person = new Person.Builder().setName("caller").build();
Notification n = new Notification.Builder(mContext, "test")
.setFlag(FLAG_USER_INITIATED_JOB, true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13072,9 +13130,8 @@
public void checkCallStyleNotification_allowedForFsiAllowed() throws Exception {
Person person = new Person.Builder().setName("caller").build();
Notification n = new Notification.Builder(mContext, "test")
- .setFullScreenIntent(mock(PendingIntent.class), true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setFullScreenIntent(mActivityIntent, true)
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13089,8 +13146,7 @@
Person person = new Person.Builder().setName("caller").build();
Notification n = new Notification.Builder(mContext, "test")
.setFlag(Notification.FLAG_FSI_REQUESTED_BUT_DENIED, true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13170,6 +13226,33 @@
}
@Test
+ public void fixSystemNotification_defaultAdservices_withOnGoingFlag_nondismissible()
+ throws Exception {
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = ADSERVICES_APK_PKG;
+ ai.uid = mUid;
+ ai.flags |= ApplicationInfo.FLAG_SYSTEM;
+
+ when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(ai);
+ when(mAppOpsManager.checkOpNoThrow(
+ AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid,
+ ai.packageName)).thenReturn(AppOpsManager.MODE_IGNORED);
+ // Given: a notification from an app on the system partition has the flag
+ // FLAG_ONGOING_EVENT set
+ Notification n = new Notification.Builder(mContext, "test")
+ .setOngoing(true)
+ .build();
+
+ // When: fix the notification with NotificationManagerService
+ mService.fixNotification(n, ADSERVICES_APK_PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE,
+ true);
+
+ // Then: the notification's flag FLAG_NO_DISMISS should be set
+ assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
+ }
+
+ @Test
public void fixCallNotification_withOnGoingFlag_shouldNotBeNonDismissible()
throws Exception {
// Given: a call notification has the flag FLAG_ONGOING_EVENT set
@@ -13178,8 +13261,7 @@
.build();
Notification n = new Notification.Builder(mContext, "test")
.setOngoing(true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.build();
// When: fix the notification with NotificationManagerService
@@ -14338,6 +14420,9 @@
eq(REASON_NOTIFICATION_SERVICE), any());
verify(mAmi, times(3)).setPendingIntentAllowBgActivityStarts(any(),
any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
+ contentIntent.cancel();
+ actionIntent2.cancel();
+ actionIntent1.cancel();
}
@Test
@@ -14366,6 +14451,10 @@
eq(REASON_NOTIFICATION_SERVICE), any());
verify(mAmi, times(4)).setPendingIntentAllowBgActivityStarts(any(),
any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
+ contentIntent.cancel();
+ publicContentIntent.cancel();
+ actionIntent.cancel();
+ publicActionIntent.cancel();
}
@Test
@@ -14891,8 +14980,8 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testFixNotification_clearsLifetimeExtendedFlag() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
Notification n = new Notification.Builder(mContext, "test")
.setFlag(FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true)
.build();
@@ -15321,7 +15410,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+ @EnableFlags(FLAG_ALL_NOTIFS_NEED_TTL)
public void testFixNotification_missingTtl() throws Exception {
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -15333,7 +15422,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+ @EnableFlags(FLAG_ALL_NOTIFS_NEED_TTL)
public void testFixNotification_doesNotOverwriteTtl() throws Exception {
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -15351,8 +15440,7 @@
Notification.Builder nb = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
.setFlag(FLAG_USER_INITIATED_JOB, true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.setSmallIcon(android.R.drawable.sym_def_app_icon);
StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1,
testName, mUid, 0, nb.build(), userHandle, null, 0);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index aeeca2ae..9a58594 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -46,10 +46,13 @@
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
+import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA;
import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT;
@@ -110,6 +113,9 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.permission.PermissionManager;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
@@ -140,6 +146,9 @@
import com.android.server.UiServiceTestCase;
import com.android.server.notification.PermissionHelper.PackagePermission;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -148,6 +157,7 @@
import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -160,6 +170,8 @@
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.time.Clock;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -171,7 +183,7 @@
import java.util.concurrent.ThreadLocalRandom;
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
public class PreferencesHelperTest extends UiServiceTestCase {
private static final int UID_HEADLESS = 1000000;
private static final UserHandle USER = UserHandle.of(0);
@@ -212,6 +224,22 @@
private AudioAttributes mAudioAttributes;
private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
+ @Mock
+ Clock mClock;
+
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_PERSIST_INCOMPLETE_RESTORE_DATA);
+ }
+
+ public PreferencesHelperTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -326,13 +354,14 @@
currentProfileIds.add(UserHandle.getUserId(UID_HEADLESS));
}
when(mUserProfiles.getCurrentProfileIds()).thenReturn(currentProfileIds);
+ when(mClock.millis()).thenReturn(System.currentTimeMillis());
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false);
+ false, mClock);
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false);
+ false, mClock);
resetZenModeHelper();
mAudioAttributes = new AudioAttributes.Builder()
@@ -680,7 +709,7 @@
public void testReadXml_oldXml_migrates() throws Exception {
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- /* showReviewPermissionsNotification= */ true);
+ /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"2\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1
@@ -816,7 +845,7 @@
public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- /* showReviewPermissionsNotification= */ true);
+ /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -875,7 +904,7 @@
public void testReadXml_newXml_permissionNotificationOff() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- /* showReviewPermissionsNotification= */ false);
+ /* showReviewPermissionsNotification= */ false, mClock);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -934,7 +963,7 @@
public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- /* showReviewPermissionsNotification= */ true);
+ /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"4\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -1010,7 +1039,8 @@
when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(1234);
final ApplicationInfo app = new ApplicationInfo();
app.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
- when(mPm.getApplicationInfoAsUser(eq("something"), anyInt(), anyInt())).thenReturn(app);
+ when(mPm.getApplicationInfoAsUser(
+ eq("something"), anyInt(), eq(USER_SYSTEM))).thenReturn(app);
mXmlHelper.onPackagesChanged(false, 0, new String[] {"something"}, new int[] {1234});
@@ -1452,6 +1482,149 @@
assertTrue(actualChannel.isSoundRestored());
}
+ @Test
+ @EnableFlags(Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA)
+ public void testRestoreXml_delayedRestore() throws Exception {
+ // simulate package not installed
+ when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UNKNOWN_UID);
+ when(mPm.getApplicationInfoAsUser(eq(PKG_R), anyInt(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException());
+ when(mClock.millis()).thenReturn(System.currentTimeMillis());
+
+ String id = "id";
+ String xml = "<ranking version=\"1\">\n"
+ + "<package name=\"" + PKG_R + "\" show_badge=\"true\">\n"
+ + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
+ + "show_badge=\"true\" />\n"
+ + "</package>\n"
+ + "</ranking>\n";
+
+ loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM);
+
+ // settings are not available with real uid because pkg is not installed
+ assertThat(mXmlHelper.getNotificationChannel(PKG_R, UID_P, id, false)).isNull();
+ // but the settings are in memory with unknown_uid
+ assertThat(mXmlHelper.getNotificationChannel(PKG_R, UNKNOWN_UID, id, false)).isNotNull();
+
+ // package is "installed"
+ when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UID_P);
+
+ // Trigger 2nd restore pass
+ mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R},
+ new int[]{UID_P});
+
+ NotificationChannel channel = mXmlHelper.getNotificationChannel(PKG_R, UID_P, id,
+ false);
+ assertThat(channel.getImportance()).isEqualTo(2);
+ assertThat(channel.canShowBadge()).isTrue();
+ assertThat(channel.canBypassDnd()).isFalse();
+
+ // removed from 'pending install' set
+ assertThat(mXmlHelper.getNotificationChannel(PKG_R, UNKNOWN_UID, id,false)).isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA)
+ public void testRestoreXml_delayedRestore_afterReboot() throws Exception {
+ // load restore data
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair<>(UID_R, PKG_R), new Pair<>(true, false));
+ when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
+ .thenReturn(appPermissions);
+
+ // simulate package not installed
+ when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UNKNOWN_UID);
+ when(mPm.getApplicationInfoAsUser(eq(PKG_R), anyInt(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException());
+ when(mClock.millis()).thenReturn(System.currentTimeMillis());
+
+ String id = "id";
+ String xml = "<ranking version=\"1\">\n"
+ + "<package name=\"" + PKG_R + "\" show_badge=\"true\">\n"
+ + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
+ + "show_badge=\"true\" />\n"
+ + "</package>\n"
+ + "</ranking>\n";
+
+ loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM);
+
+ // simulate write to disk
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mXmlHelper.writeXml(serializer, false, USER_SYSTEM);
+ serializer.endDocument();
+ serializer.flush();
+
+ // simulate load after reboot
+ mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
+ false, mClock);
+ loadByteArrayXml(baos.toByteArray(), false, USER_SYSTEM);
+
+ // Trigger 2nd restore pass
+ when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UID_P);
+ mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R},
+ new int[]{UID_P});
+
+ NotificationChannel channel = mXmlHelper.getNotificationChannel(PKG_R, UID_P, id,
+ false);
+ assertThat(channel.getImportance()).isEqualTo(2);
+ assertThat(channel.canShowBadge()).isTrue();
+ assertThat(channel.canBypassDnd()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA)
+ public void testRestoreXml_delayedRestore_packageMissingAfterTwoDays() throws Exception {
+ // load restore data
+ ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
+ appPermissions.put(new Pair<>(UID_R, PKG_R), new Pair<>(true, false));
+ when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
+ .thenReturn(appPermissions);
+
+ // simulate package not installed
+ when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UNKNOWN_UID);
+ when(mPm.getApplicationInfoAsUser(eq(PKG_R), anyInt(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException());
+
+ String id = "id";
+ String xml = "<ranking version=\"1\">\n"
+ + "<package name=\"" + PKG_R + "\" show_badge=\"true\">\n"
+ + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
+ + "show_badge=\"true\" />\n"
+ + "</package>\n"
+ + "</ranking>\n";
+
+ loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM);
+
+ // simulate write to disk
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mXmlHelper.writeXml(serializer, false, USER_SYSTEM);
+ serializer.endDocument();
+ serializer.flush();
+
+ // advance time by 2 days
+ when(mClock.millis()).thenReturn(
+ Duration.ofDays(2).toMillis() + System.currentTimeMillis());
+
+ // simulate load after reboot
+ mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
+ false, mClock);
+ loadByteArrayXml(xml.getBytes(), false, USER_SYSTEM);
+
+ // Trigger 2nd restore pass
+ mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R},
+ new int[]{UID_P});
+
+ // verify the 2nd restore pass failed because the restore data had been removed
+ assertThat(mXmlHelper.getNotificationChannel(PKG_R, UNKNOWN_UID, id, false)).isNull();
+ }
/**
* Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to
@@ -1520,10 +1693,10 @@
mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false);
+ false, mClock);
mXmlHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false);
+ false, mClock);
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -3981,7 +4154,7 @@
pm.applicationInfo = new ApplicationInfo();
pm.applicationInfo.uid = UID_O;
List<PackageInfo> packages = ImmutableList.of(pm);
- when(mPm.getInstalledPackagesAsUser(any(), anyInt())).thenReturn(packages);
+ when(mPm.getInstalledPackagesAsUser(eq(0), anyInt())).thenReturn(packages);
mHelper.updateFixedImportance(users);
assertTrue(mHelper.isImportanceLocked(PKG_O, UID_O));
@@ -4097,7 +4270,7 @@
pm.applicationInfo = new ApplicationInfo();
pm.applicationInfo.uid = UID_O;
List<PackageInfo> packages = ImmutableList.of(pm);
- when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages);
+ when(mPm.getInstalledPackagesAsUser(0, 0)).thenReturn(packages);
mHelper.updateFixedImportance(users);
assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
@@ -4120,7 +4293,7 @@
pm.applicationInfo = new ApplicationInfo();
pm.applicationInfo.uid = UID_O;
List<PackageInfo> packages = ImmutableList.of(pm);
- when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages);
+ when(mPm.getInstalledPackagesAsUser(0, 0)).thenReturn(packages);
mHelper.updateFixedImportance(users);
NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
@@ -4309,7 +4482,7 @@
pm.applicationInfo = new ApplicationInfo();
pm.applicationInfo.uid = UID_O;
List<PackageInfo> packages = ImmutableList.of(pm);
- when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages);
+ when(mPm.getInstalledPackagesAsUser(0, 0)).thenReturn(packages);
mHelper.updateFixedImportance(users);
ArraySet<String> toRemove = new ArraySet<>();
@@ -4341,7 +4514,7 @@
pm.applicationInfo = new ApplicationInfo();
pm.applicationInfo.uid = UID_O;
List<PackageInfo> packages = ImmutableList.of(pm);
- when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages);
+ when(mPm.getInstalledPackagesAsUser(0, 0)).thenReturn(packages);
mHelper.updateFixedImportance(users);
assertTrue(mHelper.isImportanceLocked(PKG_O, UID_O));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index ad420f6..527001d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -55,6 +55,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.compat.IPlatformCompat;
import com.android.server.UiServiceTestCase;
import org.junit.Before;
@@ -155,7 +156,8 @@
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper,
- mUsageStats, new String[] {ImportanceExtractor.class.getName()});
+ mUsageStats, new String[] {ImportanceExtractor.class.getName()},
+ mock(IPlatformCompat.class));
mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setContentTitle("A")
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java
new file mode 100644
index 0000000..8b46c8c
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.notification;
+
+import static com.android.server.notification.TimeToLiveHelper.EXTRA_KEY;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.SuppressLint;
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.testing.TestableLooper;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import com.android.server.UiServiceTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.quality.Strictness;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
+public class TimeToLiveHelperTest extends UiServiceTestCase {
+
+ TimeToLiveHelper mHelper;
+ @Mock
+ NotificationManagerPrivate mNm;
+ @Mock
+ AlarmManager mAm;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext.addMockSystemService(AlarmManager.class, mAm);
+ mHelper = new TimeToLiveHelper(mNm, mContext);
+ }
+
+ @After
+ public void tearDown() {
+ mHelper.destroy();
+ }
+
+ private NotificationRecord getRecord(String tag, int timeoutAfter) {
+ NotificationChannel channel = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_HIGH);
+ Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setTimeoutAfter(timeoutAfter);
+
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, tag, mUid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ return new NotificationRecord(mContext, sbn, channel);
+ }
+
+ @Test
+ public void testTimeout() {
+ mHelper.scheduleTimeoutLocked(getRecord("testTimeout", 1), 1);
+
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), any());
+ assertThat(mHelper.mKeys).hasSize(1);
+ }
+
+ @Test
+ public void testTimeoutExpires() {
+ NotificationRecord r = getRecord("testTimeoutExpires", 1);
+
+ mHelper.scheduleTimeoutLocked(r, 1);
+ ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), captor.capture());
+
+ mHelper.mNotificationTimeoutReceiver.onReceive(mContext, captor.getValue().getIntent());
+
+ assertThat(mHelper.mKeys).isEmpty();
+ }
+
+ @Test
+ public void testTimeoutExpires_twoEntries() {
+ NotificationRecord first = getRecord("testTimeoutFirst", 1);
+ NotificationRecord later = getRecord("testTimeoutSecond", 2);
+
+ mHelper.scheduleTimeoutLocked(first, 1);
+ mHelper.scheduleTimeoutLocked(later, 1);
+
+ ArgumentCaptor<PendingIntent> captorSet = ArgumentCaptor.forClass(PendingIntent.class);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), captorSet.capture());
+
+ ArgumentCaptor<PendingIntent> captorNewSet = ArgumentCaptor.forClass(PendingIntent.class);
+ mHelper.mNotificationTimeoutReceiver.onReceive(mContext, captorSet.getValue().getIntent());
+
+ assertThat(mHelper.mKeys).hasSize(1);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(3L), captorNewSet.capture());
+ assertThat(captorSet.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(first.getKey());
+ }
+
+ @Test
+ public void testTimeout_earlierEntryAddedSecond() {
+ NotificationRecord later = getRecord("testTimeoutSecond", 2);
+ mHelper.scheduleTimeoutLocked(later, 1);
+
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(3L), any());
+ assertThat(mHelper.mKeys).hasSize(1);
+
+ NotificationRecord first = getRecord("testTimeoutFirst", 1);
+ ArgumentCaptor<PendingIntent> captorSet = ArgumentCaptor.forClass(PendingIntent.class);
+ ArgumentCaptor<PendingIntent> captorCancel = ArgumentCaptor.forClass(PendingIntent.class);
+
+ mHelper.scheduleTimeoutLocked(first, 1);
+
+ assertThat(mHelper.mKeys).hasSize(2);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), captorSet.capture());
+ assertThat(captorSet.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(first.getKey());
+ assertThat(mHelper.mKeys.first().second).isEqualTo(first.getKey());
+
+ verify(mAm).cancel(captorCancel.capture());
+ assertThat(captorCancel.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(later.getKey());
+ }
+
+ @Test
+ public void testTimeout_earlierEntryAddedFirst() {
+ NotificationRecord first = getRecord("testTimeoutFirst", 1);
+ NotificationRecord later = getRecord("testTimeoutSecond", 2);
+
+ mHelper.scheduleTimeoutLocked(first, 1);
+ mHelper.scheduleTimeoutLocked(later, 1);
+
+ assertThat(mHelper.mKeys).hasSize(2);
+ assertThat(mHelper.mKeys.first().second).isEqualTo(first.getKey());
+ verify(mAm, never()).cancel((PendingIntent) any());
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), any());
+ }
+
+ @Test
+ public void testTimeout_updateEarliestEntry() {
+ NotificationRecord first = getRecord("testTimeoutFirst", 1);
+
+ mHelper.scheduleTimeoutLocked(first, 1);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), any());
+
+ NotificationRecord firstUpdated = getRecord("testTimeoutFirst", 3);
+ ArgumentCaptor<PendingIntent> captorSet = ArgumentCaptor.forClass(PendingIntent.class);
+ ArgumentCaptor<PendingIntent> captorCancel = ArgumentCaptor.forClass(PendingIntent.class);
+
+ mHelper.scheduleTimeoutLocked(firstUpdated, 1);
+
+ assertThat(mHelper.mKeys).hasSize(1);
+
+ // cancel original alarm
+ verify(mAm).cancel(captorCancel.capture());
+ assertThat(captorCancel.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(first.getKey());
+
+ // schedule later alarm
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(4L), captorSet.capture());
+ assertThat(captorSet.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(first.getKey());
+ }
+
+ @Test
+ public void testTimeout_twoEntries_updateEarliestEntry() {
+ NotificationRecord first = getRecord("testTimeoutFirst", 1);
+ NotificationRecord later = getRecord("testTimeoutSecond", 2);
+
+ mHelper.scheduleTimeoutLocked(first, 1);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), any());
+
+ mHelper.scheduleTimeoutLocked(later, 1);
+
+ NotificationRecord firstUpdated = getRecord("testTimeoutFirst", 3);
+ ArgumentCaptor<PendingIntent> captorSet = ArgumentCaptor.forClass(PendingIntent.class);
+ ArgumentCaptor<PendingIntent> captorCancel = ArgumentCaptor.forClass(PendingIntent.class);
+
+ mHelper.scheduleTimeoutLocked(firstUpdated, 1);
+
+ assertThat(mHelper.mKeys).hasSize(2);
+ assertThat(mHelper.mKeys.first().second).isEqualTo(later.getKey());
+
+ // "first" was canceled because it's now later
+ verify(mAm).cancel(captorCancel.capture());
+ assertThat(captorCancel.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(first.getKey());
+
+ // "later" is now the first entry, and needs the matching alarm
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(3L), captorSet.capture());
+ assertThat(captorSet.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(later.getKey());
+ }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 3f5217c..8ca8623 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -39,6 +39,7 @@
import android.content.ComponentName;
import android.content.pm.PackageManagerInternal;
import android.frameworks.vibrator.ScaleParam;
+import android.frameworks.vibrator.VibrationParam;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -59,6 +60,8 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -177,6 +180,24 @@
verifyZeroInteractions(mMockVibrationScaler);
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testOnRequestVibrationParamsComplete_withNullVibrationParams_throwsException() {
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ int timeoutInMillis = 10;
+ CompletableFuture<Void> unusedFuture =
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
+ timeoutInMillis);
+ IBinder token = mVibratorControlService.getRequestVibrationParamsToken();
+
+ List<VibrationParam> vibrationParamList = Arrays.asList(
+ VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_ALARM, 0.7f),
+ null,
+ VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_NOTIFICATION, 0.4f));
+
+ mVibratorControlService.onRequestVibrationParamsComplete(token,
+ vibrationParamList.toArray(new VibrationParam[0]));
+ }
+
@Test
public void testSetVibrationParams_cachesAdaptiveHapticsScalesCorrectly() {
mVibratorControlService.registerVibratorController(mFakeVibratorController);
@@ -214,6 +235,19 @@
verifyZeroInteractions(mMockVibrationScaler);
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testSetVibrationParams_withNullVibrationParams_throwsException() {
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ List<VibrationParam> vibrationParamList = Arrays.asList(
+ VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_ALARM, 0.7f),
+ null,
+ VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_NOTIFICATION, 0.4f));
+
+ mVibratorControlService.setVibrationParams(
+ vibrationParamList.toArray(new VibrationParam[0]),
+ mFakeVibratorController);
+ }
+
@Test
public void testClearVibrationParams_clearsCachedAdaptiveHapticsScales() {
mVibratorControlService.registerVibratorController(mFakeVibratorController);
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java b/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java
index a606388..c17d11e 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java
@@ -42,7 +42,10 @@
return vibrationParamList.toArray(new VibrationParam[0]);
}
- private static VibrationParam generateVibrationParam(int type, float scale) {
+ /**
+ * Generates a {@link VibrationParam} with the specified type and scale.
+ */
+ public static VibrationParam generateVibrationParam(int type, float scale) {
ScaleParam scaleParam = new ScaleParam();
scaleParam.typesMask = type;
scaleParam.scale = scale;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 2c88ed2..7356b43 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1717,6 +1717,7 @@
// The display should be rotated after the launch is finished.
doReturn(false).when(app).isAnimating(anyInt(), anyInt());
mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token);
+ waitHandlerIdle(mWm.mH);
mStatusBarWindow.finishSeamlessRotation(t);
mNavBarWindow.finishSeamlessRotation(t);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index 80e169d..b90fa21 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -25,6 +25,8 @@
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+import static com.android.server.wm.testing.Assert.assertThrows;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -37,6 +39,8 @@
import androidx.test.filters.SmallTest;
+import com.android.server.wm.testing.Assert;
+
import org.junit.Before;
import org.junit.Test;
@@ -288,4 +292,56 @@
false /* forTabletopMode */,
LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
}
+
+ @Test
+ public void test_setLetterboxHorizontalPositionMultiplier_validValues() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(-1));
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(2));
+
+ // Does not throw an exception for values [0,1].
+ mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0);
+ mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f);
+ mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(1);
+ }
+
+ @Test
+ public void test_setLetterboxVerticalPositionMultiplier_validValues() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(-1));
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(2));
+
+ // Does not throw an exception for values [0,1].
+ mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0);
+ mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+ mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(1);
+ }
+
+ @Test
+ public void test_setLetterboxBookModePositionMultiplier_validValues() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(-1));
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(2));
+
+ // Does not throw an exception for values [0,1].
+ mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(0);
+ mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(0.5f);
+ mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(1);
+ }
+
+ @Test
+ public void test_setLetterboxTabletopModePositionMultiplier_validValues() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(-1));
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(2));
+
+ // Does not throw an exception for values [0,1].
+ mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(0);
+ mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(0.5f);
+ mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(1);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 5aabea3..b41db31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -643,7 +643,8 @@
doReturn(false).when(mActivity).isInLetterboxAnimation();
assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
- doReturn(false).when(mainWindow).isOnScreen();
+ doReturn(false).when(mActivity).isVisibleRequested();
+ doReturn(false).when(mActivity).isVisible();
assertEquals(0, mController.getRoundedCornersRadius(mainWindow));
doReturn(true).when(mActivity).isInLetterboxAnimation();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 649f520..dcf3dadc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -74,6 +74,7 @@
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.util.Pair;
@@ -237,6 +238,24 @@
assertFalse(wpc.hasActivities());
}
+ @Test
+ public void testAttachApplication() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ activity.detachFromProcess();
+ mAtm.startProcessAsync(activity, false /* knownToBeDead */,
+ true /* isTop */, "test" /* hostingType */);
+ final WindowProcessController proc = mSystemServicesTestRule.addProcess(
+ activity.packageName, activity.processName,
+ 6789 /* pid */, activity.info.applicationInfo.uid);
+ try {
+ mRootWindowContainer.attachApplication(proc);
+ verify(mSupervisor).realStartActivityLocked(eq(activity), eq(proc), anyBoolean(),
+ anyBoolean());
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
/**
* This test ensures that we do not try to restore a task based off an invalid task id. We
* should expect {@code null} to be returned in this case.
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index fbf1426..9697c65 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -130,7 +130,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -916,8 +915,7 @@
assertEquals(window, mActivity.findMainWindow());
spyOn(mActivity.mLetterboxUiController);
- doReturn(true).when(mActivity.mLetterboxUiController)
- .isSurfaceVisible(any());
+ doReturn(true).when(mActivity).isVisibleRequested();
assertTrue(mActivity.mLetterboxUiController.shouldShowLetterboxUi(
mActivity.findMainWindow()));
@@ -1943,8 +1941,7 @@
assertThat(mActivity.inSizeCompatMode()).isTrue();
assertActivityMaxBoundsSandboxed();
-
- final int scale = dh / dw;
+ final int scale = dh / dw;
// App bounds should be dh / scale x dw / scale
assertEquals(dw, rotatedDisplayBounds.width());
@@ -4143,31 +4140,6 @@
}
@Test
- public void testUpdateResolvedBoundsHorizontalPosition_invalidMultiplier_defaultToCenter() {
- // Display configured as (2800, 1400).
-
- // Below 0.0.
- assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
- /* letterboxHorizontalPositionMultiplier */ -1.0f,
- // At launch.
- /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
- // After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
- // After the display is resized to (700, 1400).
- /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
-
- // Above 1.0
- assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
- /* letterboxHorizontalPositionMultiplier */ 2.0f,
- // At launch.
- /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
- // After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
- // After the display is resized to (700, 1400).
- /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
- }
-
- @Test
public void testUpdateResolvedBoundsHorizontalPosition_right() {
// Display configured as (2800, 1400).
assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
@@ -4204,13 +4176,8 @@
}
@Test
- @Ignore // TODO(b/330888878): fix test in main
- public void testPortraitCloseToSquareDisplayWithTaskbar_notLetterboxed() {
- if (Flags.insetsDecoupledConfiguration()) {
- // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
- // bounds no longer contains display cutout.
- return;
- }
+ @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
+ public void testPortraitCloseToSquareDisplayWithTaskbar_letterboxed() {
// Set up portrait close to square display
setUpDisplaySizeWithApp(2200, 2280);
final DisplayContent display = mActivity.mDisplayContent;
@@ -4223,16 +4190,58 @@
.setInsetsSize(Insets.of(0, 0, 0, 150))
};
display.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
- assertTrue(navbar.providesDisplayDecorInsets()
- && display.getDisplayPolicy().updateDecorInsetsInfo());
+ assertTrue(display.getDisplayPolicy().updateDecorInsetsInfo());
display.sendNewConfiguration();
- prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
- // Activity is fullscreen even though orientation is not respected with insets, because
- // the display still matches or is less than the activity aspect ratio
- assertEquals(display.getBounds(), mActivity.getBounds());
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ final Rect bounds = activity.getBounds();
+ // Activity should be letterboxed and should have portrait app bounds
+ assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(bounds.height() > bounds.width());
+ }
+
+ @Test
+ @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
+ public void testFixedAspectRatioAppInPortraitCloseToSquareDisplay_notInSizeCompat() {
+ setUpDisplaySizeWithApp(2200, 2280);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ final DisplayContent dc = mActivity.mDisplayContent;
+ // Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape
+ final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent,
+ "navbar");
+ final Binder owner = new Binder();
+ navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
+ .setInsetsSize(Insets.of(0, 0, 0, 150))
+ };
+ dc.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
+ assertTrue(dc.getDisplayPolicy().updateDecorInsetsInfo());
+ dc.sendNewConfiguration();
+
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+ prepareMinAspectRatio(activity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ SCREEN_ORIENTATION_LANDSCAPE);
+ // To force config to update again but with the same landscape orientation.
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
+
+ assertTrue(activity.shouldCreateCompatDisplayInsets());
+ assertNotNull(activity.getCompatDisplayInsets());
+ // Activity is not letterboxed for fixed orientation because orientation is respected
+ // with insets, and should not be in size compat mode
+ assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(activity.inSizeCompatMode());
}
@Test
@@ -4254,6 +4263,7 @@
// can be aligned inside parentAppBounds
assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 2200));
}
+
@Test
public void testApplyAspectRatio_activityCannotAlignWithParentAppVertical() {
if (Flags.insetsDecoupledConfiguration()) {
@@ -4398,31 +4408,6 @@
}
@Test
- public void testUpdateResolvedBoundsVerticalPosition_invalidMultiplier_defaultToCenter() {
- // Display configured as (1400, 2800).
-
- // Below 0.0.
- assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
- /* letterboxVerticalPositionMultiplier */ -1.0f,
- // At launch.
- /* fixedOrientationLetterbox */ new Rect(0, 1050, 1400, 1750),
- // After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(700, 350, 2100, 1050),
- // After the display is resized to (1400, 700).
- /* sizeCompatScaled */ new Rect(0, 525, 700, 875));
-
- // Above 1.0
- assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
- /* letterboxVerticalPositionMultiplier */ 2.0f,
- // At launch.
- /* fixedOrientationLetterbox */ new Rect(0, 1050, 1400, 1750),
- // After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(700, 350, 2100, 1050),
- // After the display is resized to (1400, 700).
- /* sizeCompatScaled */ new Rect(0, 525, 700, 875));
- }
-
- @Test
public void testUpdateResolvedBoundsVerticalPosition_bottom() {
// Display configured as (1400, 2800).
assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 413d003..b9fe074 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -68,6 +68,7 @@
import android.os.PowerSaveState;
import android.os.StrictMode;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.DeviceConfig;
import android.util.Log;
import android.view.DisplayInfo;
@@ -194,6 +195,7 @@
mMockitoSession = mockitoSession()
.mockStatic(LocalServices.class, spyStubOnly)
.mockStatic(DeviceConfig.class, spyStubOnly)
+ .mockStatic(UserManager.class, spyStubOnly)
.mockStatic(SurfaceControl.class, mockStubOnly)
.mockStatic(DisplayControl.class, mockStubOnly)
.mockStatic(LockGuard.class, mockStubOnly)
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 52485ee..002a3d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -19,6 +19,8 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -1024,6 +1026,58 @@
}
@Test
+ public void testApplyTransaction_createTaskFragment_overrideOrientation_systemOrganizer() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG);
+ mController.unregisterOrganizer(mIOrganizer);
+ registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */);
+
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(task);
+ final int uid = Binder.getCallingUid();
+ activity.info.applicationInfo.uid = uid;
+ activity.getTask().effectiveUid = uid;
+ final IBinder fragmentToken = new Binder();
+
+ // Create a TaskFragment with OverrideOrientation set.
+ final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+ mOrganizerToken, fragmentToken, activity.token)
+ .setOverrideOrientation(SCREEN_ORIENTATION_BEHIND)
+ .build();
+ mTransaction.setTaskFragmentOrganizer(mIOrganizer);
+ mTransaction.createTaskFragment(params);
+ assertApplyTransactionAllowed(mTransaction);
+
+ // TaskFragment override orientation should be set for a system organizer.
+ final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken);
+ assertNotNull(taskFragment);
+ assertEquals(SCREEN_ORIENTATION_BEHIND, taskFragment.getOverrideOrientation());
+ }
+
+ @Test
+ public void testApplyTransaction_createTaskFragment_overrideOrientation_nonSystemOrganizer() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(task);
+ final int uid = Binder.getCallingUid();
+ activity.info.applicationInfo.uid = uid;
+ activity.getTask().effectiveUid = uid;
+ final IBinder fragmentToken = new Binder();
+
+ // Create a TaskFragment with OverrideOrientation set.
+ final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+ mOrganizerToken, fragmentToken, activity.token)
+ .setOverrideOrientation(SCREEN_ORIENTATION_BEHIND)
+ .build();
+ mTransaction.setTaskFragmentOrganizer(mIOrganizer);
+ mTransaction.createTaskFragment(params);
+ assertApplyTransactionAllowed(mTransaction);
+
+ // TaskFragment override orientation is ignored for a non-system organizer.
+ final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken);
+ assertNotNull(taskFragment);
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, taskFragment.getOverrideOrientation());
+ }
+
+ @Test
public void testApplyTransaction_reparentActivityToTaskFragment_triggerLifecycleUpdate() {
final Task task = createTask(mDisplayContent);
final ActivityRecord activity = createActivityRecord(task);
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 3c5b12c..4837fcb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -752,6 +753,21 @@
}
@Test
+ public void testGetOrientation_reportOverrideOrientation() {
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment tf = createTaskFragmentWithActivity(task);
+ final ActivityRecord activity = tf.getTopMostActivity();
+ tf.setOverrideOrientation(SCREEN_ORIENTATION_BEHIND);
+
+ // Should report the override orientation
+ assertEquals(SCREEN_ORIENTATION_BEHIND, tf.getOrientation(SCREEN_ORIENTATION_UNSET));
+
+ // Should report the override orientation even if the activity requests a different value
+ activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_BEHIND, tf.getOrientation(SCREEN_ORIENTATION_UNSET));
+ }
+
+ @Test
public void testUpdateImeParentForActivityEmbedding() {
// Setup two activities in ActivityEmbedding.
final Task task = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 1ca808f..225e85e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -833,8 +833,11 @@
final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
final ActivityRecord.CompatDisplayInsets compatInsets =
new ActivityRecord.CompatDisplayInsets(
- display, activity, /* fixedOrientationBounds= */ null);
- task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatInsets);
+ display, activity, /* letterboxedContainerBounds */ null,
+ /* useOverrideInsets */ false);
+ final TaskFragment.ConfigOverrideHint overrideHint = new TaskFragment.ConfigOverrideHint();
+ overrideHint.mTmpCompatInsets = compatInsets;
+ task.computeConfigResourceOverrides(inOutConfig, parentConfig, overrideHint);
assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds());
final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 72bedf2..5b1a18d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -410,6 +410,8 @@
final WindowState wallpaperWindow = createWallpaperWindow(dc);
final WallpaperWindowToken token = wallpaperWindow.mToken.asWallpaperToken();
wallpaperWindow.setHasSurface(true);
+ spyOn(dc.mWallpaperController);
+ doReturn(wallpaperWindow).when(dc.mWallpaperController).getWallpaperTarget();
// Set-up mock shell transitions
registerTestTransitionPlayer();
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 50db99e..5fe71a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -25,7 +25,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_OWN_FOCUS;
import static android.view.Display.INVALID_DISPLAY;
-import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING;
@@ -38,6 +37,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -82,6 +82,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -1304,7 +1305,8 @@
}
@Test
- public void testAddOverlayWindowToUnassignedDisplay_notAllowed() {
+ public void testAddOverlayWindowToUnassignedDisplay_notAllowed_ForVisibleBackgroundUsers() {
+ doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
int uid = 100000; // uid for non-system user
Session session = createTestSession(mAtm, 1234 /* pid */, uid);
DisplayContent dc = createNewDisplay();
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 43b424f..69b5c37 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1681,7 +1681,8 @@
WindowContainerToken wct = rootTask.mRemoteToken.toWindowContainerToken();
t.setWindowingMode(wct, WINDOWING_MODE_PINNED);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
- verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
+ verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivitiesUnchecked(any(),
+ any(), any(), anyBoolean());
clearInvocations(mWm.mAtmService.mRootWindowContainer);
// The token for the PIP root task may have changed when the task entered PIP mode, so do
@@ -1690,7 +1691,8 @@
record.getRootTask().mRemoteToken.toWindowContainerToken();
t.setWindowingMode(newToken, WINDOWING_MODE_FULLSCREEN);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
- verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
+ verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivitiesUnchecked(any(),
+ any(), any(), anyBoolean());
}
@Test
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 7b5b07c..f31a87f 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -349,15 +349,16 @@
}
/**
- * @param plmn target plmn for validation.
- * @return {@code true} if the target plmn is valid {@code false} otherwise.
+ * @param input string that want to be compared.
+ * @param regex string that express regular expression
+ * @return {@code true} if matched {@code false} otherwise.
*/
- public static boolean isValidPlmn(@Nullable String plmn) {
- if (TextUtils.isEmpty(plmn)) {
+ private static boolean isValidPattern(@Nullable String input, @Nullable String regex) {
+ if (TextUtils.isEmpty(input) || TextUtils.isEmpty(regex)) {
return false;
}
- Pattern pattern = Pattern.compile("^(?:[0-9]{3})(?:[0-9]{2}|[0-9]{3})$");
- Matcher matcher = pattern.matcher(plmn);
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(input);
if (!matcher.matches()) {
return false;
}
@@ -365,6 +366,22 @@
}
/**
+ * @param countryCode two letters country code based on the ISO 3166-1.
+ * @return {@code true} if the countryCode is valid {@code false} otherwise.
+ */
+ public static boolean isValidCountryCode(@Nullable String countryCode) {
+ return isValidPattern(countryCode, "^[A-Za-z]{2}$");
+ }
+
+ /**
+ * @param plmn target plmn for validation.
+ * @return {@code true} if the target plmn is valid {@code false} otherwise.
+ */
+ public static boolean isValidPlmn(@Nullable String plmn) {
+ return isValidPattern(plmn, "^(?:[0-9]{3})(?:[0-9]{2}|[0-9]{3})$");
+ }
+
+ /**
* @param serviceType target serviceType for validation.
* @return {@code true} if the target serviceType is valid {@code false} otherwise.
*/
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index ba7ba532..8fe45cb 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4614,6 +4614,31 @@
}
/**
+ * Set owner for this subscription.
+ *
+ * @param subscriptionId the subId of the subscription.
+ * @param groupOwner The group owner to assign to the subscription
+ *
+ * @throws SecurityException if caller is not authorized.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setGroupOwner(int subscriptionId, @NonNull String groupOwner) {
+ try {
+ ISub iSub = TelephonyManager.getSubscriptionService();
+ if (iSub != null) {
+ iSub.setGroupOwner(subscriptionId, groupOwner);
+ } else {
+ throw new IllegalStateException("[setGroupOwner]: "
+ + "subscription service unavailable");
+ }
+ } catch (RemoteException ex) {
+ ex.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Set userHandle for a subscription.
*
* Used to set an association between a subscription and a user on the device so that voice
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 0a8a18dc..f076de3 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -289,7 +289,7 @@
@SystemApi
public static final int RADIO_POWER_REASON_NEARBY_DEVICE = 3;
- /** The otaspMode passed to PhoneStateListener#onOtaspChanged */
+ /** The otaspMode passed to SercvieState changes */
/** @hide */
static public final int OTASP_UNINITIALIZED = 0;
/** @hide */
@@ -995,9 +995,9 @@
"android.intent.action.CALL_DISCONNECT_CAUSE";
/**
- * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and
- * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer
- * containing the disconnect cause.
+ * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and {@link
+ * TelephonyCallback.PreciseCallStateListener#onPreciseCallStateChanged(PreciseCallState)} for
+ * an integer containing the disconnect cause.
*
* @see DisconnectCause
*
@@ -1012,9 +1012,9 @@
public static final String EXTRA_DISCONNECT_CAUSE = "disconnect_cause";
/**
- * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and
- * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer
- * containing the disconnect cause provided by the RIL.
+ * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and {@link
+ * TelephonyCallback.PreciseCallStateListener#onPreciseCallStateChanged(PreciseCallState)} for
+ * an integer containing the disconnect cause provided by the RIL.
*
* @see PreciseDisconnectCause
*
@@ -6437,8 +6437,8 @@
* This method considers not only calls in the Telephony stack, but also calls via other
* {@link android.telecom.ConnectionService} implementations.
* <p>
- * Note: The call state returned via this method may differ from what is reported by
- * {@link PhoneStateListener#onCallStateChanged(int, String)}, as that callback only considers
+ * Note: The call state returned via this method may differ from what is reported by {@link
+ * TelephonyCallback.CallStateListener#onCallStateChanged(int)}, as that callback only considers
* Telephony (mobile) calls.
* <p>
* Requires Permission:
@@ -6996,7 +6996,7 @@
*
* <p>Beginning with {@link android.os.Build.VERSION_CODES#Q Android Q},
* if this API results in a change of the cached CellInfo, that change will be reported via
- * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}.
+ * {@link TelephonyCallback.CellInfoListener#onCellInfoChanged(List) onCellInfoChanged()}.
*
* <p>Apps targeting {@link android.os.Build.VERSION_CODES#Q Android Q} or higher will no
* longer trigger a refresh of the cached CellInfo by invoking this API. Instead, those apps
@@ -7109,7 +7109,7 @@
* camped/registered, serving, and neighboring cells.
*
* <p>Any available results from this request will be provided by calls to
- * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}
+ * {@link TelephonyCallback.CellInfoListener#onCellInfoChanged(List) onCellInfoChanged()}
* for each active subscription.
*
* <p>This method returns valid data for devices with
@@ -7172,7 +7172,7 @@
* camped/registered, serving, and neighboring cells.
*
* <p>Any available results from this request will be provided by calls to
- * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}
+ * {@link TelephonyCallback.CellInfoListener#onCellInfoChanged(List) onCellInfoChanged()}
* for each active subscription.
*
* <p>This method returns valid data for devices with
@@ -7248,8 +7248,8 @@
}
/**
- * Sets the minimum time in milli-seconds between {@link PhoneStateListener#onCellInfoChanged
- * PhoneStateListener.onCellInfoChanged} will be invoked.
+ * Sets the minimum time in milli-seconds between {@link
+ * TelephonyCallback.CellInfoListener#onCellInfoChanged(List)} will be invoked.
*<p>
* The default, 0, means invoke onCellInfoChanged when any of the reported
* information changes. Setting the value to INT_MAX(0x7fffffff) means never issue
@@ -11364,7 +11364,7 @@
* Shut down all the live radios over all the slot indexes.
*
* <p>To know when the radio has completed powering off, use
- * {@link PhoneStateListener#LISTEN_SERVICE_STATE LISTEN_SERVICE_STATE}.
+ * {@link TelephonyCallback.ServiceStateListener}.
*
* @throws UnsupportedOperationException If the device does not have
* {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
@@ -13058,8 +13058,9 @@
* <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
* given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
*
- * If you want continuous updates of service state info, register a {@link PhoneStateListener}
- * via {@link #listen} with the {@link PhoneStateListener#LISTEN_SERVICE_STATE} event.
+ * If you want continuous updates of service state info, register a {@link TelephonyCallback}
+ * that implements {@link TelephonyCallback.ServiceStateListener} through {@link
+ * #registerTelephonyCallback}.
*
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
* or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges})
@@ -13086,8 +13087,9 @@
* <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
* given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
*
- * If you want continuous updates of service state info, register a {@link PhoneStateListener}
- * via {@link #listen} with the {@link PhoneStateListener#LISTEN_SERVICE_STATE} event.
+ * If you want continuous updates of service state info, register a {@link TelephonyCallback}
+ * that implements {@link TelephonyCallback.ServiceStateListener} through {@link
+ * #registerTelephonyCallback}.
*
* There's another way to renounce permissions with a custom context
* {@code AttributionSource.Builder#setRenouncedPermissions(Set<String>)} but only for system
@@ -17892,9 +17894,9 @@
* measurements breach the specified thresholds.
*
* To be notified, set the signal strength update request and then register
- * {@link TelephonyManager#listen(PhoneStateListener, int)} with
- * {@link PhoneStateListener#LISTEN_SIGNAL_STRENGTHS}. The notification will arrive through
- * {@link PhoneStateListener#onSignalStrengthsChanged(SignalStrength)}.
+ * {@link TelephonyCallback} that implements {@link TelephonyCallback.SignalStrengthsListener}
+ * through {@link #registerTelephonyCallback}. The notification will arrive through
+ * {@link TelephonyCallback.SignalStrengthsListener#onSignalStrengthsChanged(SignalStrength)}.
*
* To stop receiving the notification over the specified thresholds, pass the same
* {@link SignalStrengthUpdateRequest} object to
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index ebabbf9..ca4a643 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -1039,17 +1039,18 @@
* subscription on the
* current eUICC and the subscription to be downloaded according to the subscription metadata.
* Without the former, an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be
- * eturned in the callback intent to prompt the user to accept the download.
+ * returned in the callback intent to prompt the user to accept the download.
*
* <p> Starting from Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
* if the caller has the
* {@code android.Manifest.permission#MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS} permission or
- * is a profile owner or device owner, and
- * {@code switchAfterDownload} is {@code false}, then the downloaded subscription
- * will be managed by that caller. If {@code switchAfterDownload} is true,
- * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be
- * returned in the callback intent to prompt the user to accept the download and the
- * subscription will not be managed.
+ * is a profile owner or device owner, then the downloaded subscription
+ * will be managed by that caller.
+ * In case the caller is device owner or profile owner of an organization-owned device, {@code
+ * switchAfterDownload} can be set to true to automatically enable the subscription after
+ * download. If the caller is a profile owner on non organization owned device
+ * {@code switchAfterDownload} should be false otherwise the operation will fail with
+ * {@link #EMBEDDED_SUBSCRIPTION_RESULT_ERROR}.
*
* <p>On a multi-active SIM device, requires the
* {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, or a calling app
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 6678f40..1bfec29 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -309,6 +309,18 @@
*/
int setUsageSetting(int usageSetting, int subId, String callingPackage);
+ /**
+ * Set owner for this subscription.
+ *
+ * @param subId the unique SubscriptionInfo index in database
+ * @param groupOwner The group owner to assign to the subscription
+ *
+ * @throws SecurityException if caller is not authorized.
+ *
+ * @hide
+ */
+ void setGroupOwner(int subId, String groupOwner);
+
/**
* Set userHandle for this subscription.
*
diff --git a/tests/ChoreographerTests/Android.bp b/tests/ChoreographerTests/Android.bp
index 5d49120..3f48d70 100644
--- a/tests/ChoreographerTests/Android.bp
+++ b/tests/ChoreographerTests/Android.bp
@@ -19,6 +19,7 @@
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_android_core_graphics_stack",
}
android_test {
diff --git a/tests/CtsSurfaceControlTestsStaging/Android.bp b/tests/CtsSurfaceControlTestsStaging/Android.bp
index 96e4a9e..1038c9e 100644
--- a/tests/CtsSurfaceControlTestsStaging/Android.bp
+++ b/tests/CtsSurfaceControlTestsStaging/Android.bp
@@ -19,6 +19,7 @@
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_android_core_graphics_stack",
}
android_test {
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index f6f766a..8d2b927 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -19,17 +19,26 @@
import android.content.Context
import android.content.ContextWrapper
+import android.hardware.display.DisplayManager
import android.hardware.display.DisplayViewport
+import android.hardware.display.VirtualDisplay
import android.hardware.input.InputManager
import android.hardware.input.InputManagerGlobal
+import android.os.InputEventInjectionSync
+import android.os.SystemClock
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresFlagsDisabled
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.provider.Settings
-import android.test.mock.MockContentResolver
+import android.view.View.OnKeyListener
import android.view.Display
+import android.view.InputDevice
+import android.view.KeyEvent
import android.view.PointerIcon
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+import android.test.mock.MockContentResolver
import androidx.test.platform.app.InstrumentationRegistry
import com.android.internal.util.test.FakeSettingsProvider
import com.google.common.truth.Truth.assertThat
@@ -48,6 +57,7 @@
import org.mockito.Mockito.`when`
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
@@ -412,6 +422,174 @@
verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, 0f, 0f)
thread.join(100 /*millis*/)
}
+
+ private fun createVirtualDisplays(count: Int): List<VirtualDisplay> {
+ val displayManager: DisplayManager = context.getSystemService(
+ DisplayManager::class.java
+ ) as DisplayManager
+ val virtualDisplays = mutableListOf<VirtualDisplay>()
+ for (i in 0 until count) {
+ virtualDisplays.add(displayManager.createVirtualDisplay(
+ /* displayName= */ "testVirtualDisplay$i",
+ /* width= */ 100,
+ /* height= */ 100,
+ /* densityDpi= */ 100,
+ /* surface= */ null,
+ /* flags= */ 0
+ ))
+ }
+ return virtualDisplays
+ }
+
+ // Helper function that creates a KeyEvent with Keycode A with the given action
+ private fun createKeycodeAEvent(inputDevice: InputDevice, action: Int): KeyEvent {
+ val eventTime = SystemClock.uptimeMillis()
+ return KeyEvent(
+ /* downTime= */ eventTime,
+ /* eventTime= */ eventTime,
+ /* action= */ action,
+ /* code= */ KeyEvent.KEYCODE_A,
+ /* repeat= */ 0,
+ /* metaState= */ 0,
+ /* deviceId= */ inputDevice.id,
+ /* scanCode= */ 0,
+ /* flags= */ KeyEvent.FLAG_FROM_SYSTEM,
+ /* source= */ InputDevice.SOURCE_KEYBOARD
+ )
+ }
+
+ private fun createInputDevice(): InputDevice {
+ return InputDevice.Builder()
+ .setId(123)
+ .setName("abc")
+ .setDescriptor("def")
+ .setSources(InputDevice.SOURCE_KEYBOARD)
+ .build()
+ }
+
+ @Test
+ fun addUniqueIdAssociationByDescriptor_verifyAssociations() {
+ // Overall goal is to have 2 displays and verify that events from the InputDevice are
+ // sent only to the view that is on the associated display.
+ // So, associate the InputDevice with display 1, then send and verify KeyEvents.
+ // Then remove associations, then associate the InputDevice with display 2, then send
+ // and verify commands.
+
+ // Make 2 virtual displays with some mock SurfaceViews
+ val mockSurfaceView1 = mock(SurfaceView::class.java)
+ val mockSurfaceView2 = mock(SurfaceView::class.java)
+ val mockSurfaceHolder1 = mock(SurfaceHolder::class.java)
+ `when`(mockSurfaceView1.holder).thenReturn(mockSurfaceHolder1)
+ val mockSurfaceHolder2 = mock(SurfaceHolder::class.java)
+ `when`(mockSurfaceView2.holder).thenReturn(mockSurfaceHolder2)
+
+ val virtualDisplays = createVirtualDisplays(2)
+
+ // Simulate an InputDevice
+ val inputDevice = createInputDevice()
+
+ // Associate input device with display
+ service.addUniqueIdAssociationByDescriptor(
+ inputDevice.descriptor,
+ virtualDisplays[0].display.displayId.toString()
+ )
+
+ // Simulate 2 different KeyEvents
+ val downEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_DOWN)
+ val upEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_UP)
+
+ // Create a mock OnKeyListener object
+ val mockOnKeyListener = mock(OnKeyListener::class.java)
+
+ // Verify that the event went to Display 1 not Display 2
+ service.injectInputEvent(downEvent, InputEventInjectionSync.NONE)
+
+ // Call the onKey method on the mock OnKeyListener object
+ mockOnKeyListener.onKey(mockSurfaceView1, /* keyCode= */ KeyEvent.KEYCODE_A, downEvent)
+ mockOnKeyListener.onKey(mockSurfaceView2, /* keyCode= */ KeyEvent.KEYCODE_A, upEvent)
+
+ // Verify that the onKey method was called with the expected arguments
+ verify(mockOnKeyListener).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, downEvent)
+ verify(mockOnKeyListener, never()).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, downEvent)
+
+ // Remove association
+ service.removeUniqueIdAssociationByDescriptor(inputDevice.descriptor)
+
+ // Associate with Display 2
+ service.addUniqueIdAssociationByDescriptor(
+ inputDevice.descriptor,
+ virtualDisplays[1].display.displayId.toString()
+ )
+
+ // Simulate a KeyEvent
+ service.injectInputEvent(upEvent, InputEventInjectionSync.NONE)
+
+ // Verify that the event went to Display 2 not Display 1
+ verify(mockOnKeyListener).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, upEvent)
+ verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent)
+ }
+
+ @Test
+ fun addUniqueIdAssociationByPort_verifyAssociations() {
+ // Overall goal is to have 2 displays and verify that events from the InputDevice are
+ // sent only to the view that is on the associated display.
+ // So, associate the InputDevice with display 1, then send and verify KeyEvents.
+ // Then remove associations, then associate the InputDevice with display 2, then send
+ // and verify commands.
+
+ // Make 2 virtual displays with some mock SurfaceViews
+ val mockSurfaceView1 = mock(SurfaceView::class.java)
+ val mockSurfaceView2 = mock(SurfaceView::class.java)
+ val mockSurfaceHolder1 = mock(SurfaceHolder::class.java)
+ `when`(mockSurfaceView1.holder).thenReturn(mockSurfaceHolder1)
+ val mockSurfaceHolder2 = mock(SurfaceHolder::class.java)
+ `when`(mockSurfaceView2.holder).thenReturn(mockSurfaceHolder2)
+
+ val virtualDisplays = createVirtualDisplays(2)
+
+ // Simulate an InputDevice
+ val inputDevice = createInputDevice()
+
+ // Associate input device with display
+ service.addUniqueIdAssociationByPort(
+ inputDevice.name,
+ virtualDisplays[0].display.displayId.toString()
+ )
+
+ // Simulate 2 different KeyEvents
+ val downEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_DOWN)
+ val upEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_UP)
+
+ // Create a mock OnKeyListener object
+ val mockOnKeyListener = mock(OnKeyListener::class.java)
+
+ // Verify that the event went to Display 1 not Display 2
+ service.injectInputEvent(downEvent, InputEventInjectionSync.NONE)
+
+ // Call the onKey method on the mock OnKeyListener object
+ mockOnKeyListener.onKey(mockSurfaceView1, /* keyCode= */ KeyEvent.KEYCODE_A, downEvent)
+ mockOnKeyListener.onKey(mockSurfaceView2, /* keyCode= */ KeyEvent.KEYCODE_A, upEvent)
+
+ // Verify that the onKey method was called with the expected arguments
+ verify(mockOnKeyListener).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, downEvent)
+ verify(mockOnKeyListener, never()).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, downEvent)
+
+ // Remove association
+ service.removeUniqueIdAssociationByPort(inputDevice.name)
+
+ // Associate with Display 2
+ service.addUniqueIdAssociationByPort(
+ inputDevice.name,
+ virtualDisplays[1].display.displayId.toString()
+ )
+
+ // Simulate a KeyEvent
+ service.injectInputEvent(upEvent, InputEventInjectionSync.NONE)
+
+ // Verify that the event went to Display 2 not Display 1
+ verify(mockOnKeyListener).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, upEvent)
+ verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent)
+ }
}
private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index 80282c3..93f97cb 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -523,6 +523,12 @@
createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
)
)
+ // If prefer layout with empty country over mismatched country
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("en-AU", "qwerty"),
+ ENGLISH_US_LAYOUT_DESCRIPTOR
+ )
}
@Test
diff --git a/tests/OneMedia/Android.bp b/tests/OneMedia/Android.bp
index 5c73177..a43cd39 100644
--- a/tests/OneMedia/Android.bp
+++ b/tests/OneMedia/Android.bp
@@ -16,6 +16,7 @@
platform_apis: true,
certificate: "platform",
libs: ["org.apache.http.legacy"],
+ optional_uses_libs: ["org.apache.http.legacy"],
optimize: {
enabled: false,
},
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 1fdf97a..093923f 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -45,13 +45,13 @@
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.util.AtomicFile;
+import android.util.LongArrayQueue;
import android.util.Xml;
-import android.utils.LongArrayQueue;
-import android.utils.XmlUtils;
import androidx.test.InstrumentationRegistry;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.PackageWatchdog.HealthCheckState;
diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
index 7558332..f88d82b 100644
--- a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
+++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
@@ -85,6 +85,8 @@
assertTrue(TelephonyUtils.isValidPlmn("45006"));
assertFalse(TelephonyUtils.isValidPlmn("1234567"));
assertFalse(TelephonyUtils.isValidPlmn("1234"));
+ assertFalse(TelephonyUtils.isValidPlmn(""));
+ assertFalse(TelephonyUtils.isValidPlmn(null));
}
@Test
@@ -94,6 +96,19 @@
assertFalse(TelephonyUtils.isValidService(FIRST_SERVICE_TYPE - 1));
assertFalse(TelephonyUtils.isValidService(LAST_SERVICE_TYPE + 1));
}
+
+ @Test
+ public void testIsValidCountryCode() {
+ assertTrue(TelephonyUtils.isValidCountryCode("US"));
+ assertTrue(TelephonyUtils.isValidCountryCode("cn"));
+ assertFalse(TelephonyUtils.isValidCountryCode("11"));
+ assertFalse(TelephonyUtils.isValidCountryCode("USA"));
+ assertFalse(TelephonyUtils.isValidCountryCode("chn"));
+ assertFalse(TelephonyUtils.isValidCountryCode("U"));
+ assertFalse(TelephonyUtils.isValidCountryCode("G7"));
+ assertFalse(TelephonyUtils.isValidCountryCode(""));
+ assertFalse(TelephonyUtils.isValidCountryCode(null));
+ }
}
diff --git a/tests/TouchLatency/Android.bp b/tests/TouchLatency/Android.bp
index 4ef1ead..7990732 100644
--- a/tests/TouchLatency/Android.bp
+++ b/tests/TouchLatency/Android.bp
@@ -5,6 +5,7 @@
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_android_core_graphics_stack",
}
android_test {
diff --git a/tests/TouchLatency/app/src/main/res/values/styles.xml b/tests/TouchLatency/app/src/main/res/values/styles.xml
index b23a87e..fa352cf 100644
--- a/tests/TouchLatency/app/src/main/res/values/styles.xml
+++ b/tests/TouchLatency/app/src/main/res/values/styles.xml
@@ -18,6 +18,7 @@
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<!-- Customize your theme here. -->
+ <item name="android:windowLayoutInDisplayCutoutMode">default</item>
</style>
</resources>
diff --git a/tests/WindowInsetsTests/AndroidManifest.xml b/tests/WindowInsetsTests/AndroidManifest.xml
index 61dd9d4..dbe9d36 100644
--- a/tests/WindowInsetsTests/AndroidManifest.xml
+++ b/tests/WindowInsetsTests/AndroidManifest.xml
@@ -18,7 +18,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.test.windowinsetstests">
- <application android:label="@string/application_title">
+ <application android:label="@string/application_title"
+ android:theme="@style/base">
<activity android:name=".WindowInsetsTestsMainActivity"
android:exported="true">
<intent-filter>
@@ -29,11 +30,9 @@
<activity android:name=".ChatActivity"
android:label="@string/chat_activity_title"
- android:theme="@style/chat"
android:windowSoftInputMode="adjustResize" />
<activity android:name=".ControllerActivity"
- android:label="@string/controller_activity_title"
- android:theme="@style/controller" />
+ android:label="@string/controller_activity_title" />
</application>
</manifest>
diff --git a/tests/WindowInsetsTests/res/layout/controller_activity.xml b/tests/WindowInsetsTests/res/layout/controller_activity.xml
index 5550eab..7013059 100644
--- a/tests/WindowInsetsTests/res/layout/controller_activity.xml
+++ b/tests/WindowInsetsTests/res/layout/controller_activity.xml
@@ -15,92 +15,110 @@
-->
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
- <LinearLayout
- android:id="@+id/content"
+ <androidx.appcompat.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ />
+
+ <ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical">
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp">
- <Spinner
- android:id="@+id/spinnerBehavior"
+ <LinearLayout
+ android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="20dp" />
+ android:orientation="vertical">
- <ToggleButton
- android:id="@+id/toggleButtonStatus"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:checked="true"
- android:text="Status Bars Toggle Button"
- android:textOff="Status Bars Invisible"
- android:textOn="Status Bars Visible" />
+ <Spinner
+ android:id="@+id/spinnerBehavior"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="20dp" />
- <SeekBar
- android:id="@+id/seekBarStatus"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="20dp"
- android:max="10000"
- android:progress="10000" />
+ <ToggleButton
+ android:id="@+id/toggleButtonStatus"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:checked="true"
+ android:text="@string/status_bars_toggle_button"
+ android:textOff="@string/status_bars_invisible"
+ android:textOn="@string/status_bars_visible" />
- <ToggleButton
- android:id="@+id/toggleButtonNavigation"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:checked="true"
- android:text="Navigation Bars Toggle Button"
- android:textOff="Navigation Bars Invisible"
- android:textOn="Navigation Bars Visible" />
+ <SeekBar
+ android:id="@+id/seekBarStatus"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="20dp"
+ android:max="10000"
+ android:progress="10000" />
- <SeekBar
- android:id="@+id/seekBarNavigation"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="20dp"
- android:max="10000"
- android:progress="10000" />
+ <ToggleButton
+ android:id="@+id/toggleButtonNavigation"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:checked="true"
+ android:text="@string/navigation_bars_toggle_button"
+ android:textOff="@string/navigation_bars_invisible"
+ android:textOn="@string/navigation_bars_visible" />
- <ToggleButton
- android:id="@+id/toggleButtonIme"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:checked="true"
- android:text="IME Toggle Button"
- android:textOff="IME Invisible"
- android:textOn="IME Visible" />
+ <SeekBar
+ android:id="@+id/seekBarNavigation"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="20dp"
+ android:max="10000"
+ android:progress="10000" />
- <SeekBar
- android:id="@+id/seekBarIme"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="20dp"
- android:max="10000"
- android:progress="0" />
+ <ToggleButton
+ android:id="@+id/toggleButtonIme"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:checked="true"
+ android:text="@string/ime_toggle_button"
+ android:textOff="@string/ime_invisible"
+ android:textOn="@string/ime_visible" />
- <TextView
- android:id="@+id/textViewControllableInsets"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_margin="5dp" />
+ <SeekBar
+ android:id="@+id/seekBarIme"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="20dp"
+ android:max="10000"
+ android:progress="0" />
- <EditText
- android:id="@+id/editText"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ems="10"
- android:hint="For testing IME..."
- android:inputType="text"
- android:text="" />
+ <TextView
+ android:id="@+id/textViewControllableInsets"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dp" />
- </LinearLayout>
+ <EditText
+ android:id="@+id/editText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:autofillHints="@string/for_testing_ime"
+ android:hint="@string/for_testing_ime"
+ android:inputType="text"
+ android:text="" />
-</ScrollView>
\ No newline at end of file
+ </LinearLayout>
+
+ </ScrollView>
+
+</LinearLayout>
diff --git a/tests/WindowInsetsTests/res/layout/main_activity.xml b/tests/WindowInsetsTests/res/layout/main_activity.xml
index 621ed89..d6d4ff9 100644
--- a/tests/WindowInsetsTests/res/layout/main_activity.xml
+++ b/tests/WindowInsetsTests/res/layout/main_activity.xml
@@ -16,22 +16,38 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
- <Button
- android:id="@+id/chat_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/chat_activity_title"
- android:textAllCaps="false"/>
+ <androidx.appcompat.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ />
- <Button
- android:id="@+id/controller_button"
- android:layout_width="wrap_content"
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="@string/controller_activity_title"
- android:textAllCaps="false"/>
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp">
+
+ <Button
+ android:id="@+id/chat_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/chat_activity_title"
+ android:textAllCaps="false"/>
+
+ <Button
+ android:id="@+id/controller_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/controller_activity_title"
+ android:textAllCaps="false"/>
+
+ </LinearLayout>
</LinearLayout>
diff --git a/tests/WindowInsetsTests/res/values-night/styles.xml b/tests/WindowInsetsTests/res/values-night/styles.xml
new file mode 100644
index 0000000..323c5fd
--- /dev/null
+++ b/tests/WindowInsetsTests/res/values-night/styles.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+
+ <style name="base" parent="@style/Theme.MaterialComponents">
+ <item name="windowActionBar">false</item>
+ <item name="windowNoTitle">true</item>
+
+ <item name="colorPrimary">@color/primaryColor</item>
+ <item name="colorPrimaryDark">@color/primaryDarkColor</item>
+ <item name="colorSecondary">?attr/colorPrimary</item>
+ <item name="colorOnSecondary">@color/primaryTextColor</item>
+
+ <!-- Window decor -->
+ <item name="android:windowLightStatusBar">false</item>
+ <item name="android:windowLightNavigationBar">false</item>
+
+ </style>
+
+ <color name="primaryColor">#639ff9</color>
+ <color name="primaryLightColor">#6f6bff</color>
+ <color name="primaryDarkColor">#0016bb</color>
+ <color name="primaryTextColor">#ffffff</color>
+
+ <color name="bubble">#333333</color>
+ <color name="bubble_self">#185abc</color>
+
+</resources>
diff --git a/tests/WindowInsetsTests/res/values/strings.xml b/tests/WindowInsetsTests/res/values/strings.xml
index 516d458..7b70852 100644
--- a/tests/WindowInsetsTests/res/values/strings.xml
+++ b/tests/WindowInsetsTests/res/values/strings.xml
@@ -19,6 +19,16 @@
<string name="application_title">Window Insets Tests</string>
<string name="chat_activity_title">New Insets Chat</string>
<string name="controller_activity_title">Window Insets Controller</string>
+ <string name="status_bars_toggle_button">Status Bars Toggle Button</string>
+ <string name="status_bars_invisible">Status Bars Invisible</string>
+ <string name="status_bars_visible">Status Bars Visible</string>
+ <string name="navigation_bars_toggle_button">Navigation Bars Toggle Button</string>
+ <string name="navigation_bars_invisible">Navigation Bars Invisible</string>
+ <string name="navigation_bars_visible">Navigation Bars Visible</string>
+ <string name="ime_toggle_button">IME Bars Toggle Button</string>
+ <string name="ime_invisible">IME Bars Invisible</string>
+ <string name="ime_visible">IME Bars Visible</string>
+ <string name="for_testing_ime">For testing IME…</string>
<!-- The item positions should match the flag values respectively. -->
<string-array name="behaviors">
diff --git a/tests/WindowInsetsTests/res/values/styles.xml b/tests/WindowInsetsTests/res/values/styles.xml
index a84ffbed..4ce6323 100644
--- a/tests/WindowInsetsTests/res/values/styles.xml
+++ b/tests/WindowInsetsTests/res/values/styles.xml
@@ -17,7 +17,7 @@
<resources>
- <style name="chat" parent="@style/Theme.MaterialComponents.Light">
+ <style name="base" parent="@style/Theme.MaterialComponents.Light">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
@@ -27,10 +27,8 @@
<item name="colorOnSecondary">@color/primaryTextColor</item>
<!-- Window decor -->
- <item name="android:statusBarColor">#ffffff</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:windowLightNavigationBar">true</item>
- <item name="android:navigationBarColor">#ffffff</item>
</style>
@@ -63,11 +61,4 @@
<dimen name="bubble_padding">8dp</dimen>
<dimen name="bubble_padding_side">16dp</dimen>
- <style name="controller" parent="android:Theme.Material">
- <item name="android:colorPrimaryDark">#111111</item>
- <item name="android:navigationBarColor">#111111</item>
- <item name="android:colorPrimary">#222222</item>
- <item name="android:colorAccent">#33ccff</item>
- </style>
-
-</resources>
\ No newline at end of file
+</resources>
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
index 167d560..1dd87df 100644
--- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
@@ -16,12 +16,18 @@
package com.google.android.test.windowinsetstests;
-import android.app.Activity;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowInsets.Type.systemGestures;
+
import android.graphics.Insets;
import android.os.Bundle;
import android.view.View;
import android.view.WindowInsets;
-import android.view.WindowInsets.Type;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimationControlListener;
import android.view.WindowInsetsAnimationController;
import android.widget.AdapterView;
@@ -32,7 +38,9 @@
import android.widget.TextView;
import android.widget.ToggleButton;
-public class ControllerActivity extends Activity implements View.OnApplyWindowInsetsListener {
+import androidx.appcompat.app.AppCompatActivity;
+
+public class ControllerActivity extends AppCompatActivity {
private ToggleButton mToggleStatus;
private SeekBar mSeekStatus;
@@ -48,6 +56,29 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.controller_activity);
+ setSupportActionBar(findViewById(R.id.toolbar));
+ getWindow().setDecorFitsSystemWindows(false);
+ findViewById(R.id.root).setOnApplyWindowInsetsListener(
+ (v, insets) -> {
+ final int visibleTypes = systemBars() | displayCutout();
+ final Insets i = insets.getInsets(visibleTypes);
+ v.setPadding(i.left, i.top, i.right, i.bottom);
+
+ // Make the content view not obscured by gesture insets to prevent triggering
+ // system gestures while controlling seek bars.
+ final Insets gi = Insets.subtract(
+ insets.getInsets(systemGestures() | visibleTypes), i);
+ findViewById(R.id.content).setPadding(gi.left, gi.top, gi.right, gi.bottom);
+
+ mNotFromUser[0] = true;
+ updateWidgets(insets, statusBars(), mToggleStatus, mSeekStatus);
+ updateWidgets(insets, navigationBars(), mToggleNavigation, mSeekNavigation);
+ updateWidgets(insets, ime(), mToggleIme, mSeekIme);
+ mLastInsets = insets;
+ mNotFromUser[0] = false;
+
+ return WindowInsets.CONSUMED;
+ });
final Spinner spinnerBehavior = findViewById(R.id.spinnerBehavior);
ArrayAdapter<CharSequence> adapterBehavior = ArrayAdapter.createFromResource(this,
R.array.behaviors, android.R.layout.simple_spinner_item);
@@ -66,23 +97,21 @@
});
mToggleStatus = findViewById(R.id.toggleButtonStatus);
mToggleStatus.setTag(mNotFromUser);
- mToggleStatus.setOnCheckedChangeListener(new ToggleListener(Type.statusBars()));
+ mToggleStatus.setOnCheckedChangeListener(new ToggleListener(statusBars()));
mSeekStatus = findViewById(R.id.seekBarStatus);
- mSeekStatus.setOnSeekBarChangeListener(new SeekBarListener(Type.statusBars()));
+ mSeekStatus.setOnSeekBarChangeListener(new SeekBarListener(statusBars()));
mToggleNavigation = findViewById(R.id.toggleButtonNavigation);
mToggleNavigation.setTag(mNotFromUser);
- mToggleNavigation.setOnCheckedChangeListener(new ToggleListener(Type.navigationBars()));
+ mToggleNavigation.setOnCheckedChangeListener(new ToggleListener(navigationBars()));
mSeekNavigation = findViewById(R.id.seekBarNavigation);
- mSeekNavigation.setOnSeekBarChangeListener(new SeekBarListener(Type.navigationBars()));
+ mSeekNavigation.setOnSeekBarChangeListener(new SeekBarListener(navigationBars()));
mToggleIme = findViewById(R.id.toggleButtonIme);
mToggleIme.setTag(mNotFromUser);
- mToggleIme.setOnCheckedChangeListener(new ToggleListener(Type.ime()));
+ mToggleIme.setOnCheckedChangeListener(new ToggleListener(ime()));
mSeekIme = findViewById(R.id.seekBarIme);
- mSeekIme.setOnSeekBarChangeListener(new SeekBarListener(Type.ime()));
+ mSeekIme.setOnSeekBarChangeListener(new SeekBarListener(ime()));
mTextControllableInsets = findViewById(R.id.textViewControllableInsets);
- final View contentView = findViewById(R.id.content);
- contentView.setOnApplyWindowInsetsListener(this);
- contentView.getWindowInsetsController().addOnControllableInsetsChangedListener(
+ mTextControllableInsets.getWindowInsetsController().addOnControllableInsetsChangedListener(
(c, types) -> mTextControllableInsets.setText(
"ControllableInsetsTypes:\n" + insetsTypesToString(types)));
}
@@ -91,22 +120,6 @@
return types == 0 ? "none" : WindowInsets.Type.toString(types);
}
- @Override
- public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
- mNotFromUser[0] = true;
- updateWidgets(insets, Type.statusBars(), mToggleStatus, mSeekStatus);
- updateWidgets(insets, Type.navigationBars(), mToggleNavigation, mSeekNavigation);
- updateWidgets(insets, Type.ime(), mToggleIme, mSeekIme);
- mLastInsets = insets;
- mNotFromUser[0] = false;
-
- // Prevent triggering system gestures while controlling seek bars.
- final Insets gestureInsets = insets.getInsets(Type.systemGestures());
- v.setPadding(gestureInsets.left, 0, gestureInsets.right, 0);
-
- return v.onApplyWindowInsets(insets);
- }
-
private void updateWidgets(WindowInsets insets, int types, ToggleButton toggle, SeekBar seek) {
final boolean isVisible = insets.isVisible(types);
final boolean wasVisible = mLastInsets != null ? mLastInsets.isVisible(types) : !isVisible;
@@ -121,7 +134,7 @@
private static class ToggleListener implements CompoundButton.OnCheckedChangeListener {
- private final @Type.InsetsType int mTypes;
+ private final @InsetsType int mTypes;
ToggleListener(int types) {
mTypes = types;
@@ -143,7 +156,7 @@
private static class SeekBarListener implements SeekBar.OnSeekBarChangeListener {
- private final @Type.InsetsType int mTypes;
+ private final @InsetsType int mTypes;
private WindowInsetsAnimationController mController;
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java
index 8b77a78..278ad84 100644
--- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java
@@ -16,16 +16,30 @@
package com.google.android.test.windowinsetstests;
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.systemBars;
-public class WindowInsetsTestsMainActivity extends Activity {
+import android.content.Intent;
+import android.graphics.Insets;
+import android.os.Bundle;
+import android.view.WindowInsets;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class WindowInsetsTestsMainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
+ setSupportActionBar(findViewById(R.id.toolbar));
+
+ findViewById(R.id.root).setOnApplyWindowInsetsListener(
+ (v, insets) -> {
+ final Insets i = insets.getInsets(systemBars() | displayCutout());
+ v.setPadding(i.left, i.top, i.right, i.bottom);
+ return WindowInsets.CONSUMED;
+ });
findViewById(R.id.chat_button).setOnClickListener(
v -> startActivity(new Intent(this, ChatActivity.class)));
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
index fdf8fb8..c8b60e5 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -17,9 +17,11 @@
package com.android.server.vcn.routeselection;
import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY;
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY;
import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY;
-import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.PACKET_LOSS_UNAVALAIBLE;
+import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.MIN_VALID_EXPECTED_RX_PACKET_NUM;
+import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.getMaxSeqNumIncreasePerSecond;
import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static org.junit.Assert.assertEquals;
@@ -44,6 +46,7 @@
import android.os.OutcomeReceiver;
import android.os.PowerManager;
+import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculationResult;
import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculator;
import com.android.server.vcn.routeselection.NetworkMetricMonitor.IpSecTransformWrapper;
import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
@@ -65,6 +68,7 @@
private static final int REPLAY_BITMAP_LEN_BYTE = 512;
private static final int REPLAY_BITMAP_LEN_BIT = REPLAY_BITMAP_LEN_BYTE * 8;
private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD = 5;
+ private static final int MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED = -1;
private static final long POLL_IPSEC_STATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(30L);
@Mock private IpSecTransformWrapper mIpSecTransform;
@@ -91,6 +95,9 @@
eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
anyInt()))
.thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD);
+ when(mCarrierConfig.getInt(
+ eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt()))
+ .thenReturn(MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED);
when(mDependencies.getPacketLossCalculator()).thenReturn(mPacketLossCalculator);
@@ -112,6 +119,20 @@
.build();
}
+ private static IpSecTransformState newNextTransformState(
+ IpSecTransformState before,
+ long timeDiffMillis,
+ long rxSeqNoDiff,
+ long packtCountDiff,
+ int packetInWin) {
+ return new IpSecTransformState.Builder()
+ .setTimestampMillis(before.getTimestampMillis() + timeDiffMillis)
+ .setRxHighestSequenceNumber(before.getRxHighestSequenceNumber() + rxSeqNoDiff)
+ .setPacketCount(before.getPacketCount() + packtCountDiff)
+ .setReplayBitmap(newReplayBitmap(packetInWin))
+ .build();
+ }
+
private static byte[] newReplayBitmap(int receivedPktCnt) {
final BitSet bitSet = new BitSet(REPLAY_BITMAP_LEN_BIT);
for (int i = 0; i < receivedPktCnt; i++) {
@@ -165,7 +186,7 @@
// Verify the first polled state is stored
assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
verify(mPacketLossCalculator, never())
- .getPacketLossRatePercentage(any(), any(), anyString());
+ .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
// Verify next poll is scheduled
assertNull(mTestLooper.nextMessage());
@@ -278,7 +299,7 @@
xfrmStateReceiver.onResult(newTransformState(1, 1, newReplayBitmap(1)));
verify(mPacketLossCalculator, never())
- .getPacketLossRatePercentage(any(), any(), anyString());
+ .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
}
@Test
@@ -289,17 +310,19 @@
xfrmStateReceiver.onError(new RuntimeException("Test"));
verify(mPacketLossCalculator, never())
- .getPacketLossRatePercentage(any(), any(), anyString());
+ .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
}
private void checkHandleLossRate(
- int mockPacketLossRate, boolean isLastStateExpectedToUpdate, boolean isCallbackExpected)
+ PacketLossCalculationResult mockPacketLossRate,
+ boolean isLastStateExpectedToUpdate,
+ boolean isCallbackExpected)
throws Exception {
final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
startMonitorAndCaptureStateReceiver();
doReturn(mockPacketLossRate)
.when(mPacketLossCalculator)
- .getPacketLossRatePercentage(any(), any(), anyString());
+ .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
// Mock receiving two states with mTransformStateInitial and an arbitrary transformNew
final IpSecTransformState transformNew = newTransformState(1, 1, newReplayBitmap(1));
@@ -309,7 +332,10 @@
// Verifications
verify(mPacketLossCalculator)
.getPacketLossRatePercentage(
- eq(mTransformStateInitial), eq(transformNew), anyString());
+ eq(mTransformStateInitial),
+ eq(transformNew),
+ eq(MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED),
+ anyString());
if (isLastStateExpectedToUpdate) {
assertEquals(transformNew, mIpSecPacketLossDetector.getLastTransformState());
@@ -327,30 +353,53 @@
@Test
public void testHandleLossRate_validationPass() throws Exception {
checkHandleLossRate(
- 2, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+ PacketLossCalculationResult.valid(2),
+ true /* isLastStateExpectedToUpdate */,
+ true /* isCallbackExpected */);
}
@Test
public void testHandleLossRate_validationFail() throws Exception {
checkHandleLossRate(
- 22, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+ PacketLossCalculationResult.valid(22),
+ true /* isLastStateExpectedToUpdate */,
+ true /* isCallbackExpected */);
verify(mConnectivityManager).reportNetworkConnectivity(mNetwork, false);
}
@Test
public void testHandleLossRate_resultUnavalaible() throws Exception {
checkHandleLossRate(
- PACKET_LOSS_UNAVALAIBLE,
+ PacketLossCalculationResult.invalid(),
false /* isLastStateExpectedToUpdate */,
false /* isCallbackExpected */);
}
+ @Test
+ public void testHandleLossRate_unusualSeqNumLeap_highLossRate() throws Exception {
+ checkHandleLossRate(
+ PacketLossCalculationResult.unusualSeqNumLeap(22),
+ true /* isLastStateExpectedToUpdate */,
+ false /* isCallbackExpected */);
+ }
+
+ @Test
+ public void testHandleLossRate_unusualSeqNumLeap_lowLossRate() throws Exception {
+ checkHandleLossRate(
+ PacketLossCalculationResult.unusualSeqNumLeap(2),
+ true /* isLastStateExpectedToUpdate */,
+ true /* isCallbackExpected */);
+ }
+
private void checkGetPacketLossRate(
- IpSecTransformState oldState, IpSecTransformState newState, int expectedLossRate)
+ IpSecTransformState oldState,
+ IpSecTransformState newState,
+ PacketLossCalculationResult expectedLossRate)
throws Exception {
assertEquals(
expectedLossRate,
- mPacketLossCalculator.getPacketLossRatePercentage(oldState, newState, TAG));
+ mPacketLossCalculator.getPacketLossRatePercentage(
+ oldState, newState, MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED, TAG));
}
private void checkGetPacketLossRate(
@@ -362,14 +411,45 @@
throws Exception {
final IpSecTransformState newState =
newTransformState(rxSeqNo, packetCount, newReplayBitmap(packetInWin));
+ checkGetPacketLossRate(
+ oldState, newState, PacketLossCalculationResult.valid(expectedDataLossRate));
+ }
+
+ private void checkGetPacketLossRate(
+ IpSecTransformState oldState,
+ int rxSeqNo,
+ int packetCount,
+ int packetInWin,
+ PacketLossCalculationResult expectedDataLossRate)
+ throws Exception {
+ final IpSecTransformState newState =
+ newTransformState(rxSeqNo, packetCount, newReplayBitmap(packetInWin));
checkGetPacketLossRate(oldState, newState, expectedDataLossRate);
}
@Test
public void testGetPacketLossRate_replayWindowUnchanged() throws Exception {
checkGetPacketLossRate(
- mTransformStateInitial, mTransformStateInitial, PACKET_LOSS_UNAVALAIBLE);
- checkGetPacketLossRate(mTransformStateInitial, 3000, 2000, 2000, PACKET_LOSS_UNAVALAIBLE);
+ mTransformStateInitial,
+ mTransformStateInitial,
+ PacketLossCalculationResult.invalid());
+ checkGetPacketLossRate(
+ mTransformStateInitial, 3000, 2000, 2000, PacketLossCalculationResult.invalid());
+ }
+
+ @Test
+ public void testGetPacketLossRate_expectedPacketNumTooFew() throws Exception {
+ final int oldRxNo = 4096;
+ final int oldPktCnt = 4096;
+ final int pktCntDiff = MIN_VALID_EXPECTED_RX_PACKET_NUM - 1;
+ final byte[] bitmapReceiveAll = newReplayBitmap(4096);
+
+ final IpSecTransformState oldState =
+ newTransformState(oldRxNo, oldPktCnt, bitmapReceiveAll);
+ final IpSecTransformState newState =
+ newTransformState(oldRxNo + pktCntDiff, oldPktCnt + pktCntDiff, bitmapReceiveAll);
+
+ checkGetPacketLossRate(oldState, newState, PacketLossCalculationResult.invalid());
}
@Test
@@ -419,6 +499,45 @@
checkGetPacketLossRate(oldState, 20000, 14000, 3000, 10);
}
+ private void checkGetPktLossRate_unusualSeqNumLeap(
+ int maxSeqNumIncreasePerSecond,
+ int timeDiffMillis,
+ int rxSeqNoDiff,
+ PacketLossCalculationResult expected)
+ throws Exception {
+ final IpSecTransformState oldState = mTransformStateInitial;
+ final IpSecTransformState newState =
+ newNextTransformState(
+ oldState,
+ timeDiffMillis,
+ rxSeqNoDiff,
+ 1 /* packtCountDiff */,
+ 1 /* packetInWin */);
+
+ assertEquals(
+ expected,
+ mPacketLossCalculator.getPacketLossRatePercentage(
+ oldState, newState, maxSeqNumIncreasePerSecond, TAG));
+ }
+
+ @Test
+ public void testGetPktLossRate_unusualSeqNumLeap() throws Exception {
+ checkGetPktLossRate_unusualSeqNumLeap(
+ 10000 /* maxSeqNumIncreasePerSecond */,
+ (int) TimeUnit.SECONDS.toMillis(2L),
+ 30000 /* rxSeqNoDiff */,
+ PacketLossCalculationResult.unusualSeqNumLeap(100));
+ }
+
+ @Test
+ public void testGetPktLossRate_unusualSeqNumLeap_smallSeqNumDiff() throws Exception {
+ checkGetPktLossRate_unusualSeqNumLeap(
+ 10000 /* maxSeqNumIncreasePerSecond */,
+ (int) TimeUnit.SECONDS.toMillis(2L),
+ 5000 /* rxSeqNoDiff */,
+ PacketLossCalculationResult.valid(100));
+ }
+
// Verify the polling event is scheduled with expected delays
private void verifyPollEventDelayAndScheduleNext(long expectedDelayMs) {
if (expectedDelayMs > 0) {
@@ -445,4 +564,24 @@
// Verify the 3rd poll is scheduled with configured delay
verifyPollEventDelayAndScheduleNext(POLL_IPSEC_STATE_INTERVAL_MS);
}
+
+ @Test
+ public void testGetMaxSeqNumIncreasePerSecond() throws Exception {
+ final int seqNumLeapNegative = 500_000;
+ when(mCarrierConfig.getInt(
+ eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt()))
+ .thenReturn(seqNumLeapNegative);
+ assertEquals(seqNumLeapNegative, getMaxSeqNumIncreasePerSecond(mCarrierConfig));
+ }
+
+ @Test
+ public void testGetMaxSeqNumIncreasePerSecond_negativeValue() throws Exception {
+ final int seqNumLeapNegative = -10;
+ when(mCarrierConfig.getInt(
+ eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt()))
+ .thenReturn(seqNumLeapNegative);
+ assertEquals(
+ MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED,
+ getMaxSeqNumIncreasePerSecond(mCarrierConfig));
+ }
}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index af6daa1..edad678 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -107,7 +107,6 @@
@Mock protected Context mContext;
@Mock protected Network mNetwork;
@Mock protected FeatureFlags mFeatureFlags;
- @Mock protected android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags;
@Mock protected TelephonySubscriptionSnapshot mSubscriptionSnapshot;
@Mock protected ConnectivityManager mConnectivityManager;
@Mock protected TelephonyManager mTelephonyManager;
@@ -123,6 +122,7 @@
mSetFlagsRule.enableFlags(Flags.FLAG_VALIDATE_NETWORK_ON_IPSEC_LOSS);
mSetFlagsRule.enableFlags(Flags.FLAG_EVALUATE_IPSEC_LOSS_ON_LP_NC_CHANGE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_HANDLE_SEQ_NUM_LEAP);
when(mNetwork.getNetId()).thenReturn(-1);
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.cpp b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
index 9d61111..be5c197 100644
--- a/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
@@ -18,11 +18,13 @@
#include <stdio.h>
+#include <algorithm>
#include <iomanip>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
+#include <unordered_set>
#include "Errors.h"
@@ -30,21 +32,39 @@
using namespace google::protobuf::io;
using namespace std;
+static bool outer_class_name_clashes_with_any_message(const string& outer_class_name,
+ const vector<DescriptorProto>& messages) {
+ return any_of(messages.cbegin(), messages.cend(), [&](const DescriptorProto& message) {
+ return message.name() == outer_class_name;
+ });
+}
+
/**
* If the descriptor gives us a class name, use that. Otherwise make one up from
* the filename of the .proto file.
*/
-static string make_outer_class_name(const FileDescriptorProto& file_descriptor) {
+static string make_outer_class_name(const FileDescriptorProto& file_descriptor,
+ const vector<DescriptorProto>& messages) {
string name = file_descriptor.options().java_outer_classname();
- if (name.size() == 0) {
- name = to_camel_case(file_base_name(file_descriptor.name()));
- if (name.size() == 0) {
- ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
- "Unable to make an outer class name for file: %s",
- file_descriptor.name().c_str());
- name = "Unknown";
- }
+ if (!name.empty()) {
+ return name;
}
+
+ // Outer class and messages with the same name would result in invalid java (outer class and
+ // inner class cannot have same names).
+ // If the outer class name clashes with any message, let's append an "OuterClass" suffix.
+ // This behavior is consistent with the standard protoc.
+ name = to_camel_case(file_base_name(file_descriptor.name()));
+ while (outer_class_name_clashes_with_any_message(name, messages)) {
+ name += "OuterClass";
+ }
+
+ if (name.empty()) {
+ ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unable to make an outer class name for file: %s",
+ file_descriptor.name().c_str());
+ name = "Unknown";
+ }
+
return name;
}
@@ -149,6 +169,12 @@
write_field(text, message.field(i), indented);
}
+ // Extensions
+ N = message.extension_size();
+ for (int i = 0; i < N; i++) {
+ write_field(text, message.extension(i), indented);
+ }
+
text << indent << "}" << endl;
text << endl;
}
@@ -165,7 +191,7 @@
stringstream text;
string const package_name = make_java_package(file_descriptor);
- string const outer_class_name = make_outer_class_name(file_descriptor);
+ string const outer_class_name = make_outer_class_name(file_descriptor, messages);
text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
text << "// source: " << file_descriptor.name() << endl << endl;
@@ -214,7 +240,7 @@
*/
static void write_multiple_files(CodeGeneratorResponse* response,
const FileDescriptorProto& file_descriptor,
- set<string> messages_to_compile) {
+ const unordered_set<string>& messages_allowlist) {
// If there is anything to put in the outer class file, create one
if (file_descriptor.enum_type_size() > 0) {
vector<EnumDescriptorProto> enums;
@@ -222,7 +248,7 @@
for (int i = 0; i < N; i++) {
auto enum_full_name =
file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
- if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+ if (!messages_allowlist.empty() && !messages_allowlist.count(enum_full_name)) {
continue;
}
enums.push_back(file_descriptor.enum_type(i));
@@ -230,9 +256,10 @@
vector<DescriptorProto> messages;
- if (messages_to_compile.empty() || !enums.empty()) {
+ if (messages_allowlist.empty() || !enums.empty()) {
write_file(response, file_descriptor,
- make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+ make_file_name(file_descriptor,
+ make_outer_class_name(file_descriptor, messages)),
true, enums, messages);
}
}
@@ -246,12 +273,12 @@
auto message_full_name =
file_descriptor.package() + "." + file_descriptor.message_type(i).name();
- if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+ if (!messages_allowlist.empty() && !messages_allowlist.count(message_full_name)) {
continue;
}
messages.push_back(file_descriptor.message_type(i));
- if (messages_to_compile.empty() || !messages.empty()) {
+ if (messages_allowlist.empty() || !messages.empty()) {
write_file(response, file_descriptor,
make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
false, enums, messages);
@@ -261,14 +288,14 @@
static void write_single_file(CodeGeneratorResponse* response,
const FileDescriptorProto& file_descriptor,
- set<string> messages_to_compile) {
+ const unordered_set<string>& messages_allowlist) {
int N;
vector<EnumDescriptorProto> enums;
N = file_descriptor.enum_type_size();
for (int i = 0; i < N; i++) {
auto enum_full_name = file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
- if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+ if (!messages_allowlist.empty() && !messages_allowlist.count(enum_full_name)) {
continue;
}
@@ -281,22 +308,23 @@
auto message_full_name =
file_descriptor.package() + "." + file_descriptor.message_type(i).name();
- if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+ if (!messages_allowlist.empty() && !messages_allowlist.count(message_full_name)) {
continue;
}
messages.push_back(file_descriptor.message_type(i));
}
- if (messages_to_compile.empty() || !enums.empty() || !messages.empty()) {
+ if (messages_allowlist.empty() || !enums.empty() || !messages.empty()) {
write_file(response, file_descriptor,
- make_file_name(file_descriptor, make_outer_class_name(file_descriptor)), true,
- enums, messages);
+ make_file_name(file_descriptor,
+ make_outer_class_name(file_descriptor, messages)),
+ true, enums, messages);
}
}
static void parse_args_string(stringstream args_string_stream,
- set<string>* messages_to_compile_out) {
+ unordered_set<string>& messages_allowlist_out) {
string line;
while (getline(args_string_stream, line, ';')) {
stringstream line_ss(line);
@@ -305,7 +333,7 @@
if (arg_name == "include_filter") {
string full_message_name;
while (getline(line_ss, full_message_name, ',')) {
- messages_to_compile_out->insert(full_message_name);
+ messages_allowlist_out.insert(full_message_name);
}
} else {
ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unexpected argument '%s'.", arg_name.c_str());
@@ -316,10 +344,10 @@
CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request) {
CodeGeneratorResponse response;
- set<string> messages_to_compile;
+ unordered_set<string> messages_allowlist;
auto request_params = request.parameter();
if (!request_params.empty()) {
- parse_args_string(stringstream(request_params), &messages_to_compile);
+ parse_args_string(stringstream(request_params), messages_allowlist);
}
// Build the files we need.
@@ -328,9 +356,9 @@
const FileDescriptorProto& file_descriptor = request.proto_file(i);
if (should_generate_for_file(request, file_descriptor.name())) {
if (file_descriptor.options().java_multiple_files()) {
- write_multiple_files(&response, file_descriptor, messages_to_compile);
+ write_multiple_files(&response, file_descriptor, messages_allowlist);
} else {
- write_single_file(&response, file_descriptor, messages_to_compile);
+ write_single_file(&response, file_descriptor, messages_allowlist);
}
}
}
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 45ab986..2ba5705 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -993,6 +993,16 @@
* {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
* or {@link #setupInterfaceForSoftApMode(String)}.
*
+ * <p>
+ * When an Access Point’s beacon or probe response includes a Multi-BSSID Element, the
+ * returned scan results should include separate scan result for each BSSID within the
+ * Multi-BSSID Information Element. This includes both transmitted and non-transmitted BSSIDs.
+ * Original Multi-BSSID Element will be included in the Information Elements attached to
+ * each of the scan results.
+ * Note: This is the expected behavior for devices supporting 11ax (WiFi-6) and above, and an
+ * optional requirement for devices running with older WiFi generations.
+ * </p>
+ *
* @param ifaceName Name of the interface.
* @param scanType The type of scan result to be returned, can be
* {@link #SCAN_TYPE_SINGLE_SCAN} or {@link #SCAN_TYPE_PNO_SCAN}.